From: Junio C Hamano <gitster@pobox.com>
To: Patrick Steinhardt <ps@pks.im>
Cc: git@vger.kernel.org
Subject: Re: [PATCH v2] update-ref: add --rename option
Date: Thu, 11 Jun 2026 11:47:16 -0700 [thread overview]
Message-ID: <xmqqwlw4nccr.fsf@gitster.g> (raw)
In-Reply-To: <aiqytJD-rcEirhgE@pks.im> (Patrick Steinhardt's message of "Thu, 11 Jun 2026 15:05:56 +0200")
Patrick Steinhardt <ps@pks.im> writes:
> One thing that I'm missing from the commit message: what's the
> motivation for this new mode?
Maintenance of merge-fix database, a kludgy way to manage evil
merges that are needed to deal with inter-topic semantic crashes.
If you are really interested, see the appendix.
>> diff --git a/Documentation/git-update-ref.adoc b/Documentation/git-update-ref.adoc
>> index 37a5019a8b..0c27efaa52 100644
>> --- a/Documentation/git-update-ref.adoc
>> +++ b/Documentation/git-update-ref.adoc
>> @@ -39,6 +40,14 @@ the result of following the symbolic pointers.
>> With `-d`, it deletes the named <ref> after verifying that it
>> still contains <old-oid>.
>>
>> +With `--rename`, it renames <old-refname> together with its reflog to
>> +<new-refname>. The command fails if <old-refname> does not exist, or
>> +if <new-refname> already exists. Because `git update-ref` does not
>> +update active worktree `HEAD` symbolic references or `.git/config`
>> +tracking settings when you rename a local branch in the `refs/heads/`
>> +hierarchy, think twice before using this command to rename a local
>> +branch (use `git branch -m` instead).
>
> I'd rephrase this slightly to first document behaviour and then draw the
> conclusion that it shouldn't be used in many cases separately. For
> example:
>
> This command does not update any symbolic references pointing to
> the renamed reference, and neither does it update `.git/config`
> tracking settings. It is thus not recommended to use it for renaming
> local branches. Use `git branch -m` instead.
Thanks, that is much better.
>> + if (!refs_ref_exists(get_main_ref_store(the_repository), oldref))
>> + die("no ref named '%s'", oldref);
>> +
>> + if (refs_ref_exists(get_main_ref_store(the_repository), newref))
>> + die("ref '%s' already exists", newref);
>> +
>> + if (refs_rename_ref(get_main_ref_store(the_repository),
>> + oldref, newref, msg))
>> + die("rename failed");
>> + return 0;
>> + }
>
> Hm. I think we're not using "--deref" / "--no-deref" at all, but we
> document this flag as accepted in the synopsis.
Good point. refs_rename_ref() never derefs, right? We should drop
these two from the synopsis section.
[Appendix]
Often there are two topics, A and B, in flight that merging A into B
(or vice versa) requires changes more than the mechanical merge
needs. If this is a one-shot merge of A into B (or B into A), then
we can just record the evil merge and be done with it, but the same
issue arises if you are merging A into 'seen' first and then later
(possibly after merging other topics on top) B into 'seen'. The
merge of 'B' needs the same evil merge to resolve semantic
conflicts.
As those familiar with how 'seen' works in my tree, reapplying such
evil merges MUST BE automated, or the project will not work at all,
as 'seen' is rebuilt at least twice during the day, or even more
often.
So, what I do is, when I merge 'B' into 'seen' after merging 'A' and
possibly some other topics, I let the rerere database to record the
resolution of textual conflicts and make a commit. The tree
recorded in this commit will not work, due to semantic conflicts. I
create another commit on top of this merge to resolve the semantic
conflict to make the tree work.
Let's take ps/history-drop (A) and ps/setup-drop-global-state (B) as
an easy-to-understand example.
$ git checkout --detach ps/history-drop
$ git merge ps/setup-drop-global-state
This textually merges cleanly, but the result would not compile.
The history-drop added a new call to "is_bare_repository()", while
setup-drop-global-state added an extra parameter to the function.
So a merge-fix prepared on top of this "textually clean but does
not work" merge is created and looks something like this:
diff --git a/builtin/history.c b/builtin/history.c
index 65845e7359..eece221e63 100644
--- a/builtin/history.c
+++ b/builtin/history.c
@@ -1150,7 +1150,7 @@ static int cmd_history_drop(int argc,
* inconsistent repository state. So we first perform a dry-run merge
* here before updating refs.
*/
- if (!is_bare_repository()) {
+ if (!is_bare_repository(repo)) {
ret = find_head_tree_change(repo, &result, &old_head,
&new_head, &head_moves);
if (ret < 0)
And this commit (i.e. a commit on top of the mechanical/textual
merge result that adjusts the non-working merge result into workable
form) is pointed at by refs/merge-fix/ps/setup-drop-global-state.
Rebuilding 'seen' is driven by a script that takes a moral
equivalent of 'git log --first-parent --oneline --reverse
master..seen' and replays each merge on top of what is checked out
(to bootstrap, you would "git checkout -B seen master" and start
there). For each topic branch found in the input, the script
(1) skips if the topic has been merged and move on to the next
topic.
(2) runs "git merge" of the topic, taking resolution by the rerere
database. If this step leaves mechanical/textual conflicts,
the script stops and I'll hand resolve to update my rerere
database, and rerun the script (which will succeed the next
time).
(3) runs "git cherry-pick --no-commit merge-fix/$topic" if such a
ref exists, and if successfull, runs "git commit --amend".
That is how merging ps/setup-drop-global-state into 'seen' that has
already merged ps/history-drop would automatically get the right
evil merge to resolve semantic conflicts.
The renaming of update-ref becomes needed when the order of merging
topics into 'seen' changes. Ideally, these cherry-pickable commits
that are stored under refs/merge-fix hierarchies SHOULD be indexable
by a pair of topic (i.e. "when topic A and topic B first meets, apply
this evil merge"), but this computation is cumbersome to write, so
the above scheme has baked-in assumption that we know which topic
comes later. Once we start merging ps/setup-drop-global-state first
and then ps/history-drop next, we would need
$ git update-ref --rename \
refs/merge-fix/ps/setup-drop-global-state \
refs/merge-fix/ps/history-drop
next prev parent reply other threads:[~2026-06-11 18:47 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-09 21:35 [PATCH] update-ref: add --rename option Junio C Hamano
2026-06-10 21:28 ` [PATCH v2] " Junio C Hamano
2026-06-11 13:05 ` Patrick Steinhardt
2026-06-11 18:47 ` Junio C Hamano [this message]
2026-06-11 21:37 ` [PATCH v3] " Junio C Hamano
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=xmqqwlw4nccr.fsf@gitster.g \
--to=gitster@pobox.com \
--cc=git@vger.kernel.org \
--cc=ps@pks.im \
/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