git@vger.kernel.org list mirror (unofficial, one of many)
 help / color / mirror / code / Atom feed
* [PATCH v7 0/8] protect branches checked out in all worktrees
@ 2021-12-01 22:15 Anders Kaseorg
  2021-12-01 22:15 ` [PATCH v7 1/8] fetch: lowercase error messages Anders Kaseorg
                   ` (7 more replies)
  0 siblings, 8 replies; 20+ messages in thread
From: Anders Kaseorg @ 2021-12-01 22:15 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Johannes Schindelin, Jeff King, Andreas Heiduk,
	Ævar Arnfjörð Bjarmason ,
	Jiang Xin, Anders Kaseorg

‘git fetch’ (without ‘--update-head-ok’), ‘git receive-pack’, and ‘git
branch -M’ protect the currently checked out branch from being
accidentally updated.  However, the code for these checks predates
‘git worktree’.  Improve it to protect branches checked out in all
worktrees, not just the current one.

Anders Kaseorg (8):
  fetch: lowercase error messages
  receive-pack: lowercase error messages
  branch: lowercase error messages
  worktree: simplify find_shared_symref() memory ownership model
  fetch: protect branches checked out in all worktrees
  receive-pack: clean dead code from update_worktree()
  receive-pack: protect current branch for bare repository worktree
  branch: protect branches checked out in all worktrees

 branch.c                        |  45 +++++++-----
 builtin/branch.c                |   7 +-
 builtin/fetch.c                 | 119 +++++++++++++++++---------------
 builtin/notes.c                 |   6 +-
 builtin/receive-pack.c          |  92 +++++++++++++-----------
 t/t2018-checkout-branch.sh      |   2 +-
 t/t3200-branch.sh               |  11 ++-
 t/t5504-fetch-receive-strict.sh |   2 +-
 t/t5516-fetch-push.sh           |  32 +++++++++
 worktree.c                      |   8 +--
 worktree.h                      |   5 +-
 11 files changed, 198 insertions(+), 131 deletions(-)

-- 
2.34.1


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v7 1/8] fetch: lowercase error messages
  2021-12-01 22:15 [PATCH v7 0/8] protect branches checked out in all worktrees Anders Kaseorg
@ 2021-12-01 22:15 ` Anders Kaseorg
  2021-12-01 22:15 ` [PATCH v7 2/8] receive-pack: " Anders Kaseorg
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 20+ messages in thread
From: Anders Kaseorg @ 2021-12-01 22:15 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Johannes Schindelin, Jeff King, Andreas Heiduk,
	Ævar Arnfjörð Bjarmason ,
	Jiang Xin, Anders Kaseorg

Documentation/CodingGuidelines says “do not end error messages with a
full stop” and “do not capitalize the first word”.  Clean up existing
messages, some of which we will be touching in later steps in the
series, that deviate from these rules in this file, as a preparation for
the main part of the topic.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
---
 builtin/fetch.c | 50 +++++++++++++++++++++++++------------------------
 1 file changed, 26 insertions(+), 24 deletions(-)

diff --git a/builtin/fetch.c b/builtin/fetch.c
index f7abbc31ff..16b98523ce 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -552,7 +552,7 @@ static struct ref *get_ref_map(struct remote *remote,
 		for (i = 0; i < fetch_refspec->nr; i++)
 			get_fetch_map(ref_map, &fetch_refspec->items[i], &oref_tail, 1);
 	} else if (refmap.nr) {
-		die("--refmap option is only meaningful with command-line refspec(s).");
+		die("--refmap option is only meaningful with command-line refspec(s)");
 	} else {
 		/* Use the defaults */
 		struct branch *branch = branch_get(NULL);
@@ -583,7 +583,7 @@ static struct ref *get_ref_map(struct remote *remote,
 		} else if (!prefetch) {
 			ref_map = get_remote_ref(remote_refs, "HEAD");
 			if (!ref_map)
-				die(_("Couldn't find remote ref HEAD"));
+				die(_("couldn't find remote ref HEAD"));
 			ref_map->fetch_head_status = FETCH_HEAD_MERGE;
 			tail = &ref_map->next;
 		}
@@ -1067,13 +1067,13 @@ static void close_fetch_head(struct fetch_head *fetch_head)
 }
 
 static const char warn_show_forced_updates[] =
-N_("Fetch normally indicates which branches had a forced update,\n"
-   "but that check has been disabled. To re-enable, use '--show-forced-updates'\n"
-   "flag or run 'git config fetch.showForcedUpdates true'.");
+N_("fetch normally indicates which branches had a forced update,\n"
+   "but that check has been disabled; to re-enable, use '--show-forced-updates'\n"
+   "flag or run 'git config fetch.showForcedUpdates true'");
 static const char warn_time_show_forced_updates[] =
-N_("It took %.2f seconds to check forced updates. You can use\n"
+N_("it took %.2f seconds to check forced updates; you can use\n"
    "'--no-show-forced-updates' or run 'git config fetch.showForcedUpdates false'\n"
-   " to avoid this check.\n");
+   "to avoid this check\n");
 
 static int store_updated_refs(const char *raw_url, const char *remote_name,
 			      int connectivity_checked, struct ref *ref_map)
@@ -1395,8 +1395,9 @@ static void check_not_current_branch(struct ref *ref_map)
 	for (; ref_map; ref_map = ref_map->next)
 		if (ref_map->peer_ref && !strcmp(current_branch->refname,
 					ref_map->peer_ref->name))
-			die(_("Refusing to fetch into current branch %s "
-			    "of non-bare repository"), current_branch->refname);
+			die(_("refusing to fetch into current branch %s "
+			      "of non-bare repository"),
+			    current_branch->refname);
 }
 
 static int truncate_fetch_head(void)
@@ -1414,10 +1415,10 @@ static void set_option(struct transport *transport, const char *name, const char
 {
 	int r = transport_set_option(transport, name, value);
 	if (r < 0)
-		die(_("Option \"%s\" value \"%s\" is not valid for %s"),
+		die(_("option \"%s\" value \"%s\" is not valid for %s"),
 		    name, value, transport->url);
 	if (r > 0)
-		warning(_("Option \"%s\" is ignored for %s\n"),
+		warning(_("option \"%s\" is ignored for %s\n"),
 			name, transport->url);
 }
 
@@ -1451,7 +1452,7 @@ static void add_negotiation_tips(struct git_transport_options *smart_options)
 		old_nr = oids->nr;
 		for_each_glob_ref(add_oid, s, oids);
 		if (old_nr == oids->nr)
-			warning("Ignoring --negotiation-tip=%s because it does not match any refs",
+			warning("ignoring --negotiation-tip=%s because it does not match any refs",
 				s);
 	}
 	smart_options->negotiation_tips = oids;
@@ -1489,7 +1490,7 @@ static struct transport *prepare_transport(struct remote *remote, int deepen)
 		if (transport->smart_options)
 			add_negotiation_tips(transport->smart_options);
 		else
-			warning("Ignoring --negotiation-tip because the protocol does not support it.");
+			warning("ignoring --negotiation-tip because the protocol does not support it");
 	}
 	return transport;
 }
@@ -1651,8 +1652,8 @@ static int do_fetch(struct transport *transport,
 			else
 				warning(_("unknown branch type"));
 		} else {
-			warning(_("no source branch found.\n"
-				"you need to specify exactly one branch with the --set-upstream option."));
+			warning(_("no source branch found;\n"
+				  "you need to specify exactly one branch with the --set-upstream option"));
 		}
 	}
  skip:
@@ -1790,7 +1791,7 @@ static int fetch_failed_to_start(struct strbuf *out, void *cb, void *task_cb)
 	struct parallel_fetch_state *state = cb;
 	const char *remote = task_cb;
 
-	state->result = error(_("Could not fetch %s"), remote);
+	state->result = error(_("could not fetch %s"), remote);
 
 	return 0;
 }
@@ -1845,7 +1846,7 @@ static int fetch_multiple(struct string_list *list, int max_children)
 			if (verbosity >= 0)
 				printf(_("Fetching %s\n"), name);
 			if (run_command_v_opt(argv.v, RUN_GIT_CMD)) {
-				error(_("Could not fetch %s"), name);
+				error(_("could not fetch %s"), name);
 				result = 1;
 			}
 			strvec_pop(&argv);
@@ -1906,8 +1907,8 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
 	int remote_via_config = remote_is_configured(remote, 0);
 
 	if (!remote)
-		die(_("No remote repository specified.  Please, specify either a URL or a\n"
-		    "remote name from which new revisions should be fetched."));
+		die(_("no remote repository specified; please specify either a URL or a\n"
+		      "remote name from which new revisions should be fetched"));
 
 	gtransport = prepare_transport(remote, 1);
 
