fetch: protect branches checked out in all worktrees

Refuse to fetch into the currently checked out branch of any working tree, not just the current one. Fixes this previously reported bug: https://lore.kernel.org/git/cb957174-5e9a-5603-ea9e-ac9b58a2eaad@mathema.de/ As a side effect of using find_shared_symref, we’ll also refuse the fetch when we’re on a detached HEAD because we’re rebasing or bisecting on the branch in question. This seems like a sensible change. Signed-off-by: Anders Kaseorg <andersk@mit.edu> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Anders Kaseorg committed Dec 1, 2021 at 14:15 UTC 8bc1f39f411e06b940230dc84da5568dfafec922
2 files changed +58 -35
builtin/fetch.c
+40 -35
@@ -28,6 +28,7 @@
28 #include "promisor-remote.h"
29 #include "commit-graph.h"
30 #include "shallow.h"
31 +#include "worktree.h"
32
33 #define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000)
34
@@ -848,13 +849,12 @@ static void format_display(struct strbuf *display, char code,
849
850 static int update_local_ref(struct ref *ref,
851 struct ref_transaction *transaction,
851 - const char *remote,
852 - const struct ref *remote_ref,
853 - struct strbuf *display,
854 - int summary_width)
852 + const char *remote, const struct ref *remote_ref,
853 + struct strbuf *display, int summary_width,
854 + struct worktree **worktrees)
855 {
856 struct commit *current = NULL, *updated;
857 - struct branch *current_branch = branch_get(NULL);
857 + const struct worktree *wt;
858 const char *pretty_ref = prettify_refname(ref->name);
859 int fast_forward = 0;
860
@@ -868,16 +868,17 @@ static int update_local_ref(struct ref *ref,
868 return 0;
869 }
870
871 - if (current_branch &&
872 - !strcmp(ref->name, current_branch->name) &&
873 - !(update_head_ok || is_bare_repository()) &&
874 - !is_null_oid(&ref->old_oid)) {
871 + if (!update_head_ok &&
872 + (wt = find_shared_symref(worktrees, "HEAD", ref->name)) &&
873 + !wt->is_bare && !is_null_oid(&ref->old_oid)) {
874 /*
875 * If this is the head, and it's not okay to update
876 * the head, and the old value of the head isn't empty...
877 */
878 format_display(display, '!', _("[rejected]"),
880 - _("can't fetch in current branch"),
879 + wt->is_current ?
880 + _("can't fetch in current branch") :
881 + _("checked out in another worktree"),
882 remote, pretty_ref, summary_width);
883 return 1;
884 }
@@ -1076,7 +1077,8 @@ N_("it took %.2f seconds to check forced updates; you can use\n"
1077 "to avoid this check\n");
1078
1079 static int store_updated_refs(const char *raw_url, const char *remote_name,
1079 - int connectivity_checked, struct ref *ref_map)
1080 + int connectivity_checked, struct ref *ref_map,
1081 + struct worktree **worktrees)
1082 {
1083 struct fetch_head fetch_head;
1084 int url_len, i, rc = 0;
@@ -1205,7 +1207,8 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
1207 strbuf_reset(&note);
1208 if (ref) {
1209 rc |= update_local_ref(ref, transaction, what,
1208 - rm, &note, summary_width);
1210 + rm, &note, summary_width,
1211 + worktrees);
1212 free(ref);
1213 } else if (write_fetch_head || dry_run) {
1214 /*
@@ -1298,7 +1301,9 @@ static int check_exist_and_connected(struct ref *ref_map)
1301 return check_connected(iterate_ref_map, &rm, &opt);
1302 }
1303
1301 -static int fetch_and_consume_refs(struct transport *transport, struct ref *ref_map)
1304 +static int fetch_and_consume_refs(struct transport *transport,
1305 + struct ref *ref_map,
1306 + struct worktree **worktrees)
1307 {
1308 int connectivity_checked = 1;
1309 int ret;
@@ -1319,10 +1324,8 @@ static int fetch_and_consume_refs(struct transport *transport, struct ref *ref_m
1324 }
1325
1326 trace2_region_enter("fetch", "consume_refs", the_repository);
1322 - ret = store_updated_refs(transport->url,
1323 - transport->remote->name,
1324 - connectivity_checked,
1325 - ref_map);
1327 + ret = store_updated_refs(transport->url, transport->remote->name,
1328 + connectivity_checked, ref_map, worktrees);
1329 trace2_region_leave("fetch", "consume_refs", the_repository);
1330
1331 out:
@@ -1385,19 +1388,18 @@ static int prune_refs(struct refspec *rs, struct ref *ref_map,
1388 return result;
1389 }
1390
1388 -static void check_not_current_branch(struct ref *ref_map)
1391 +static void check_not_current_branch(struct ref *ref_map,
1392 + struct worktree **worktrees)
1393 {
1390 - struct branch *current_branch = branch_get(NULL);
1391 -
1392 - if (is_bare_repository() || !current_branch)
1393 - return;
1394 -
1394 + const struct worktree *wt;
1395 for (; ref_map; ref_map = ref_map->next)
1396 - if (ref_map->peer_ref && !strcmp(current_branch->refname,
1397 - ref_map->peer_ref->name))
1398 - die(_("refusing to fetch into current branch %s "
1399 - "of non-bare repository"),
1400 - current_branch->refname);
1396 + if (ref_map->peer_ref &&
1397 + (wt = find_shared_symref(worktrees, "HEAD",
1398 + ref_map->peer_ref->name)) &&
1399 + !wt->is_bare)
1400 + die(_("refusing to fetch into branch '%s' "
1401 + "checked out at '%s'"),
1402 + ref_map->peer_ref->name, wt->path);
1403 }
1404
1405 static int truncate_fetch_head(void)
@@ -1495,7 +1497,8 @@ static struct transport *prepare_transport(struct remote *remote, int deepen)
1497 return transport;
1498 }
1499
1498 -static void backfill_tags(struct transport *transport, struct ref *ref_map)
1500 +static void backfill_tags(struct transport *transport, struct ref *ref_map,
1501 + struct worktree **worktrees)
1502 {
1503 int cannot_reuse;
1504
@@ -1516,7 +1519,7 @@ static void backfill_tags(struct transport *transport, struct ref *ref_map)
1519 transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
1520 transport_set_option(transport, TRANS_OPT_DEPTH, "0");
1521 transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL);
1519 - fetch_and_consume_refs(transport, ref_map);
1522 + fetch_and_consume_refs(transport, ref_map, worktrees);
1523
1524 if (gsecondary) {
1525 transport_disconnect(gsecondary);
@@ -1534,6 +1537,7 @@ static int do_fetch(struct transport *transport,
1537 struct transport_ls_refs_options transport_ls_refs_options =
1538 TRANSPORT_LS_REFS_OPTIONS_INIT;
1539 int must_list_refs = 1;
1540 + struct worktree **worktrees = get_worktrees();
1541
1542 if (tags == TAGS_DEFAULT) {
1543 if (transport->remote->fetch_tags == 2)
@@ -1589,7 +1593,7 @@ static int do_fetch(struct transport *transport,
1593 ref_map = get_ref_map(transport->remote, remote_refs, rs,
1594 tags, &autotags);
1595 if (!update_head_ok)
1592 - check_not_current_branch(ref_map);
1596 + check_not_current_branch(ref_map, worktrees);
1597
1598 if (tags == TAGS_DEFAULT && autotags)
1599 transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
@@ -1607,7 +1611,7 @@ static int do_fetch(struct transport *transport,
1611 transport->url);
1612 }
1613 }
1610 - if (fetch_and_consume_refs(transport, ref_map)) {
1614 + if (fetch_and_consume_refs(transport, ref_map, worktrees)) {
1615 free_refs(ref_map);
1616 retcode = 1;
1617 goto cleanup;
@@ -1656,7 +1660,7 @@ static int do_fetch(struct transport *transport,
1660 "you need to specify exactly one branch with the --set-upstream option"));
1661 }
1662 }
1659 - skip:
1663 +skip:
1664 free_refs(ref_map);
1665
1666 /* if neither --no-tags nor --tags was specified, do automated tag
@@ -1666,11 +1670,12 @@ static int do_fetch(struct transport *transport,
1670 ref_map = NULL;
1671 find_non_local_tags(remote_refs, &ref_map, &tail);
1672 if (ref_map)
1669 - backfill_tags(transport, ref_map);
1673 + backfill_tags(transport, ref_map, worktrees);
1674 free_refs(ref_map);
1675 }
1676
1673 - cleanup:
1677 +cleanup:
1678 + free_worktrees(worktrees);
1679 return retcode;
1680 }
1681
t/t5516-fetch-push.sh
+18
@@ -1771,4 +1771,22 @@ test_expect_success 'denyCurrentBranch and worktrees' '
1771 git -C cloned push origin HEAD:new-wt &&
1772 test_must_fail git -C cloned push --delete origin new-wt
1773 '
1774 +
1775 +test_expect_success 'refuse fetch to current branch of worktree' '
1776 + test_when_finished "git worktree remove --force wt && git branch -D wt" &&
1777 + git worktree add wt &&
1778 + test_commit apple &&
1779 + test_must_fail git fetch . HEAD:wt &&
1780 + git fetch -u . HEAD:wt
1781 +'
1782 +
1783 +test_expect_success 'refuse fetch to current branch of bare repository worktree' '
1784 + test_when_finished "rm -fr bare.git" &&
1785 + git clone --bare . bare.git &&
1786 + git -C bare.git worktree add wt &&
1787 + test_commit banana &&
1788 + test_must_fail git -C bare.git fetch .. HEAD:wt &&
1789 + git -C bare.git fetch -u .. HEAD:wt
1790 +'
1791 +
1792 test_done