Git development
 help / color / mirror / Atom feed
From: "Son Luong Ngoc via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com>,
	Phillip Wood <phillip.wood123@gmail.com>,
	Son Luong Ngoc <sluongng@gmail.com>,
	Son Luong Ngoc <sluongng@gmail.com>
Subject: [PATCH v3 1/2] rebase: skip branch symref aliases
Date: Wed, 22 Jul 2026 08:15:05 +0000	[thread overview]
Message-ID: <b9a01e9141d580606527cb1a658c7c72710fb013.1784708107.git.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2126.v3.git.1784708107.gitgitgadget@gmail.com>

From: Son Luong Ngoc <sluongng@gmail.com>

git rebase --update-refs can finish rewriting the current branch and
then fail while updating a local branch that is a symbolic ref. This can
happen during a default-branch rename where refs/heads/main points at
refs/heads/master while users migrate.

The problem is a partially applied ref update: the main rebase has
already succeeded when the later ref update fails.

The sequencer queues updates from local branch decorations. Commit
106b6885c7 (rebase: ignore non-branch update-refs) filters out
decorations such as HEAD and tags. A branch symref is still a local
branch decoration, but refs_update_ref() dereferences it, so an alias to
another branch duplicates the concrete branch update.

Resolve local branch decorations before queuing them. Skip symrefs whose
targets are under refs/heads/ so that only the concrete branch update is
queued. Keep an owned copy of the resolved HEAD and skip the current
branch before checked-out handling so later ref resolution cannot change
the comparison.

This prevents a successful rebase from being followed by a failed,
partially applied ref update while preserving each alias as a symref.

Signed-off-by: Son Luong Ngoc <sluongng@gmail.com>
---
 sequencer.c                   | 44 +++++++++++++++++++++++++----------
 t/t3400-rebase.sh             |  2 +-
 t/t3404-rebase-interactive.sh | 16 +++++++++++++
 3 files changed, 49 insertions(+), 13 deletions(-)