@@ -1942,7 +1943,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
 		if (!strcmp(argv[i], "tag")) {
 			i++;
 			if (i >= argc)
-				die(_("You need to specify a tag name."));
+				die(_("you need to specify a tag name"));
 
 			refspec_appendf(&rs, "refs/tags/%s:refs/tags/%s",
 					argv[i], argv[i]);
@@ -2010,7 +2011,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
 
 	if (deepen_relative) {
 		if (deepen_relative < 0)
-			die(_("Negative depth in --deepen is not supported"));
+			die(_("negative depth in --deepen is not supported"));
 		if (depth)
 			die(_("--deepen and --depth are mutually exclusive"));
 		depth = xstrfmt("%d", deepen_relative);
@@ -2047,14 +2048,15 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
 		/* All arguments are assumed to be remotes or groups */
 		for (i = 0; i < argc; i++)
 			if (!add_remote_or_group(argv[i], &list))
-				die(_("No such remote or remote group: %s"), argv[i]);
+				die(_("no such remote or remote group: %s"),
+				    argv[i]);
 	} else {
 		/* Single remote or group */
 		(void) add_remote_or_group(argv[0], &list);
 		if (list.nr > 1) {
 			/* More than one remote */
 			if (argc > 1)
-				die(_("Fetching a group and specifying refspecs does not make sense"));
+				die(_("fetching a group and specifying refspecs does not make sense"));
 		} else {
 			/* Zero or one remotes */
 			remote = remote_get(argv[0]);
@@ -2075,7 +2077,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
 		if (gtransport->smart_options) {
 			gtransport->smart_options->acked_commits = &acked_commits;
 		} else {
-			warning(_("Protocol does not support --negotiate-only, exiting."));
+			warning(_("protocol does not support --negotiate-only, exiting"));
 			return 1;
 		}
 		if (server_options.nr)
-- 
2.34.1


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v7 2/8] receive-pack: lowercase error messages
  2021-12-01 22:15 [PATCH v7 0/8] protect branches checked out in all worktrees Anders Kaseorg
  2021-12-01 22:15 ` [PATCH v7 1/8] fetch: lowercase error messages Anders Kaseorg
@ 2021-12-01 22:15 ` Anders Kaseorg
  2021-12-01 22:15 ` [PATCH v7 3/8] branch: " Anders Kaseorg
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 20+ messages in thread
From: Anders Kaseorg @ 2021-12-01 22:15 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Johannes Schindelin, Jeff King, Andreas Heiduk,
	Ævar Arnfjörð Bjarmason ,
	Jiang Xin, Anders Kaseorg

Documentation/CodingGuidelines says “do not end error messages with a
full stop” and “do not capitalize the first word”.  Clean up existing
messages, some of which we will be touching in later steps in the
series, that deviate from these rules in this file, as a preparation for
the main part of the topic.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
---
 builtin/receive-pack.c          | 10 +++++-----
 t/t5504-fetch-receive-strict.sh |  2 +-
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 49b846d960..ddda27c184 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -175,7 +175,7 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
 			strbuf_addf(&fsck_msg_types, "%c%s=%s",
 				fsck_msg_types.len ? ',' : '=', var, value);
 		else
-			warning("Skipping unknown msg id '%s'", var);
+			warning("skipping unknown msg id '%s'", var);
 		return 0;
 	}
 
@@ -1589,9 +1589,9 @@ static const char *update(struct command *cmd, struct shallow_info *si)
 		if (!parse_object(the_repository, old_oid)) {
 			old_oid = NULL;
 			if (ref_exists(name)) {
-				rp_warning("Allowing deletion of corrupt ref.");
+				rp_warning("allowing deletion of corrupt ref");
 			} else {
-				rp_warning("Deleting a non-existent ref.");
+				rp_warning("deleting a non-existent ref");
 				cmd->did_not_exist = 1;
 			}
 		}
@@ -2490,9 +2490,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);
 
 	if (argc > 1)
-		usage_msg_opt(_("Too many arguments."), receive_pack_usage, options);
+		usage_msg_opt(_("too many arguments"), receive_pack_usage, options);
 	if (argc == 0)
-		usage_msg_opt(_("You must specify a directory."), receive_pack_usage, options);
+		usage_msg_opt(_("you must specify a directory"), receive_pack_usage, options);
 
 	service_dir = argv[0];
 
diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh
index 6e5a9c20e7..b0b795aca9 100755
--- a/t/t5504-fetch-receive-strict.sh
+++ b/t/t5504-fetch-receive-strict.sh
@@ -292,7 +292,7 @@ test_expect_success 'push with receive.fsck.missingEmail=warn' '
 		receive.fsck.missingEmail warn &&
 	git push --porcelain dst bogus >act 2>&1 &&
 	grep "missingEmail" act &&
-	test_i18ngrep "Skipping unknown msg id.*whatever" act &&
+	test_i18ngrep "skipping unknown msg id.*whatever" act &&
 	git --git-dir=dst/.git branch -D bogus &&
 	git --git-dir=dst/.git config --add \
 		receive.fsck.missingEmail ignore &&
-- 
2.34.1


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v7 3/8] branch: lowercase error messages
  2021-12-01 22:15 [PATCH v7 0/8] protect branches checked out in all worktrees Anders Kaseorg
  2021-12-01 22:15 ` [PATCH v7 1/8] fetch: lowercase error messages Anders Kaseorg
  2021-12-01 22:15 ` [PATCH v7 2/8] receive-pack: " Anders Kaseorg
@ 2021-12-01 22:15 ` Anders Kaseorg
  2021-12-01 22:15 ` [PATCH v7 4/8] worktree: simplify find_shared_symref() memory ownership model Anders Kaseorg
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 20+ messages in thread
From: Anders Kaseorg @ 2021-12-01 22:15 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Johannes Schindelin, Jeff King, Andreas Heiduk,
	Ævar Arnfjörð Bjarmason ,
	Jiang Xin, Anders Kaseorg

Documentation/CodingGuidelines says “do not end error messages with a
full stop” and “do not capitalize the first word”.  Clean up existing
messages, some of which we will be touching in later steps in the
series, that deviate from these rules in this file, as a preparation for
the main part of the topic.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
---
 branch.c                   | 20 ++++++++++----------
 t/t2018-checkout-branch.sh |  2 +-
 t/t3200-branch.sh          |  4 ++--
 3 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/branch.c b/branch.c
index 07a46430b3..85777b939b 100644
--- a/branch.c
+++ b/branch.c
@@ -64,7 +64,7 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 	if (skip_prefix(remote, "refs/heads/", &shortname)
 	    && !strcmp(local, shortname)
 	    && !origin) {
-		warning(_("Not setting branch %s as its own upstream."),
+		warning(_("not setting branch %s as its own upstream"),
 			local);
 		return 0;
 	}
@@ -116,7 +116,7 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 
 out_err:
 	strbuf_release(&key);
-	error(_("Unable to write upstream branch configuration"));
+	error(_("unable to write upstream branch configuration"));
 
 	advise(_(tracking_advice),
 	       origin ? origin : "",
@@ -153,7 +153,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 		}
 
 	if (tracking.matches > 1)
-		die(_("Not tracking: ambiguous information for ref %s"),
+		die(_("not tracking: ambiguous information for ref %s"),
 		    orig_ref);
 
 	if (install_branch_config(config_flags, new_ref, tracking.remote,
@@ -186,7 +186,7 @@ int read_branch_desc(struct strbuf *buf, const char *branch_name)
 int validate_branchname(const char *name, struct strbuf *ref)
 {
 	if (strbuf_check_branch_ref(ref, name))
-		die(_("'%s' is not a valid branch name."), name);
+		die(_("'%s' is not a valid branch name"), name);
 
 	return ref_exists(ref->buf);
 }
@@ -205,12 +205,12 @@ int validate_new_branchname(const char *name, struct strbuf *ref, int force)
 		return 0;
 
 	if (!force)
-		die(_("A branch named '%s' already exists."),
+		die(_("a branch named '%s' already exists"),
 		    ref->buf + strlen("refs/heads/"));
 
 	head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
 	if (!is_bare_repository() && head && !strcmp(head, ref->buf))
-		die(_("Cannot force update the current branch."));
+		die(_("cannot force update the current branch"));
 
 	return 1;
 }
@@ -230,7 +230,7 @@ static int validate_remote_tracking_branch(char *ref)
 }
 
 static const char upstream_not_branch[] =
-N_("Cannot setup tracking information; starting point '%s' is not a branch.");
+N_("cannot set up tracking information; starting point '%s' is not a branch");
 static const char upstream_missing[] =
 N_("the requested upstream branch '%s' does not exist");
 static const char upstream_advice[] =
@@ -278,7 +278,7 @@ void create_branch(struct repository *r,
 			}
 			die(_(upstream_missing), start_name);
 		}
-		die(_("Not a valid object name: '%s'."), start_name);
+		die(_("not a valid object name: '%s'"), start_name);
 	}
 
 	switch (dwim_ref(start_name, strlen(start_name), &oid, &real_ref, 0)) {
@@ -298,12 +298,12 @@ void create_branch(struct repository *r,
 		}
 		break;
 	default:
-		die(_("Ambiguous object name: '%s'."), start_name);
+		die(_("ambiguous object name: '%s'"), start_name);
 		break;
 	}
 
 	if ((commit = lookup_commit_reference(r, &oid)) == NULL)
-		die(_("Not a valid branch point: '%s'."), start_name);
+		die(_("not a valid branch point: '%s'"), start_name);
 	oidcpy(&oid, &commit->object.oid);
 
 	if (reflog)
diff --git a/t/t2018-checkout-branch.sh b/t/t2018-checkout-branch.sh
index 93be1c0eae..3e93506c04 100755
--- a/t/t2018-checkout-branch.sh
+++ b/t/t2018-checkout-branch.sh
@@ -148,7 +148,7 @@ test_expect_success 'checkout -b to an existing branch fails' '
 test_expect_success 'checkout -b to @{-1} fails with the right branch name' '
 	git checkout branch1 &&
 	git checkout branch2 &&
-	echo  >expect "fatal: A branch named '\''branch1'\'' already exists." &&
+	echo  >expect "fatal: a branch named '\''branch1'\'' already exists" &&
 	test_must_fail git checkout -b @{-1} 2>actual &&
 	test_cmp expect actual
 '
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index e575ffb4ff..498cea31bd 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -866,7 +866,7 @@ test_expect_success '--set-upstream-to fails on a missing src branch' '
 '
 
 test_expect_success '--set-upstream-to fails on a non-ref' '
-	echo "fatal: Cannot setup tracking information; starting point '"'"'HEAD^{}'"'"' is not a branch." >expect &&
+	echo "fatal: cannot set up tracking information; starting point '"'"'HEAD^{}'"'"' is not a branch" >expect &&
 	test_must_fail git branch --set-upstream-to HEAD^{} 2>err &&
 	test_cmp expect err
 '
@@ -953,7 +953,7 @@ test_expect_success 'disabled option --set-upstream fails' '
 test_expect_success '--set-upstream-to notices an error to set branch as own upstream' '
 	git branch --set-upstream-to refs/heads/my13 my13 2>actual &&
 	cat >expect <<-\EOF &&
-	warning: Not setting branch my13 as its own upstream.
+	warning: not setting branch my13 as its own upstream
 	EOF
 	test_expect_code 1 git config branch.my13.remote &&
 	test_expect_code 1 git config branch.my13.merge &&
-- 
2.34.1


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v7 4/8] worktree: simplify find_shared_symref() memory ownership model
  2021-12-01 22:15 [PATCH v7 0/8] protect branches checked out in all worktrees Anders Kaseorg
                   ` (2 preceding siblings ...)
  2021-12-01 22:15 ` [PATCH v7 3/8] branch: " Anders Kaseorg
@ 2021-12-01 22:15 ` Anders Kaseorg
  2021-12-01 23:10   ` Eric Sunshine
  2021-12-01 22:15 ` [PATCH v7 5/8] fetch: protect branches checked out in all worktrees Anders Kaseorg
                   ` (3 subsequent siblings)
  7 siblings, 1 reply; 20+ messages in thread
From: Anders Kaseorg @ 2021-12-01 22:15 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Johannes Schindelin, Jeff King, Andreas Heiduk,
	Ævar Arnfjörð Bjarmason ,
	Jiang Xin, Anders Kaseorg

Storing the worktrees list in a static variable meant that
find_shared_symref() had to rebuild the list on each call (which is
inefficient when the call site is in a loop), and also that each call
invalidated the pointer returned by the previous call (which is
confusing).

Instead, make it the caller’s responsibility to pass in the worktrees
list and manage its lifetime.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
---
 branch.c               | 14 ++++++----
 builtin/branch.c       |  7 ++++-
 builtin/notes.c        |  6 +++-
 builtin/receive-pack.c | 63 +++++++++++++++++++++++++++---------------
 worktree.c             |  8 ++----
 worktree.h             |  5 ++--
 6 files changed, 65 insertions(+), 38 deletions(-)

diff --git a/branch.c b/branch.c
index 85777b939b..c66b222abd 100644
--- a/branch.c
+++ b/branch.c
@@ -357,14 +357,16 @@ void remove_branch_state(struct repository *r, int verbose)
 
 void die_if_checked_out(const char *branch, int ignore_current_worktree)
 {
+	struct worktree **worktrees = get_worktrees();
 	const struct worktree *wt;
 
-	wt = find_shared_symref("HEAD", branch);
-	if (!wt || (ignore_current_worktree && wt->is_current))
-		return;
-	skip_prefix(branch, "refs/heads/", &branch);
-	die(_("'%s' is already checked out at '%s'"),
-	    branch, wt->path);
+	wt = find_shared_symref(worktrees, "HEAD", branch);
+	if (wt && (!ignore_current_worktree || !wt->is_current)) {
+		skip_prefix(branch, "refs/heads/", &branch);
+		die(_("'%s' is already checked out at '%s'"), branch, wt->path);
+	}
+
+	free_worktrees(worktrees);
 }
 
 int replace_each_worktree_head_symref(const char *oldref, const char *newref,
diff --git a/builtin/branch.c b/builtin/branch.c
index 7a1d1eeb07..d8f2164cd7 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -193,6 +193,7 @@ static void delete_branch_config(const char *branchname)
 static int delete_branches(int argc, const char **argv, int force, int kinds,
 			   int quiet)
 {
+	struct worktree **worktrees;
 	struct commit *head_rev = NULL;
 	struct object_id oid;
 	char *name = NULL;
@@ -229,6 +230,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
 		if (!head_rev)
 			die(_("Couldn't look up commit object for HEAD"));
 	}
+
+	worktrees = get_worktrees();
+
 	for (i = 0; i < argc; i++, strbuf_reset(&bname)) {
 		char *target = NULL;
 		int flags = 0;
@@ -239,7 +243,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
 
 		if (kinds == FILTER_REFS_BRANCHES) {
 			const struct worktree *wt =
-				find_shared_symref("HEAD", name);
+				find_shared_symref(worktrees, "HEAD", name);
 			if (wt) {
 				error(_("Cannot delete branch '%s' "
 					"checked out at '%s'"),
@@ -300,6 +304,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
 
 	free(name);
 	strbuf_release(&bname);
+	free_worktrees(worktrees);
 
 	return ret;
 }
diff --git a/builtin/notes.c b/builtin/notes.c
index 71c59583a1..7f60408dbb 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -861,15 +861,19 @@ static int merge(int argc, const char **argv, const char *prefix)
 		update_ref(msg.buf, default_notes_ref(), &result_oid, NULL, 0,
 			   UPDATE_REFS_DIE_ON_ERR);
 	else { /* Merge has unresolved conflicts */
+		struct worktree **worktrees;
 		const struct worktree *wt;
 		/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
 		update_ref(msg.buf, "NOTES_MERGE_PARTIAL", &result_oid, NULL,
 			   0, UPDATE_REFS_DIE_ON_ERR);
 		/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
-		wt = find_shared_symref("NOTES_MERGE_REF", default_notes_ref());
+		worktrees = get_worktrees();
+		wt = find_shared_symref(worktrees, "NOTES_MERGE_REF",
+					default_notes_ref());
 		if (wt)
 			die(_("a notes merge into %s is already in-progress at %s"),
 			    default_notes_ref(), wt->path);
+		free_worktrees(worktrees);
 		if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
 			die(_("failed to store link to current notes ref (%s)"),
 			    default_notes_ref());
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index ddda27c184..525b021ec0 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1486,12 +1486,17 @@ static const char *update(struct command *cmd, struct shallow_info *si)
 	struct object_id *old_oid = &cmd->old_oid;
 	struct object_id *new_oid = &cmd->new_oid;
 	int do_update_worktree = 0;
-	const struct worktree *worktree = is_bare_repository() ? NULL : find_shared_symref("HEAD", name);
+	struct worktree **worktrees = get_worktrees();
+	const struct worktree *worktree =
+		is_bare_repository() ?
+			NULL :
+			find_shared_symref(worktrees, "HEAD", name);
 
 	/* only refs/... are allowed */
 	if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) {
 		rp_error("refusing to create funny ref '%s' remotely", name);
-		return "funny refname";
+		ret = "funny refname";
+		goto out;
 	}
 
 	strbuf_addf(&namespaced_name_buf, "%s%s", get_git_namespace(), name);
@@ -1510,7 +1515,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)
 			rp_error("refusing to update checked out branch: %s", name);
 			if (deny_current_branch == DENY_UNCONFIGURED)
 				refuse_unconfigured_deny();
-			return "branch is currently checked out";
+			ret = "branch is currently checked out";
+			goto out;
 		case DENY_UPDATE_INSTEAD:
 			/* pass -- let other checks intervene first */
 			do_update_worktree = 1;
@@ -1521,13 +1527,15 @@ static const char *update(struct command *cmd, struct shallow_info *si)
 	if (!is_null_oid(new_oid) && !has_object_file(new_oid)) {
 		error("unpack should have generated %s, "
 		      "but I can't find it!", oid_to_hex(new_oid));
-		return "bad pack";
+		ret = "bad pack";
+		goto out;
 	}
 
 	if (!is_null_oid(old_oid) && is_null_oid(new_oid)) {
 		if (deny_deletes && starts_with(name, "refs/heads/")) {
 			rp_error("denying ref deletion for %s", name);
-			return "deletion prohibited";
+			ret = "deletion prohibited";
+			goto out;
 		}
 
 		if (worktree || (head_name && !strcmp(namespaced_name, head_name))) {
@@ -1543,9 +1551,11 @@ static const char *update(struct command *cmd, struct shallow_info *si)
 				if (deny_delete_current == DENY_UNCONFIGURED)
 					refuse_unconfigured_deny_delete_current();
 				rp_error("refusing to delete the current branch: %s", name);
-				return "deletion of the current branch prohibited";
+				ret = "deletion of the current branch prohibited";
+				goto out;
 			default:
-				return "Invalid denyDeleteCurrent setting";
+				ret = "Invalid denyDeleteCurrent setting";
+				goto out;
 			}
 		}
 	}
@@ -1563,25 +1573,30 @@ static const char *update(struct command *cmd, struct shallow_info *si)
 		    old_object->type != OBJ_COMMIT ||
 		    new_object->type != OBJ_COMMIT) {
 			error("bad sha1 objects for %s", name);
-			return "bad ref";
+			ret = "bad ref";
+			goto out;
 		}
 		old_commit = (struct commit *)old_object;
 		new_commit = (struct commit *)new_object;
 		if (!in_merge_bases(old_commit, new_commit)) {
 			rp_error("denying non-fast-forward %s"
 				 " (you should pull first)", name);
-			return "non-fast-forward";
+			ret = "non-fast-forward";
+			goto out;
 		}
 	}
 	if (run_update_hook(cmd)) {
 		rp_error("hook declined to update %s", name);
-		return "hook declined";
+		ret = "hook declined";
+		goto out;
 	}
 
 	if (do_update_worktree) {
-		ret = update_worktree(new_oid->hash, find_shared_symref("HEAD", name));
+		ret = update_worktree(new_oid->hash,
+				      find_shared_symref(worktrees, "HEAD",
+							 name));
 		if (ret)
-			return ret;
+			goto out;
 	}
 
 	if (is_null_oid(new_oid)) {
@@ -1600,17 +1615,19 @@ static const char *update(struct command *cmd, struct shallow_info *si)
 					   old_oid,
 					   0, "push", &err)) {
 			rp_error("%s", err.buf);
-			strbuf_release(&err);
-			return "failed to delete";
+			ret = "failed to delete";
+		} else {
+			ret = NULL; /* good */
 		}
 		strbuf_release(&err);
-		return NULL; /* good */
 	}
 	else {
 		struct strbuf err = STRBUF_INIT;
 		if (shallow_update && si->shallow_ref[cmd->index] &&
-		    update_shallow_ref(cmd, si))
-			return "shallow error";
+		    update_shallow_ref(cmd, si)) {
+			ret = "shallow error";
+			goto out;
+		}
 
 		if (ref_transaction_update(transaction,
 					   namespaced_name,
@@ -1618,14 +1635,16 @@ static const char *update(struct command *cmd, struct shallow_info *si)
 					   0, "push",
 					   &err)) {
 			rp_error("%s", err.buf);
-			strbuf_release(&err);
-
-			return "failed to update ref";
+			ret = "failed to update ref";
+		} else {
+			ret = NULL; /* good */
 		}
 		strbuf_release(&err);
-
-		return NULL; /* good */
 	}
+
+out:
+	free_worktrees(worktrees);
+	return ret;
 }
 
 static void run_update_post_hook(struct command *commands)
diff --git a/worktree.c b/worktree.c
index 092a4f92ad..cf13d63845 100644
--- a/worktree.c
+++ b/worktree.c
@@ -402,17 +402,13 @@ int is_worktree_being_bisected(const struct worktree *wt,
  * bisect). New commands that do similar things should update this
  * function as well.
  */
-const struct worktree *find_shared_symref(const char *symref,
+const struct worktree *find_shared_symref(struct worktree **worktrees,
+					  const char *symref,
 					  const char *target)
 {
 	const struct worktree *existing = NULL;
-	static struct worktree **worktrees;
 	int i = 0;
 
-	if (worktrees)
-		free_worktrees(worktrees);
-	worktrees = get_worktrees();
-
 	for (i = 0; worktrees[i]; i++) {
 		struct worktree *wt = worktrees[i];
 		const char *symref_target;
diff --git a/worktree.h b/worktree.h
index 8b7c408132..9e06fcbdf3 100644
--- a/worktree.h
+++ b/worktree.h
@@ -143,9 +143,10 @@ void free_worktrees(struct worktree **);
 /*
  * Check if a per-worktree symref points to a ref in the main worktree
  * or any linked worktree, and return the worktree that holds the ref,
- * or NULL otherwise. The result may be destroyed by the next call.
+ * or NULL otherwise.
  */
-const struct worktree *find_shared_symref(const char *symref,
+const struct worktree *find_shared_symref(struct worktree **worktrees,
+					  const char *symref,
 					  const char *target);
 
 /*
-- 
2.34.1


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v7 5/8] fetch: protect branches checked out in all worktrees
  2021-12-01 22:15 [PATCH v7 0/8] protect branches checked out in all worktrees Anders Kaseorg
                   ` (3 preceding siblings ...)
  2021-12-01 22:15 ` [PATCH v7 4/8] worktree: simplify find_shared_symref() memory ownership model Anders Kaseorg
@ 2021-12-01 22:15 ` Anders Kaseorg
  2021-12-02  2:51   ` Eric Sunshine
  2021-12-01 22:15 ` [PATCH v7 6/8] receive-pack: clean dead code from update_worktree() Anders Kaseorg
                   ` (2 subsequent siblings)
  7 siblings, 1 reply; 20+ messages in thread
From: Anders Kaseorg @ 2021-12-01 22:15 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Johannes Schindelin, Jeff King, Andreas Heiduk,
	Ævar Arnfjörð Bjarmason ,
	Jiang Xin, Anders Kaseorg

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>
---
 builtin/fetch.c       | 75 +++++++++++++++++++++++--------------------
 t/t5516-fetch-push.sh | 18 +++++++++++
 2 files changed, 58 insertions(+), 35 deletions(-)

diff --git a/builtin/fetch.c b/builtin/fetch.c
index 16b98523ce..e45185cf9c 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -28,6 +28,7 @@
 #include "promisor-remote.h"
 #include "commit-graph.h"
 #include "shallow.h"
+#include "worktree.h"
 
 #define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000)
 
@@ -848,13 +849,12 @@ static void format_display(struct strbuf *display, char code,
 
 static int update_local_ref(struct ref *ref,
 			    struct ref_transaction *transaction,
-			    const char *remote,
-			    const struct ref *remote_ref,
-			    struct strbuf *display,
-			    int summary_width)
+			    const char *remote, const struct ref *remote_ref,
+			    struct strbuf *display, int summary_width,
+			    struct worktree **worktrees)
 {
 	struct commit *current = NULL, *updated;
-	struct branch *current_branch = branch_get(NULL);
+	const struct worktree *wt;
 	const char *pretty_ref = prettify_refname(ref->name);
 	int fast_forward = 0;
 
@@ -868,16 +868,17 @@ static int update_local_ref(struct ref *ref,
 		return 0;
 	}
 
-	if (current_branch &&
-	    !strcmp(ref->name, current_branch->name) &&
-	    !(update_head_ok || is_bare_repository()) &&
-	    !is_null_oid(&ref->old_oid)) {
+	if (!update_head_ok &&
+	    (wt = find_shared_symref(worktrees, "HEAD", ref->name)) &&
+	    !wt->is_bare && !is_null_oid(&ref->old_oid)) {
 		/*
 		 * If this is the head, and it's not okay to update
 		 * the head, and the old value of the head isn't empty...
 		 */
 		format_display(display, '!', _("[rejected]"),
-			       _("can't fetch in current branch"),
+			       wt->is_current ?
+				       _("can't fetch in current branch") :
+				       _("checked out in another worktree"),
 			       remote, pretty_ref, summary_width);
 		return 1;
 	}
@@ -1076,7 +1077,8 @@ N_("it took %.2f seconds to check forced updates; you can use\n"
    "to avoid this check\n");
 
 static int store_updated_refs(const char *raw_url, const char *remote_name,
-			      int connectivity_checked, struct ref *ref_map)
+			      int connectivity_checked, struct ref *ref_map,
+			      struct worktree **worktrees)
 {
 	struct fetch_head fetch_head;
 	int url_len, i, rc = 0;
@@ -1205,7 +1207,8 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
 			strbuf_reset(&note);
 			if (ref) {
 				rc |= update_local_ref(ref, transaction, what,
-						       rm, &note, summary_width);
+						       rm, &note, summary_width,
+						       worktrees);
 				free(ref);
 			} else if (write_fetch_head || dry_run) {
 				/*
@@ -1298,7 +1301,9 @@ static int check_exist_and_connected(struct ref *ref_map)
 	return check_connected(iterate_ref_map, &rm, &opt);
 }
 
-static int fetch_and_consume_refs(struct transport *transport, struct ref *ref_map)
+static int fetch_and_consume_refs(struct transport *transport,
+				  struct ref *ref_map,
+				  struct worktree **worktrees)
 {
 	int connectivity_checked = 1;
 	int ret;
@@ -1319,10 +1324,8 @@ static int fetch_and_consume_refs(struct transport *transport, struct ref *ref_m
 	}
 
 	trace2_region_enter("fetch", "consume_refs", the_repository);
-	ret = store_updated_refs(transport->url,
-				 transport->remote->name,
-				 connectivity_checked,
-				 ref_map);
+	ret = store_updated_refs(transport->url, transport->remote->name,
+				 connectivity_checked, ref_map, worktrees);
 	trace2_region_leave("fetch", "consume_refs", the_repository);
 
 out:
@@ -1385,19 +1388,18 @@ static int prune_refs(struct refspec *rs, struct ref *ref_map,
 	return result;
 }
 
-static void check_not_current_branch(struct ref *ref_map)
+static void check_not_current_branch(struct ref *ref_map,
+				     struct worktree **worktrees)
 {
-	struct branch *current_branch = branch_get(NULL);
-
-	if (is_bare_repository() || !current_branch)
-		return;
-
+	const struct worktree *wt;
 	for (; ref_map; ref_map = ref_map->next)
-		if (ref_map->peer_ref && !strcmp(current_branch->refname,
-					ref_map->peer_ref->name))
-			die(_("refusing to fetch into current branch %s "
-			      "of non-bare repository"),
-			    current_branch->refname);
+		if (ref_map->peer_ref &&
+		    (wt = find_shared_symref(worktrees, "HEAD",
+					     ref_map->peer_ref->name)) &&
+		    !wt->is_bare)
+			die(_("refusing to fetch into branch '%s' "
+			      "checked out at '%s'"),
+			    ref_map->peer_ref->name, wt->path);
 }
 
 static int truncate_fetch_head(void)
@@ -1495,7 +1497,8 @@ static struct transport *prepare_transport(struct remote *remote, int deepen)
 	return transport;
 }
 
-static void backfill_tags(struct transport *transport, struct ref *ref_map)
+static void backfill_tags(struct transport *transport, struct ref *ref_map,
+			  struct worktree **worktrees)
 {
 	int cannot_reuse;
 
@@ -1516,7 +1519,7 @@ static void backfill_tags(struct transport *transport, struct ref *ref_map)
 	transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
 	transport_set_option(transport, TRANS_OPT_DEPTH, "0");
 	transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL);
-	fetch_and_consume_refs(transport, ref_map);
+	fetch_and_consume_refs(transport, ref_map, worktrees);
 
 	if (gsecondary) {
 		transport_disconnect(gsecondary);
@@ -1534,6 +1537,7 @@ static int do_fetch(struct transport *transport,
 	struct transport_ls_refs_options transport_ls_refs_options =
 		TRANSPORT_LS_REFS_OPTIONS_INIT;
 	int must_list_refs = 1;
+	struct worktree **worktrees = get_worktrees();
 
 	if (tags == TAGS_DEFAULT) {
 		if (transport->remote->fetch_tags == 2)
@@ -1589,7 +1593,7 @@ static int do_fetch(struct transport *transport,
 	ref_map = get_ref_map(transport->remote, remote_refs, rs,
 			      tags, &autotags);
 	if (!update_head_ok)
-		check_not_current_branch(ref_map);
+		check_not_current_branch(ref_map, worktrees);
 
 	if (tags == TAGS_DEFAULT && autotags)
 		transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
@@ -1607,7 +1611,7 @@ static int do_fetch(struct transport *transport,
 				   transport->url);
 		}
 	}
-	if (fetch_and_consume_refs(transport, ref_map)) {
+	if (fetch_and_consume_refs(transport, ref_map, worktrees)) {
 		free_refs(ref_map);
 		retcode = 1;
 		goto cleanup;
@@ -1656,7 +1660,7 @@ static int do_fetch(struct transport *transport,
 				  "you need to specify exactly one branch with the --set-upstream option"));
 		}
 	}
- skip:
+skip:
 	free_refs(ref_map);
 
 	/* if neither --no-tags nor --tags was specified, do automated tag
@@ -1666,11 +1670,12 @@ static int do_fetch(struct transport *transport,
 		ref_map = NULL;
 		find_non_local_tags(remote_refs, &ref_map, &tail);
 		if (ref_map)
-			backfill_tags(transport, ref_map);
+			backfill_tags(transport, ref_map, worktrees);
 		free_refs(ref_map);
 	}
 
- cleanup:
+cleanup:
+	free_worktrees(worktrees);
 	return retcode;
 }
 
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 8212ca56dc..f07e32126f 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1771,4 +1771,22 @@ test_expect_success 'denyCurrentBranch and worktrees' '
 	git -C cloned push origin HEAD:new-wt &&
 	test_must_fail git -C cloned push --delete origin new-wt
 '
+
+test_expect_success 'refuse fetch to current branch of worktree' '
+	test_when_finished "git worktree remove --force wt && git branch -D wt" &&
+	git worktree add wt &&
+	test_commit apple &&
+	test_must_fail git fetch . HEAD:wt &&
+	git fetch -u . HEAD:wt
+'
+
+test_expect_success 'refuse fetch to current branch of bare repository worktree' '
+	test_when_finished "rm -fr bare.git" &&
+	git clone --bare . bare.git &&
+	git -C bare.git worktree add wt &&
+	test_commit banana &&
+	test_must_fail git -C bare.git fetch .. HEAD:wt &&
+	git -C bare.git fetch -u .. HEAD:wt
+'
+
 test_done
-- 
2.34.1


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v7 6/8] receive-pack: clean dead code from update_worktree()
  2021-12-01 22:15 [PATCH v7 0/8] protect branches checked out in all worktrees Anders Kaseorg
                   ` (4 preceding siblings ...)
  2021-12-01 22:15 ` [PATCH v7 5/8] fetch: protect branches checked out in all worktrees Anders Kaseorg
@ 2021-12-01 22:15 ` Anders Kaseorg
  2021-12-01 22:15 ` [PATCH v7 7/8] receive-pack: protect current branch for bare repository worktree Anders Kaseorg
  2021-12-01 22:15 ` [PATCH v7 8/8] branch: protect branches checked out in all worktrees Anders Kaseorg
  7 siblings, 0 replies; 20+ messages in thread
From: Anders Kaseorg @ 2021-12-01 22:15 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Johannes Schindelin, Jeff King, Andreas Heiduk,
	Ævar Arnfjörð Bjarmason ,
	Jiang Xin, Anders Kaseorg

update_worktree() can only be called with a non-NULL worktree parameter,
because that’s the only case where we set do_update_worktree = 1.
worktree->path is always initialized to non-NULL.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
---
 builtin/receive-pack.c | 23 +++++++----------------
 1 file changed, 7 insertions(+), 16 deletions(-)

diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 525b021ec0..29dc0e56f7 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1449,29 +1449,22 @@ static const char *push_to_checkout(unsigned char *hash,
 
 static const char *update_worktree(unsigned char *sha1, const struct worktree *worktree)
 {
-	const char *retval, *work_tree, *git_dir = NULL;
+	const char *retval, *git_dir;
 	struct strvec env = STRVEC_INIT;
 
-	if (worktree && worktree->path)
-		work_tree = worktree->path;
-	else if (git_work_tree_cfg)
-		work_tree = git_work_tree_cfg;
-	else
-		work_tree = "..";
+	if (!worktree || !worktree->path)
+		BUG("worktree->path must be non-NULL");
 
 	if (is_bare_repository())
 		return "denyCurrentBranch = updateInstead needs a worktree";
-	if (worktree)
-		git_dir = get_worktree_git_dir(worktree);
-	if (!git_dir)
-		git_dir = get_git_dir();
+	git_dir = get_worktree_git_dir(worktree);
 
 	strvec_pushf(&env, "GIT_DIR=%s", absolute_path(git_dir));
 
 	if (!hook_exists(push_to_checkout_hook))
-		retval = push_to_deploy(sha1, &env, work_tree);
+		retval = push_to_deploy(sha1, &env, worktree->path);
 	else
-		retval = push_to_checkout(sha1, &env, work_tree);
+		retval = push_to_checkout(sha1, &env, worktree->path);
 
 	strvec_clear(&env);
 	return retval;
@@ -1592,9 +1585,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
 	}
 
 	if (do_update_worktree) {
-		ret = update_worktree(new_oid->hash,
-				      find_shared_symref(worktrees, "HEAD",
-							 name));
+		ret = update_worktree(new_oid->hash, worktree);
 		if (ret)
 			goto out;
 	}
-- 
2.34.1


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v7 7/8] receive-pack: protect current branch for bare repository worktree
  2021-12-01 22:15 [PATCH v7 0/8] protect branches checked out in all worktrees Anders Kaseorg
                   ` (5 preceding siblings ...)
  2021-12-01 22:15 ` [PATCH v7 6/8] receive-pack: clean dead code from update_worktree() Anders Kaseorg
@ 2021-12-01 22:15 ` Anders Kaseorg
  2021-12-01 22:15 ` [PATCH v7 8/8] branch: protect branches checked out in all worktrees Anders Kaseorg
  7 siblings, 0 replies; 20+ messages in thread
From: Anders Kaseorg @ 2021-12-01 22:15 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Johannes Schindelin, Jeff King, Andreas Heiduk,
	Ævar Arnfjörð Bjarmason ,
	Jiang Xin, Anders Kaseorg

A bare repository won’t have a working tree at "..", but it may still
have separate working trees created with git worktree. We should protect
the current branch of such working trees from being updated or deleted,
according to receive.denyCurrentBranch.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
---
 builtin/receive-pack.c |  8 +++-----
 t/t5516-fetch-push.sh  | 14 ++++++++++++++
 2 files changed, 17 insertions(+), 5 deletions(-)

diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 29dc0e56f7..26ecaa5cd5 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1455,7 +1455,7 @@ static const char *update_worktree(unsigned char *sha1, const struct worktree *w
 	if (!worktree || !worktree->path)
 		BUG("worktree->path must be non-NULL");
 
-	if (is_bare_repository())
+	if (worktree->is_bare)
 		return "denyCurrentBranch = updateInstead needs a worktree";
 	git_dir = get_worktree_git_dir(worktree);
 
@@ -1481,9 +1481,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
 	int do_update_worktree = 0;
 	struct worktree **worktrees = get_worktrees();
 	const struct worktree *worktree =
-		is_bare_repository() ?
-			NULL :
-			find_shared_symref(worktrees, "HEAD", name);
+		find_shared_symref(worktrees, "HEAD", name);
 
 	/* only refs/... are allowed */
 	if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) {
@@ -1496,7 +1494,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
 	free(namespaced_name);
 	namespaced_name = strbuf_detach(&namespaced_name_buf, NULL);
 
-	if (worktree) {
+	if (worktree && !worktree->is_bare) {
 		switch (deny_current_branch) {
 		case DENY_IGNORE:
 			break;
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index f07e32126f..cd2e144c37 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1769,9 +1769,23 @@ test_expect_success 'denyCurrentBranch and worktrees' '
 	test_must_fail git -C cloned push origin HEAD:new-wt &&
 	test_config receive.denyCurrentBranch updateInstead &&
 	git -C cloned push origin HEAD:new-wt &&
+	test_path_exists new-wt/first.t &&
 	test_must_fail git -C cloned push --delete origin new-wt
 '
 
+test_expect_success 'denyCurrentBranch and bare repository worktrees' '
+	test_when_finished "rm -fr bare.git" &&
+	git clone --bare . bare.git &&
+	git -C bare.git worktree add wt &&
+	test_commit grape &&
+	git -C bare.git config receive.denyCurrentBranch refuse &&
+	test_must_fail git push bare.git HEAD:wt &&
+	git -C bare.git config receive.denyCurrentBranch updateInstead &&
+	git push bare.git HEAD:wt &&
+	test_path_exists bare.git/wt/grape.t &&
+	test_must_fail git push --delete bare.git wt
+'
+
 test_expect_success 'refuse fetch to current branch of worktree' '
 	test_when_finished "git worktree remove --force wt && git branch -D wt" &&
 	git worktree add wt &&
-- 
2.34.1


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v7 8/8] branch: protect branches checked out in all worktrees
  2021-12-01 22:15 [PATCH v7 0/8] protect branches checked out in all worktrees Anders Kaseorg
                   ` (6 preceding siblings ...)
  2021-12-01 22:15 ` [PATCH v7 7/8] receive-pack: protect current branch for bare repository worktree Anders Kaseorg
@ 2021-12-01 22:15 ` Anders Kaseorg
  2021-12-23  0:58   ` Jiang Xin
  7 siblings, 1 reply; 20+ messages in thread
From: Anders Kaseorg @ 2021-12-01 22:15 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Johannes Schindelin, Jeff King, Andreas Heiduk,
	Ævar Arnfjörð Bjarmason ,
	Jiang Xin, Anders Kaseorg

Refuse to force-move a branch over the currently checked out branch of
any working tree, not just the current one.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
---
 branch.c          | 13 +++++++++----
 t/t3200-branch.sh |  7 +++++++
 2 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/branch.c b/branch.c
index c66b222abd..2cfe496d24 100644
--- a/branch.c
+++ b/branch.c
@@ -199,7 +199,8 @@ int validate_branchname(const char *name, struct strbuf *ref)
  */
 int validate_new_branchname(const char *name, struct strbuf *ref, int force)
 {
-	const char *head;
+	struct worktree **worktrees;
+	const struct worktree *wt;
 
 	if (!validate_branchname(name, ref))
 		return 0;
@@ -208,9 +209,13 @@ int validate_new_branchname(const char *name, struct strbuf *ref, int force)
 		die(_("a branch named '%s' already exists"),
 		    ref->buf + strlen("refs/heads/"));
 
-	head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
-	if (!is_bare_repository() && head && !strcmp(head, ref->buf))
-		die(_("cannot force update the current branch"));
+	worktrees = get_worktrees();
+	wt = find_shared_symref(worktrees, "HEAD", ref->buf);
+	if (wt && !wt->is_bare)
+		die(_("cannot force update the branch '%s'"
+		      "checked out at '%s'"),
+		    ref->buf + strlen("refs/heads/"), wt->path);
+	free_worktrees(worktrees);
 
 	return 1;
 }
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 498cea31bd..9c45170661 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -168,6 +168,13 @@ test_expect_success 'git branch -M foo bar should fail when bar is checked out'
 	test_must_fail git branch -M bar foo
 '
 
+test_expect_success 'git branch -M foo bar should fail when bar is checked out in worktree' '
+	git branch -f bar &&
+	test_when_finished "git worktree remove wt && git branch -D wt" &&
+	git worktree add wt &&
+	test_must_fail git branch -M bar wt
+'
+
 test_expect_success 'git branch -M baz bam should succeed when baz is checked out' '
 	git checkout -b baz &&
 	git branch bam &&
-- 
2.34.1


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v7 4/8] worktree: simplify find_shared_symref() memory ownership model
  2021-12-01 22:15 ` [PATCH v7 4/8] worktree: simplify find_shared_symref() memory ownership model Anders Kaseorg
@ 2021-12-01 23:10   ` Eric Sunshine
  2021-12-01 23:47     ` Anders Kaseorg
  0 siblings, 1 reply; 20+ messages in thread
From: Eric Sunshine @ 2021-12-01 23:10 UTC (permalink / raw)
  To: Anders Kaseorg
  Cc: Junio C Hamano, Git List, Johannes Schindelin, Jeff King,
	Andreas Heiduk, Ævar Arnfjörð Bjarmason,
	Jiang Xin

On Wed, Dec 1, 2021 at 5:16 PM Anders Kaseorg <andersk@mit.edu> wrote:
> Storing the worktrees list in a static variable meant that
> find_shared_symref() had to rebuild the list on each call (which is
> inefficient when the call site is in a loop), and also that each call
> invalidated the pointer returned by the previous call (which is
> confusing).
>
> Instead, make it the caller’s responsibility to pass in the worktrees
> list and manage its lifetime.
>
> Signed-off-by: Anders Kaseorg <andersk@mit.edu>
> ---
> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
> @@ -1486,12 +1486,17 @@ static const char *update(struct command *cmd, struct shallow_info *si)
> -       const struct worktree *worktree = is_bare_repository() ? NULL : find_shared_symref("HEAD", name);
> +       struct worktree **worktrees = get_worktrees();
> +       const struct worktree *worktree =
> +               is_bare_repository() ?
> +                       NULL :
> +                       find_shared_symref(worktrees, "HEAD", name);

As far as I can see, this code only cares whether find_shared_symref()
returned a result; it doesn't actually consult the returned worktree
at all, thus it semantically considers `worktree` as a boolean, not as
a `struct worktree`. I see in [7/8] that you do add an access to the
`worktree.is_bare` field, but that also is used purely in a boolean
fashion. If my understanding is correct, then it seems as if it would
be cleaner and make for a much less noisy patch if you did this
instead:

    struct worktree **worktrees = get_worktrees();
    int has_worktree = 0;
    if (!is_bare_repository()) {
        struct worktree *w = find_shared_symref(worktrees, ...);
        has_worktree = !!w;
    }
    free_worktrees(worktrees);
    ...
    if (has_worktree) {
        ...
    }

and in patch [7/8] augment that to:

    int is_worktree_bare = 0;
    if (!is_bare_repository()) {
        struct worktree *w = find_shared_symref(worktrees, ...);
        if (w) {
            has_worktree = 1;
            is_worktree_bare = w->is_bare;
        }
    }
    free_worktrees(worktrees);

which would then allow you to...

>         if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) {
>                 rp_error("refusing to create funny ref '%s' remotely", name);
> -               return "funny refname";
> +               ret = "funny refname";
> +               goto out;
>         }

... drop this change and all the other similar changes to this file
since `worktrees` gets cleaned up as soon as `has_worktree` and
`is_worktree_bare` have been determined, so there's no need to
introduce an `out` label just to clean up `worktrees` at the end of
the function.

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v7 4/8] worktree: simplify find_shared_symref() memory ownership model
  2021-12-01 23:10   ` Eric Sunshine
@ 2021-12-01 23:47     ` Anders Kaseorg
  2021-12-02  0:13       ` Eric Sunshine
  0 siblings, 1 reply; 20+ messages in thread
From: Anders Kaseorg @ 2021-12-01 23:47 UTC (permalink / raw)
  To: Eric Sunshine
  Cc: Junio C Hamano, Git List, Johannes Schindelin, Jeff King,
	Andreas Heiduk, Ævar Arnfjörð Bjarmason,
	Jiang Xin

On 12/1/21 15:10, Eric Sunshine wrote:
> As far as I can see, this code only cares whether find_shared_symref()
> returned a result; it doesn't actually consult the returned worktree
> at all, thus it semantically considers `worktree` as a boolean, not as
> a `struct worktree`.

No, the update_worktree(new_oid->hash, worktree) call uses it in a 
non-boolean way, so we do need to keep it around.

Anders

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v7 4/8] worktree: simplify find_shared_symref() memory ownership model
  2021-12-01 23:47     ` Anders Kaseorg
@ 2021-12-02  0:13       ` Eric Sunshine
  2021-12-02  0:32         ` Eric Sunshine
  2021-12-02  9:06         ` Anders Kaseorg
  0 siblings, 2 replies; 20+ messages in thread
From: Eric Sunshine @ 2021-12-02  0:13 UTC (permalink / raw)
  To: Anders Kaseorg
  Cc: Junio C Hamano, Git List, Johannes Schindelin, Jeff King,
	Andreas Heiduk, Ævar Arnfjörð Bjarmason,
	Jiang Xin

On Wed, Dec 1, 2021 at 6:48 PM Anders Kaseorg <andersk@mit.edu> wrote:
> On 12/1/21 15:10, Eric Sunshine wrote:
> > As far as I can see, this code only cares whether find_shared_symref()
> > returned a result; it doesn't actually consult the returned worktree
> > at all, thus it semantically considers `worktree` as a boolean, not as
> > a `struct worktree`.
>
> No, the update_worktree(new_oid->hash, worktree) call uses it in a
> non-boolean way, so we do need to keep it around.

Okay, I missed that call to update_worktree() in the "noise" of all
those other changes, but looking at the code a bit more carefully, it
seems as if we might still be able to drop all the noise changes
anyhow by hoisting ownership of `worktrees` up a level or two. In
particular, both execute_commands_atomic() and
execute_commands_non_atomic() are calling update() in a loop, and
update() is calling get_worktrees() / free_worktrees() each time it is
called within those loops, which seems unnecessarily expensive.

If we instead hoist ownership of `worktrees` up to execute_commands()
-- which calls execute_commands_atomic() or
execute_commands_non_atomic() -- then we can get by with retrieving
the worktrees just once, and all those noise changes in update() can
be dropped since it will no longer be responsible for allocating or
freeing `worktrees`. For instance:

    static void execute_commands(...)
    {
        struct worktree **worktrees;
        ...
        worktrees = get_worktrees();
         if (use_atomic)
            execute_commands_atomic(commands, si, worktrees);
        else
            execute_commands_non_atomic(commands, si, worktrees);
        free_worktrees(worktrees);
        ...
    }

and then execute_commands_atomic() and execute_commands_non_atomic()
can pass `worktrees` along to update().

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v7 4/8] worktree: simplify find_shared_symref() memory ownership model
  2021-12-02  0:13       ` Eric Sunshine
@ 2021-12-02  0:32         ` Eric Sunshine
  2021-12-02  9:06         ` Anders Kaseorg
  1 sibling, 0 replies; 20+ messages in thread
From: Eric Sunshine @ 2021-12-02  0:32 UTC (permalink / raw)
  To: Anders Kaseorg
  Cc: Junio C Hamano, Git List, Johannes Schindelin, Jeff King,
	Andreas Heiduk, Ævar Arnfjörð Bjarmason,
	Jiang Xin

On Wed, Dec 1, 2021 at 7:13 PM Eric Sunshine <sunshine@sunshineco.com> wrote:
> If we instead hoist ownership of `worktrees` up to execute_commands()
> -- which calls execute_commands_atomic() or
> execute_commands_non_atomic() -- then we can get by with retrieving
> the worktrees just once, and all those noise changes in update() can
> be dropped since it will no longer be responsible for allocating or
> freeing `worktrees`. For instance:
>
>     static void execute_commands(...)
>     {
>         struct worktree **worktrees;
>         ...
>         worktrees = get_worktrees();
>          if (use_atomic)
>             execute_commands_atomic(commands, si, worktrees);
>         else
>             execute_commands_non_atomic(commands, si, worktrees);
>         free_worktrees(worktrees);
>         ...
>     }
>
> and then execute_commands_atomic() and execute_commands_non_atomic()
> can pass `worktrees` along to update().

By the way, I wouldn't typically recommend this sort of change for
public API since it probably exposes too much implementation detail to
callers, but as these are all private (static) worker functions --
with very few callers, moreover -- I don't find it terribly worrying.

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v7 5/8] fetch: protect branches checked out in all worktrees
  2021-12-01 22:15 ` [PATCH v7 5/8] fetch: protect branches checked out in all worktrees Anders Kaseorg
@ 2021-12-02  2:51   ` Eric Sunshine
  2021-12-02  8:37     ` Anders Kaseorg
  0 siblings, 1 reply; 20+ messages in thread
From: Eric Sunshine @ 2021-12-02  2:51 UTC (permalink / raw)
  To: Anders Kaseorg
  Cc: Junio C Hamano, Git List, Johannes Schindelin, Jeff King,
	Andreas Heiduk, Ævar Arnfjörð Bjarmason,
	Jiang Xin

On Wed, Dec 1, 2021 at 5:16 PM Anders Kaseorg <andersk@mit.edu> wrote:
> Refuse to fetch into the currently checked out branch of any working
> tree, not just the current one.
> [...]
> Signed-off-by: Anders Kaseorg <andersk@mit.edu>
> ---
> diff --git a/builtin/fetch.c b/builtin/fetch.c
> @@ -868,16 +868,17 @@ static int update_local_ref(struct ref *ref,
> +       if (!update_head_ok &&
> +           (wt = find_shared_symref(worktrees, "HEAD", ref->name)) &&
> +           !wt->is_bare && !is_null_oid(&ref->old_oid)) {
>                 /*
>                  * If this is the head, and it's not okay to update
>                  * the head, and the old value of the head isn't empty...
>                  */
>                 format_display(display, '!', _("[rejected]"),
> -                              _("can't fetch in current branch"),
> +                              wt->is_current ?
> +                                      _("can't fetch in current branch") :
> +                                      _("checked out in another worktree"),
>                                remote, pretty_ref, summary_width);

Minor observation: The "checked out in another worktree" message would
be much more helpful if it mentioned the worktree itself...

> @@ -1385,19 +1388,18 @@ static int prune_refs(struct refspec *rs, struct ref *ref_map,
> -               if (ref_map->peer_ref && !strcmp(current_branch->refname,
> -                                       ref_map->peer_ref->name))
> -                       die(_("refusing to fetch into current branch %s "
> -                             "of non-bare repository"),
> -                           current_branch->refname);
> +               if (ref_map->peer_ref &&
> +                   (wt = find_shared_symref(worktrees, "HEAD",
> +                                            ref_map->peer_ref->name)) &&
> +                   !wt->is_bare)
> +                       die(_("refusing to fetch into branch '%s' "
> +                             "checked out at '%s'"),
> +                           ref_map->peer_ref->name, wt->path);

... much like this message has been updated to mention the worktree.

However, I didn't look to see how much work it would be to improve the
earlier message; such an improvement could always be done atop this
series later on by someone.

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v7 5/8] fetch: protect branches checked out in all worktrees
  2021-12-02  2:51   ` Eric Sunshine
@ 2021-12-02  8:37     ` Anders Kaseorg
  0 siblings, 0 replies; 20+ messages in thread
From: Anders Kaseorg @ 2021-12-02  8:37 UTC (permalink / raw)
  To: Eric Sunshine
  Cc: Junio C Hamano, Git List, Johannes Schindelin, Jeff King,
	Andreas Heiduk, Ævar Arnfjörð Bjarmason,
	Jiang Xin

On 12/1/21 18:51, Eric Sunshine wrote:
> Minor observation: The "checked out in another worktree" message would
> be much more helpful if it mentioned the worktree itself...

Previous discussion of that: 
https://lore.kernel.org/git/YYTtdZblnHYgDgBq@coredump.intra.peff.net/ 
and replies.

In short, this message seems to be unreachable because the other message 
will always be triggered first, so I didn’t spend much effort improving 
it.  As you say, we can leave the task of sorting out the apparently 
duplicate check to another series.

Anders

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v7 4/8] worktree: simplify find_shared_symref() memory ownership model
  2021-12-02  0:13       ` Eric Sunshine
  2021-12-02  0:32         ` Eric Sunshine
@ 2021-12-02  9:06         ` Anders Kaseorg
  2021-12-02 21:00           ` Eric Sunshine
  1 sibling, 1 reply; 20+ messages in thread
From: Anders Kaseorg @ 2021-12-02  9:06 UTC (permalink / raw)
  To: Eric Sunshine
  Cc: Junio C Hamano, Git List, Johannes Schindelin, Jeff King,
	Andreas Heiduk, Ævar Arnfjörð Bjarmason,
	Jiang Xin

[-- Attachment #1: Type: text/plain, Size: 9910 bytes --]

On Wed, 1 Dec 2021, Eric Sunshine wrote:
> If we instead hoist ownership of `worktrees` up to execute_commands()
> -- which calls execute_commands_atomic() or
> execute_commands_non_atomic() -- then we can get by with retrieving
> the worktrees just once, and all those noise changes in update() can
> be dropped since it will no longer be responsible for allocating or
> freeing `worktrees`.

Seems reasonable to me.  It’s a smaller textual change, potentially a 
larger conceptual change, but the efficiency improvement is probably 
worthwhile.

That would modify patch 4 to the below.  Patches 5 through 8 cleanly 
rebase past this modification.

Anders

-- >8 --
Subject: [PATCH v7½ 4/8] worktree: simplify find_shared_symref() memory ownership model

Storing the worktrees list in a static variable meant that
find_shared_symref() had to rebuild the list on each call (which is
inefficient when the call site is in a loop), and also that each call
invalidated the pointer returned by the previous call (which is
confusing).

Instead, make it the caller’s responsibility to pass in the worktrees
list and manage its lifetime.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
---
 branch.c               | 14 ++++++++------
 builtin/branch.c       |  7 ++++++-
 builtin/notes.c        |  6 +++++-
 builtin/receive-pack.c | 29 ++++++++++++++++++++---------
 worktree.c             |  8 ++------
 worktree.h             |  5 +++--
 6 files changed, 44 insertions(+), 25 deletions(-)

diff --git a/branch.c b/branch.c
index 85777b939b..c66b222abd 100644
--- a/branch.c
+++ b/branch.c
@@ -357,14 +357,16 @@ void remove_branch_state(struct repository *r, int verbose)
 
 void die_if_checked_out(const char *branch, int ignore_current_worktree)
 {
+	struct worktree **worktrees = get_worktrees();
 	const struct worktree *wt;
 
-	wt = find_shared_symref("HEAD", branch);
-	if (!wt || (ignore_current_worktree && wt->is_current))
-		return;
-	skip_prefix(branch, "refs/heads/", &branch);
-	die(_("'%s' is already checked out at '%s'"),
-	    branch, wt->path);
+	wt = find_shared_symref(worktrees, "HEAD", branch);
+	if (wt && (!ignore_current_worktree || !wt->is_current)) {
+		skip_prefix(branch, "refs/heads/", &branch);
+		die(_("'%s' is already checked out at '%s'"), branch, wt->path);
+	}
+
+	free_worktrees(worktrees);
 }
 
 int replace_each_worktree_head_symref(const char *oldref, const char *newref,
diff --git a/builtin/branch.c b/builtin/branch.c
index 7a1d1eeb07..d8f2164cd7 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -193,6 +193,7 @@ static void delete_branch_config(const char *branchname)
 static int delete_branches(int argc, const char **argv, int force, int kinds,
 			   int quiet)
 {
+	struct worktree **worktrees;
 	struct commit *head_rev = NULL;
 	struct object_id oid;
 	char *name = NULL;
@@ -229,6 +230,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
 		if (!head_rev)
 			die(_("Couldn't look up commit object for HEAD"));
 	}
+
+	worktrees = get_worktrees();
+
 	for (i = 0; i < argc; i++, strbuf_reset(&bname)) {
 		char *target = NULL;
 		int flags = 0;
@@ -239,7 +243,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
 
 		if (kinds == FILTER_REFS_BRANCHES) {
 			const struct worktree *wt =
-				find_shared_symref("HEAD", name);
+				find_shared_symref(worktrees, "HEAD", name);
 			if (wt) {
 				error(_("Cannot delete branch '%s' "
 					"checked out at '%s'"),
@@ -300,6 +304,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
 
 	free(name);
 	strbuf_release(&bname);
+	free_worktrees(worktrees);
 
 	return ret;
 }
diff --git a/builtin/notes.c b/builtin/notes.c
index 71c59583a1..7f60408dbb 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -861,15 +861,19 @@ static int merge(int argc, const char **argv, const char *prefix)
 		update_ref(msg.buf, default_notes_ref(), &result_oid, NULL, 0,
 			   UPDATE_REFS_DIE_ON_ERR);
 	else { /* Merge has unresolved conflicts */
+		struct worktree **worktrees;
 		const struct worktree *wt;
 		/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
 		update_ref(msg.buf, "NOTES_MERGE_PARTIAL", &result_oid, NULL,
 			   0, UPDATE_REFS_DIE_ON_ERR);
 		/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
-		wt = find_shared_symref("NOTES_MERGE_REF", default_notes_ref());
+		worktrees = get_worktrees();
+		wt = find_shared_symref(worktrees, "NOTES_MERGE_REF",
+					default_notes_ref());
 		if (wt)
 			die(_("a notes merge into %s is already in-progress at %s"),
 			    default_notes_ref(), wt->path);
+		free_worktrees(worktrees);
 		if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
 			die(_("failed to store link to current notes ref (%s)"),
 			    default_notes_ref());
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index ddda27c184..925ce7f32d 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1477,7 +1477,8 @@ static const char *update_worktree(unsigned char *sha1, const struct worktree *w
 	return retval;
 }
 
-static const char *update(struct command *cmd, struct shallow_info *si)
+static const char *update(struct command *cmd, struct shallow_info *si,
+			  struct worktree **worktrees)
 {
 	const char *name = cmd->ref_name;
 	struct strbuf namespaced_name_buf = STRBUF_INIT;
@@ -1486,7 +1487,10 @@ static const char *update(struct command *cmd, struct shallow_info *si)
 	struct object_id *old_oid = &cmd->old_oid;
 	struct object_id *new_oid = &cmd->new_oid;
 	int do_update_worktree = 0;
-	const struct worktree *worktree = is_bare_repository() ? NULL : find_shared_symref("HEAD", name);
+	const struct worktree *worktree =
+		is_bare_repository() ?
+			NULL :
+			find_shared_symref(worktrees, "HEAD", name);
 
 	/* only refs/... are allowed */
 	if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) {
@@ -1579,7 +1583,9 @@ static const char *update(struct command *cmd, struct shallow_info *si)
 	}
 
 	if (do_update_worktree) {
-		ret = update_worktree(new_oid->hash, find_shared_symref("HEAD", name));
+		ret = update_worktree(new_oid->hash,
+				      find_shared_symref(worktrees, "HEAD",
+							 name));
 		if (ret)
 			return ret;
 	}
@@ -1842,7 +1848,8 @@ static void warn_if_skipped_connectivity_check(struct command *commands,
 }
 
 static void execute_commands_non_atomic(struct command *commands,
-					struct shallow_info *si)
+					struct shallow_info *si,
+					struct worktree **worktrees)
 {
 	struct command *cmd;
 	struct strbuf err = STRBUF_INIT;
@@ -1859,7 +1866,7 @@ static void execute_commands_non_atomic(struct command *commands,
 			continue;
 		}
 
-		cmd->error_string = update(cmd, si);
+		cmd->error_string = update(cmd, si, worktrees);
 
 		if (!cmd->error_string
 		    && ref_transaction_commit(transaction, &err)) {
@@ -1873,7 +1880,8 @@ static void execute_commands_non_atomic(struct command *commands,
 }
 
 static void execute_commands_atomic(struct command *commands,
-					struct shallow_info *si)
+				    struct shallow_info *si,
+				    struct worktree **worktrees)
 {
 	struct command *cmd;
 	struct strbuf err = STRBUF_INIT;
@@ -1891,7 +1899,7 @@ static void execute_commands_atomic(struct command *commands,
 		if (!should_process_cmd(cmd) || cmd->run_proc_receive)
 			continue;
 
-		cmd->error_string = update(cmd, si);
+		cmd->error_string = update(cmd, si, worktrees);
 
 		if (cmd->error_string)
 			goto failure;
@@ -1925,6 +1933,7 @@ static void execute_commands(struct command *commands,
 	struct async muxer;
 	int err_fd = 0;
 	int run_proc_receive = 0;
+	struct worktree **worktrees;
 
 	if (unpacker_error) {
 		for (cmd = commands; cmd; cmd = cmd->next)
@@ -2004,10 +2013,12 @@ static void execute_commands(struct command *commands,
 			    (cmd->run_proc_receive || use_atomic))
 				cmd->error_string = "fail to run proc-receive hook";
 
+	worktrees = get_worktrees();
 	if (use_atomic)
-		execute_commands_atomic(commands, si);
+		execute_commands_atomic(commands, si, worktrees);
 	else
-		execute_commands_non_atomic(commands, si);
+		execute_commands_non_atomic(commands, si, worktrees);
+	free_worktrees(worktrees);
 
 	if (shallow_update)
 		warn_if_skipped_connectivity_check(commands, si);
diff --git a/worktree.c b/worktree.c
index 092a4f92ad..cf13d63845 100644
--- a/worktree.c
+++ b/worktree.c
@@ -402,17 +402,13 @@ int is_worktree_being_bisected(const struct worktree *wt,
  * bisect). New commands that do similar things should update this
  * function as well.
  */
-const struct worktree *find_shared_symref(const char *symref,
+const struct worktree *find_shared_symref(struct worktree **worktrees,
+					  const char *symref,
 					  const char *target)
 {
 	const struct worktree *existing = NULL;
-	static struct worktree **worktrees;
 	int i = 0;
 
-	if (worktrees)
-		free_worktrees(worktrees);
-	worktrees = get_worktrees();
-
 	for (i = 0; worktrees[i]; i++) {
 		struct worktree *wt = worktrees[i];
 		const char *symref_target;
diff --git a/worktree.h b/worktree.h
index 8b7c408132..9e06fcbdf3 100644
--- a/worktree.h
+++ b/worktree.h
@@ -143,9 +143,10 @@ void free_worktrees(struct worktree **);
 /*
  * Check if a per-worktree symref points to a ref in the main worktree
  * or any linked worktree, and return the worktree that holds the ref,
- * or NULL otherwise. The result may be destroyed by the next call.
+ * or NULL otherwise.
  */
-const struct worktree *find_shared_symref(const char *symref,
+const struct worktree *find_shared_symref(struct worktree **worktrees,
+					  const char *symref,
 					  const char *target);
 
 /*
-- 
2.34.1

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v7 4/8] worktree: simplify find_shared_symref() memory ownership model
  2021-12-02  9:06         ` Anders Kaseorg
@ 2021-12-02 21:00           ` Eric Sunshine
  0 siblings, 0 replies; 20+ messages in thread
From: Eric Sunshine @ 2021-12-02 21:00 UTC (permalink / raw)
  To: Anders Kaseorg
  Cc: Junio C Hamano, Git List, Johannes Schindelin, Jeff King,
	Andreas Heiduk, Ævar Arnfjörð Bjarmason,
	Jiang Xin

On Thu, Dec 2, 2021 at 4:07 AM Anders Kaseorg <andersk@mit.edu> wrote:
> On Wed, 1 Dec 2021, Eric Sunshine wrote:
> > If we instead hoist ownership of `worktrees` up to execute_commands()
> > -- which calls execute_commands_atomic() or
> > execute_commands_non_atomic() -- then we can get by with retrieving
> > the worktrees just once, and all those noise changes in update() can
> > be dropped since it will no longer be responsible for allocating or
> > freeing `worktrees`.
>
> Seems reasonable to me.  It’s a smaller textual change, potentially a
> larger conceptual change, but the efficiency improvement is probably
> worthwhile.
>
> That would modify patch 4 to the below.  Patches 5 through 8 cleanly
> rebase past this modification.

Yes, this version looks good. Thanks for accommodating my suggestion.
Not only does it make for a less noisy diff (which is pleasing), but
perhaps more importantly, it reduces cognitive load a small amount (at
least for me[1]).

> Subject: [PATCH v7½ 4/8] worktree: simplify find_shared_symref() memory ownership model

I suspect that Junio will find it easier to take a full re-roll than
dealing with a single patch replacement since he doesn't necessarily
follow these discussions closely. It's more likely that he'll pick up
this change if you post v8 in its entirety.

[1]: Although we regularly use `goto` for this sort of resource
cleanup, most such functions are much shorter than the one here;
somehow the length of this function made the new `goto`s a tiny bit
less easy to grasp-at-a-glance. Purely subjective, I know.

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v7 8/8] branch: protect branches checked out in all worktrees
  2021-12-01 22:15 ` [PATCH v7 8/8] branch: protect branches checked out in all worktrees Anders Kaseorg
@ 2021-12-23  0:58   ` Jiang Xin
  2022-01-12  6:31     ` Jiang Xin
  0 siblings, 1 reply; 20+ messages in thread
From: Jiang Xin @ 2021-12-23  0:58 UTC (permalink / raw)
  To: Anders Kaseorg
  Cc: Junio C Hamano, Git List, Johannes Schindelin, Jeff King,
	Andreas Heiduk, Ævar Arnfjörð Bjarmason

On Thu, Dec 2, 2021 at 6:16 AM Anders Kaseorg <andersk@mit.edu> wrote:
>
> Refuse to force-move a branch over the currently checked out branch of
> any working tree, not just the current one.
>
> Signed-off-by: Anders Kaseorg <andersk@mit.edu>
> ---
>  branch.c          | 13 +++++++++----
>  t/t3200-branch.sh |  7 +++++++
>  2 files changed, 16 insertions(+), 4 deletions(-)
>
> diff --git a/branch.c b/branch.c
> index c66b222abd..2cfe496d24 100644
> --- a/branch.c
> +++ b/branch.c
> @@ -199,7 +199,8 @@ int validate_branchname(const char *name, struct strbuf *ref)
>   */
>  int validate_new_branchname(const char *name, struct strbuf *ref, int force)
>  {
> -       const char *head;
> +       struct worktree **worktrees;
> +       const struct worktree *wt;
>
>         if (!validate_branchname(name, ref))
>                 return 0;
> @@ -208,9 +209,13 @@ int validate_new_branchname(const char *name, struct strbuf *ref, int force)
>                 die(_("a branch named '%s' already exists"),
>                     ref->buf + strlen("refs/heads/"));
>
> -       head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
> -       if (!is_bare_repository() && head && !strcmp(head, ref->buf))
> -               die(_("cannot force update the current branch"));
> +       worktrees = get_worktrees();
> +       wt = find_shared_symref(worktrees, "HEAD", ref->buf);
> +       if (wt && !wt->is_bare)
> +               die(_("cannot force update the branch '%s'"
> +                     "checked out at '%s'"),

There is no space between "'%s'" and "checkout". Found this from [1],
sorry for reporting this is a bit late.

[1] https://github.com/git-l10n/git-po/blob/pot/master/2021-12-22.diff#L315

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v7 8/8] branch: protect branches checked out in all worktrees
  2021-12-23  0:58   ` Jiang Xin
@ 2022-01-12  6:31     ` Jiang Xin
  2022-01-12 19:10       ` Junio C Hamano
  0 siblings, 1 reply; 20+ messages in thread
From: Jiang Xin @ 2022-01-12  6:31 UTC (permalink / raw)
  To: Anders Kaseorg, Junio C Hamano
  Cc: Git List, Alexander Shopov, Jordi Mas, Matthias Rüster,
	Jimmy Angelakos, Christopher Díaz, Jean-Noël Avila,
	Bagas Sanjaya, Alessandro Menti, Gwan-gyeong Mun, Arusekk,
	Daniel Santos, Dimitriy Ryazantcev, Peter Krefting, Emir SARI,
	Trần Ngọc Quân, Fangyi Zhou, Yi-Jyun Pan

On Thu, Dec 23, 2021 at 8:58 AM Jiang Xin <worldhello.net@gmail.com> wrote:
>
> On Thu, Dec 2, 2021 at 6:16 AM Anders Kaseorg <andersk@mit.edu> wrote:
> > +       if (wt && !wt->is_bare)
> > +               die(_("cannot force update the branch '%s'"
> > +                     "checked out at '%s'"),
>
> There is no space between "'%s'" and "checkout". Found this from [1],
> sorry for reporting this is a bit late.
>
> [1] https://github.com/git-l10n/git-po/blob/pot/master/2021-12-22.diff#L315

This issue was still there in git v2.35.0-rc0. Peter Krefting and Emir
SARI also reported this issue in pull requests:

 * https://github.com/git-l10n/git-po/pull/599
 * https://github.com/git-l10n/git-po/pull/600

I see Bagas has sent a patch for this:

 * https://lore.kernel.org/git/20220111123627.58625-1-bagasdotme@gmail.com/

--
Jiang Xin

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v7 8/8] branch: protect branches checked out in all worktrees
  2022-01-12  6:31     ` Jiang Xin
@ 2022-01-12 19:10       ` Junio C Hamano
  0 siblings, 0 replies; 20+ messages in thread
From: Junio C Hamano @ 2022-01-12 19:10 UTC (permalink / raw)
  To: Jiang Xin
  Cc: Anders Kaseorg, Git List, Alexander Shopov, Jordi Mas,
	Matthias Rüster, Jimmy Angelakos, Christopher Díaz,
	Jean-Noël Avila, Bagas Sanjaya, Alessandro Menti,
	Gwan-gyeong Mun, Arusekk, Daniel Santos, Dimitriy Ryazantcev,
	Peter Krefting, Emir SARI, Trần Ngọc Quân,
	Fangyi Zhou, Yi-Jyun Pan

Jiang Xin <worldhello.net@gmail.com> writes:

> I see Bagas has sent a patch for this:
>
>  * https://lore.kernel.org/git/20220111123627.58625-1-bagasdotme@gmail.com/

Yup, picked up and will merge down before -rc1.

Thanks, all.

^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2022-01-12 19:19 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-12-01 22:15 [PATCH v7 0/8] protect branches checked out in all worktrees Anders Kaseorg
2021-12-01 22:15 ` [PATCH v7 1/8] fetch: lowercase error messages Anders Kaseorg
2021-12-01 22:15 ` [PATCH v7 2/8] receive-pack: " Anders Kaseorg
2021-12-01 22:15 ` [PATCH v7 3/8] branch: " Anders Kaseorg
2021-12-01 22:15 ` [PATCH v7 4/8] worktree: simplify find_shared_symref() memory ownership model Anders Kaseorg
2021-12-01 23:10   ` Eric Sunshine
2021-12-01 23:47     ` Anders Kaseorg
2021-12-02  0:13       ` Eric Sunshine
2021-12-02  0:32         ` Eric Sunshine
2021-12-02  9:06         ` Anders Kaseorg
2021-12-02 21:00           ` Eric Sunshine
2021-12-01 22:15 ` [PATCH v7 5/8] fetch: protect branches checked out in all worktrees Anders Kaseorg
2021-12-02  2:51   ` Eric Sunshine
2021-12-02  8:37     ` Anders Kaseorg
2021-12-01 22:15 ` [PATCH v7 6/8] receive-pack: clean dead code from update_worktree() Anders Kaseorg
2021-12-01 22:15 ` [PATCH v7 7/8] receive-pack: protect current branch for bare repository worktree Anders Kaseorg
2021-12-01 22:15 ` [PATCH v7 8/8] branch: protect branches checked out in all worktrees Anders Kaseorg
2021-12-23  0:58   ` Jiang Xin
2022-01-12  6:31     ` Jiang Xin
2022-01-12 19:10       ` Junio C Hamano

Code repositories for project(s) associated with this inbox:

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).