diff mbox series

[v4,1/3] branch: avoid unnecessary worktrees traversals

Message ID 50dd7c7a-5656-e010-1c0b-819a40a1f1a0@gmail.com (mailing list archive)
State Accepted
Commit c8fb01f6f726d362f263e9277c16cb646f2409f3
Headers show
Series branch: operations on orphan branches | expand

Commit Message

Rubén Justo Feb. 22, 2023, 10:52 p.m. UTC
"reject_rebase_or_bisect_branch()" was introduced [1] to prevent a
branch under bisect or rebase from being renamed or copied.  It
traverses all worktrees in the repository and dies if the specified
branch is being rebased or bisected in any of them.

"replace_each_worktree_head_symref()" was introduced [2] to adjust the
HEAD in all worktrees after a branch rename succeeded.  It traverses all
worktrees in the repository and if any of them have their HEAD pointing
to the renamed ref, it adjusts it.

Considering that both functions are only called from within
copy_or_rename_branch() and each of them traverses all worktrees, which
might involve disk access and resolving multiple references, inlining
these two functions to do the traversing once, makes sense.

  1.- 14ace5b (branch: do not rename a branch under bisect or rebase,
      2016-04-22)

  2.- 70999e9 (branch -m: update all per-worktree HEADs, 2016-03-27)

Signed-off-by: Rubén Justo <rjusto@gmail.com>
---
 branch.c         | 27 --------------------
 branch.h         |  8 ------
 builtin/branch.c | 64 ++++++++++++++++++++++++++++--------------------
 3 files changed, 38 insertions(+), 61 deletions(-)

Comments

Rubén Justo Feb. 25, 2023, 3:08 p.m. UTC | #1
On 22-feb-2023 23:52:51, Rubén Justo wrote:
> "reject_rebase_or_bisect_branch()" was introduced [1] to prevent a
> branch under bisect or rebase from being renamed or copied.  It
> traverses all worktrees in the repository and dies if the specified
> branch is being rebased or bisected in any of them.
> 
> "replace_each_worktree_head_symref()" was introduced [2] to adjust the
> HEAD in all worktrees after a branch rename succeeded.  It traverses all
> worktrees in the repository and if any of them have their HEAD pointing
> to the renamed ref, it adjusts it.
> 
> Considering that both functions are only called from within
> copy_or_rename_branch() and each of them traverses all worktrees, which
> might involve disk access and resolving multiple references, inlining
> these two functions to do the traversing once, makes sense.
> 
>   1.- 14ace5b (branch: do not rename a branch under bisect or rebase,
>       2016-04-22)
> 
>   2.- 70999e9 (branch -m: update all per-worktree HEADs, 2016-03-27)
> 
> Signed-off-by: Rubén Justo <rjusto@gmail.com>
> ---
>  branch.c         | 27 --------------------
>  branch.h         |  8 ------
>  builtin/branch.c | 64 ++++++++++++++++++++++++++++--------------------
>  3 files changed, 38 insertions(+), 61 deletions(-)
> 
> diff --git a/branch.c b/branch.c
> index e5614b53b3..f64062be71 100644
> --- a/branch.c
> +++ b/branch.c
> @@ -830,30 +830,3 @@ void die_if_checked_out(const char *branch, int ignore_current_worktree)
>  
>  	free_worktrees(worktrees);
>  }
> -
> -int replace_each_worktree_head_symref(const char *oldref, const char *newref,
> -				      const char *logmsg)
> -{
> -	int ret = 0;
> -	struct worktree **worktrees = get_worktrees();
> -	int i;
> -
> -	for (i = 0; worktrees[i]; i++) {
> -		struct ref_store *refs;
> -
> -		if (worktrees[i]->is_detached)
> -			continue;
> -		if (!worktrees[i]->head_ref)
> -			continue;
> -		if (strcmp(oldref, worktrees[i]->head_ref))
> -			continue;
> -
> -		refs = get_worktree_ref_store(worktrees[i]);
> -		if (refs_create_symref(refs, "HEAD", newref, logmsg))
> -			ret = error(_("HEAD of working tree %s is not updated"),
> -				    worktrees[i]->path);
> -	}
> -
> -	free_worktrees(worktrees);
> -	return ret;
> -}
> diff --git a/branch.h b/branch.h
> index ef56103c05..30c01aed73 100644
> --- a/branch.h
> +++ b/branch.h
> @@ -155,12 +155,4 @@ int read_branch_desc(struct strbuf *, const char *branch_name);
>   */
>  void die_if_checked_out(const char *branch, int ignore_current_worktree);
>  
> -/*
> - * Update all per-worktree HEADs pointing at the old ref to point the new ref.
> - * This will be used when renaming a branch. Returns 0 if successful, non-zero
> - * otherwise.
> - */
> -int replace_each_worktree_head_symref(const char *oldref, const char *newref,
> -				      const char *logmsg);
> -
>  #endif
> diff --git a/builtin/branch.c b/builtin/branch.c
> index f63fd45edb..a32ae64006 100644
> --- a/builtin/branch.c
> +++ b/builtin/branch.c
> @@ -486,28 +486,6 @@ static void print_current_branch_name(void)
>  		die(_("HEAD (%s) points outside of refs/heads/"), refname);
>  }
>  
> -static void reject_rebase_or_bisect_branch(const char *target)
> -{
> -	struct worktree **worktrees = get_worktrees();
> -	int i;
> -
> -	for (i = 0; worktrees[i]; i++) {
> -		struct worktree *wt = worktrees[i];
> -
> -		if (!wt->is_detached)
> -			continue;
> -
> -		if (is_worktree_being_rebased(wt, target))
> -			die(_("Branch %s is being rebased at %s"),
> -			    target, wt->path);
> -
> -		if (is_worktree_being_bisected(wt, target))
> -			die(_("Branch %s is being bisected at %s"),
> -			    target, wt->path);
> -	}
> -
> -	free_worktrees(worktrees);
> -}
>  
>  static void copy_or_rename_branch(const char *oldname, const char *newname, int copy, int force)
>  {
> @@ -516,6 +494,7 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
>  	const char *interpreted_oldname = NULL;
>  	const char *interpreted_newname = NULL;
>  	int recovery = 0;
> +	struct worktree **worktrees = get_worktrees();
>  
>  	if (strbuf_check_branch_ref(&oldref, oldname)) {
>  		/*
> @@ -544,7 +523,20 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
>  	else
>  		validate_new_branchname(newname, &newref, force);
>  
> -	reject_rebase_or_bisect_branch(oldref.buf);
> +	for (int i = 0; worktrees[i]; i++) {
> +		struct worktree *wt = worktrees[i];
> +
> +		if (!wt->is_detached)
> +			continue;
> +
> +		if (is_worktree_being_rebased(wt, oldref.buf))
> +			die(_("Branch %s is being rebased at %s"),
> +			    oldref.buf, wt->path);
> +
> +		if (is_worktree_being_bisected(wt, oldref.buf))
> +			die(_("Branch %s is being bisected at %s"),
> +			    oldref.buf, wt->path);
> +	}
>  
>  	if (!skip_prefix(oldref.buf, "refs/heads/", &interpreted_oldname) ||
>  	    !skip_prefix(newref.buf, "refs/heads/", &interpreted_newname)) {
> @@ -574,9 +566,29 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
>  				interpreted_oldname);
>  	}
>  
> -	if (!copy &&
> -	    replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf))
> -		die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
> +	if (!copy) {
> +		/*
> +		 * Update all per-worktree HEADs pointing at the old ref to
> +		 * point the new ref.
> +		 */
> +		for (int i = 0; worktrees[i]; i++) {
> +			struct ref_store *refs;
> +
> +			if (worktrees[i]->is_detached)
> +				continue;
> +			if (!worktrees[i]->head_ref)
> +				continue;
> +			if (strcmp(oldref.buf, worktrees[i]->head_ref))
> +				continue;
> +
> +			refs = get_worktree_ref_store(worktrees[i]);
> +			if (refs_create_symref(refs, "HEAD", newref.buf, logmsg.buf))
> +				die(_("Branch renamed to %s, but HEAD is not updated!"),
> +					newname);

Reviewing this, I noticed I made a mistake here.  The original code
doesn't stop iterating whenever refs_create_symref() fails; it continues
trying to update the remaining worktrees.  When the iteration ends, if
any of the updates failed, then die().  Also, the error message "HEAD of
working tree %s is not updated" is lost.  I'll reroll with this errors
fixed but maybe some review is already underway, so I'll wait some
time.  Sorry for the inconvenience.
Jonathan Tan Feb. 27, 2023, 7:30 p.m. UTC | #2
Rubén Justo <rjusto@gmail.com> writes:
> Reviewing this, I noticed I made a mistake here.  The original code
> doesn't stop iterating whenever refs_create_symref() fails; it continues
> trying to update the remaining worktrees.  When the iteration ends, if
> any of the updates failed, then die().  Also, the error message "HEAD of
> working tree %s is not updated" is lost.  

Ah yes, I noticed this too.

Besides that, a reviewer, upon reading the commit message, might ask:
why not take the worktrees as a parameter then, if we're so worried
about performance? I think that the real reason for inlining is that the
code being inlined needs to communicate more information to its calling
function in subsequent patches; the performance improvement is only a
beneficial side effect.
Rubén Justo Feb. 28, 2023, 12:11 a.m. UTC | #3
On 27/2/23 20:30, Jonathan Tan wrote:
> Rubén Justo <rjusto@gmail.com> writes:
>> Reviewing this, I noticed I made a mistake here.  The original code
>> doesn't stop iterating whenever refs_create_symref() fails; it continues
>> trying to update the remaining worktrees.  When the iteration ends, if
>> any of the updates failed, then die().  Also, the error message "HEAD of
>> working tree %s is not updated" is lost.  
> 
> Ah yes, I noticed this too.

I'll re-roll with the fix in the next iteration.

Thank you for reading carefully.

> 
> Besides that, a reviewer, upon reading the commit message, might ask:
> why not take the worktrees as a parameter then, if we're so worried
> about performance? I think that the real reason for inlining is that the
> code being inlined needs to communicate more information to its calling
> function in subsequent patches; the performance improvement is only a
> beneficial side effect.
> 

Certainly, that's a way to see the modification within this series.
Actually, this modification wasn't present in v1, but once it was
introduced in v2, it made sense on its own: to eliminate the redundant
traversals of worktrees when renaming a branch, even if the branch isn't
HEAD in any worktree.

Therefore, I think the change can also be seen in another way: the
increased ease in the next modifications is a beneficial side effect
of this patch.
diff mbox series

Patch

diff --git a/branch.c b/branch.c
index e5614b53b3..f64062be71 100644
--- a/branch.c
+++ b/branch.c
@@ -830,30 +830,3 @@  void die_if_checked_out(const char *branch, int ignore_current_worktree)
 
 	free_worktrees(worktrees);
 }
-
-int replace_each_worktree_head_symref(const char *oldref, const char *newref,
-				      const char *logmsg)
-{
-	int ret = 0;
-	struct worktree **worktrees = get_worktrees();
-	int i;
-
-	for (i = 0; worktrees[i]; i++) {
-		struct ref_store *refs;
-
-		if (worktrees[i]->is_detached)
-			continue;
-		if (!worktrees[i]->head_ref)
-			continue;
-		if (strcmp(oldref, worktrees[i]->head_ref))
-			continue;
-
-		refs = get_worktree_ref_store(worktrees[i]);
-		if (refs_create_symref(refs, "HEAD", newref, logmsg))
-			ret = error(_("HEAD of working tree %s is not updated"),
-				    worktrees[i]->path);
-	}
-
-	free_worktrees(worktrees);
-	return ret;
-}
diff --git a/branch.h b/branch.h
index ef56103c05..30c01aed73 100644
--- a/branch.h
+++ b/branch.h
@@ -155,12 +155,4 @@  int read_branch_desc(struct strbuf *, const char *branch_name);
  */
 void die_if_checked_out(const char *branch, int ignore_current_worktree);
 
-/*
- * Update all per-worktree HEADs pointing at the old ref to point the new ref.
- * This will be used when renaming a branch. Returns 0 if successful, non-zero
- * otherwise.
- */
-int replace_each_worktree_head_symref(const char *oldref, const char *newref,
-				      const char *logmsg);
-
 #endif
diff --git a/builtin/branch.c b/builtin/branch.c
index f63fd45edb..a32ae64006 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -486,28 +486,6 @@  static void print_current_branch_name(void)
 		die(_("HEAD (%s) points outside of refs/heads/"), refname);
 }
 
-static void reject_rebase_or_bisect_branch(const char *target)
-{
-	struct worktree **worktrees = get_worktrees();
-	int i;
-
-	for (i = 0; worktrees[i]; i++) {
-		struct worktree *wt = worktrees[i];
-
-		if (!wt->is_detached)
-			continue;
-
-		if (is_worktree_being_rebased(wt, target))
-			die(_("Branch %s is being rebased at %s"),
-			    target, wt->path);
-
-		if (is_worktree_being_bisected(wt, target))
-			die(_("Branch %s is being bisected at %s"),
-			    target, wt->path);
-	}
-
-	free_worktrees(worktrees);
-}
 
 static void copy_or_rename_branch(const char *oldname, const char *newname, int copy, int force)
 {
@@ -516,6 +494,7 @@  static void copy_or_rename_branch(const char *oldname, const char *newname, int
 	const char *interpreted_oldname = NULL;
 	const char *interpreted_newname = NULL;
 	int recovery = 0;
+	struct worktree **worktrees = get_worktrees();
 
 	if (strbuf_check_branch_ref(&oldref, oldname)) {
 		/*
@@ -544,7 +523,20 @@  static void copy_or_rename_branch(const char *oldname, const char *newname, int
 	else
 		validate_new_branchname(newname, &newref, force);
 
-	reject_rebase_or_bisect_branch(oldref.buf);
+	for (int i = 0; worktrees[i]; i++) {
+		struct worktree *wt = worktrees[i];
+
+		if (!wt->is_detached)
+			continue;
+
+		if (is_worktree_being_rebased(wt, oldref.buf))
+			die(_("Branch %s is being rebased at %s"),
+			    oldref.buf, wt->path);
+
+		if (is_worktree_being_bisected(wt, oldref.buf))
+			die(_("Branch %s is being bisected at %s"),
+			    oldref.buf, wt->path);
+	}
 
 	if (!skip_prefix(oldref.buf, "refs/heads/", &interpreted_oldname) ||
 	    !skip_prefix(newref.buf, "refs/heads/", &interpreted_newname)) {
@@ -574,9 +566,29 @@  static void copy_or_rename_branch(const char *oldname, const char *newname, int
 				interpreted_oldname);
 	}
 
-	if (!copy &&
-	    replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf))
-		die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
+	if (!copy) {
+		/*
+		 * Update all per-worktree HEADs pointing at the old ref to
+		 * point the new ref.
+		 */
+		for (int i = 0; worktrees[i]; i++) {
+			struct ref_store *refs;
+
+			if (worktrees[i]->is_detached)
+				continue;
+			if (!worktrees[i]->head_ref)
+				continue;
+			if (strcmp(oldref.buf, worktrees[i]->head_ref))
+				continue;
+
+			refs = get_worktree_ref_store(worktrees[i]);
+			if (refs_create_symref(refs, "HEAD", newref.buf, logmsg.buf))
+				die(_("Branch renamed to %s, but HEAD is not updated!"),
+					newname);
+		}
+	}
+
+	free_worktrees(worktrees);
 
 	strbuf_release(&logmsg);