diff --git a/sequencer.c b/sequencer.c
index 1355a99a09..63aba60a08 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -6465,32 +6465,50 @@ static int add_decorations_to_list(const struct commit *commit,
 				   struct todo_add_branch_context *ctx)
 {
 	const struct name_decoration *decoration = get_name_decoration(&commit->object);
-	const char *head_ref = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
-						       "HEAD",
-						       RESOLVE_REF_READING,
-						       NULL,
-						       NULL);
+	struct ref_store *refs = get_main_ref_store(the_repository);
+	char *head_ref = refs_resolve_refdup(refs, "HEAD",
+					     RESOLVE_REF_READING,
+					     NULL, NULL);
 
 	while (decoration) {
 		struct todo_item *item;
 		const char *path;
+		char *resolved_ref;
+		int flags = 0;
 		size_t base_offset = ctx->buf->len;
 
 		/*
-		 * If the branch is the current HEAD, then it will be
-		 * updated by the default rebase behavior.
-		 * Exclude it from the list of refs to update,
-		 * as well as any non-branch decorations.
 		 * Non-branch decorations may be present if the pretty format
 		 * includes "%d", which would have loaded all refs
 		 * into the global decoration table.
 		 */
-		if ((head_ref && !strcmp(head_ref, decoration->name)) ||
-		    (decoration->type != DECORATION_REF_LOCAL)) {
+		if (decoration->type != DECORATION_REF_LOCAL) {
+			decoration = decoration->next;
+			continue;
+		}
+
+		resolved_ref = refs_resolve_refdup(refs, decoration->name,
+						      RESOLVE_REF_READING,
+						      NULL, &flags);
+		if (resolved_ref && (flags & REF_ISSYMREF) &&
+		    starts_with(resolved_ref, "refs/heads/")) {
+			free(resolved_ref);
+			decoration = decoration->next;
+			continue;
+		}
+
+		/*
+		 * If the branch is the current HEAD, then it will be
+		 * updated by the default rebase behavior.
+		 */
+		if (head_ref && !strcmp(head_ref, decoration->name)) {
+			free(resolved_ref);
 			decoration = decoration->next;
 			continue;
 		}
 
+		path = branch_checked_out(decoration->name);
+
 		ALLOC_GROW(ctx->items,
 			ctx->items_nr + 1,
 			ctx->items_alloc);
@@ -6498,7 +6516,7 @@ static int add_decorations_to_list(const struct commit *commit,
 		memset(item, 0, sizeof(*item));
 
 		/* If the branch is checked out, then leave a comment instead. */
-		if ((path = branch_checked_out(decoration->name))) {
+		if (path) {
 			item->command = TODO_COMMENT;
 			strbuf_commented_addf(ctx->buf, comment_line_str,
 					      "Ref %s checked out at '%s'\n",
@@ -6518,9 +6536,11 @@ static int add_decorations_to_list(const struct commit *commit,
 		item->arg_len = ctx->buf->len - base_offset;
 		ctx->items_nr++;
 
+		free(resolved_ref);
 		decoration = decoration->next;
 	}
 
+	free(head_ref);
 	return 0;
 }
 
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index e62e07b894..1a02f6546b 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -471,7 +471,7 @@ test_expect_success 'git rebase --update-ref with core.commentChar and branch on
 	GIT_SEQUENCE_EDITOR="cat >actual" git -c core.commentChar=% \
 		 rebase -i --update-refs base &&
 	test_grep "% Ref refs/heads/wt-topic checked out at" actual &&
-	test_grep "% Ref refs/heads/topic2 checked out at" actual
+	test_grep ! "% Ref refs/heads/topic2 checked out at" actual
 '
 
 test_done
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index e64816770a..11afa8be56 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -1975,15 +1975,23 @@ test_expect_success '--update-refs ignores non-branch decorations' '
 	) &&
 	grep ^update-ref todo >actual &&
 	test_write_lines "update-ref refs/heads/no-conflict-branch" >expect &&
+	test_grep ! "^# Ref refs/heads/update-refs checked out" todo &&
 	test_cmp expect actual
 '
 
 test_expect_success '--update-refs updates refs correctly' '
+	test_when_finished "
+		test_might_fail git symbolic-ref -d refs/heads/no-conflict-branch-alias &&
+		test_might_fail git symbolic-ref -d refs/heads/second-alias
+	" &&
 	git checkout -B update-refs no-conflict-branch &&
 	git branch -f base HEAD~4 &&
 	git branch -f first HEAD~3 &&
 	git branch -f second HEAD~3 &&
 	git branch -f third HEAD~1 &&
+	git symbolic-ref refs/heads/no-conflict-branch-alias \
+		refs/heads/no-conflict-branch &&
+	git symbolic-ref refs/heads/second-alias refs/heads/second &&
 	test_commit extra2 fileX &&
 	git commit --amend --fixup=L &&
 
@@ -1991,8 +1999,16 @@ test_expect_success '--update-refs updates refs correctly' '
 
 	test_cmp_rev HEAD~3 refs/heads/first &&
 	test_cmp_rev HEAD~3 refs/heads/second &&
+	test_cmp_rev HEAD~3 refs/heads/second-alias &&
 	test_cmp_rev HEAD~1 refs/heads/third &&
 	test_cmp_rev HEAD refs/heads/no-conflict-branch &&
+	test_cmp_rev HEAD refs/heads/no-conflict-branch-alias &&
+	test_write_lines refs/heads/no-conflict-branch >expect &&
+	git symbolic-ref refs/heads/no-conflict-branch-alias >actual &&
+	test_cmp expect actual &&
+	test_write_lines refs/heads/second >expect &&
+	git symbolic-ref refs/heads/second-alias >actual &&
+	test_cmp expect actual &&
 
 	q_to_tab >expect <<-\EOF &&
 	Successfully rebased and updated refs/heads/update-refs.
-- 
gitgitgadget


  reply	other threads:[~2026-07-22  8:15 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-28  5:41 [PATCH 0/2] rebase: handle --update-refs branch symrefs Son Luong Ngoc via GitGitGadget
2026-05-28  5:42 ` [PATCH 1/2] t3404: add failing branch symref test Son Luong Ngoc via GitGitGadget
2026-06-01 13:52   ` Phillip Wood
2026-05-28  5:42 ` [PATCH 2/2] rebase: skip branch symref aliases Son Luong Ngoc via GitGitGadget
2026-05-28  7:08   ` Kristoffer Haugsbakk
2026-06-01 14:10   ` Phillip Wood
2026-05-28 20:42 ` [PATCH 0/2] rebase: handle --update-refs branch symrefs Junio C Hamano
2026-06-03 10:27 ` [PATCH v2] rebase: skip branch symref aliases Son Luong Ngoc via GitGitGadget
2026-06-04 15:37   ` Phillip Wood
2026-07-22  8:16     ` Son Luong Ngoc
2026-07-22  8:15   ` [PATCH v3 0/2] rebase: handle --update-refs branch symrefs Son Luong Ngoc via GitGitGadget
2026-07-22  8:15     ` Son Luong Ngoc via GitGitGadget [this message]
2026-07-23 18:58       ` [PATCH v3 1/2] rebase: skip branch symref aliases Phillip Wood
2026-07-22  8:15     ` [PATCH v3 2/2] rebase: guard non-branch symref targets Son Luong Ngoc via GitGitGadget

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=b9a01e9141d580606527cb1a658c7c72710fb013.1784708107.git.gitgitgadget@gmail.com \
    --to=gitgitgadget@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=kristofferhaugsbakk@fastmail.com \
    --cc=phillip.wood123@gmail.com \
    --cc=sluongng@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox