Git interactive rebase: how to move other branches (refs) automatically?
Asked Answered
D

2

7

Sometimes I would like to do a rebase and make sure other refs are updated to the new structure without manual resets or multiple rebases.

Is there a way to do it in one go, so that git rebase would update refs to new commit that are picked and had references before rebase?

One example:

Situation before Rebase:

* abc3... commit3 (branch:a, HEAD) 
* abc2... commit2 
* abc1... commit1 (branch:b)
* abc0... base commmit (branch:master)

Then do a Rebase master -i: pick abc3, abc1, abc2

And the result will look like: (branch:b stays in its own branch of commit)

* abc6... commit2 (branch:a, HEAD) 
* abc5... commit1   
* abc4... commit3
|
| * abc1... commit1 (branch:b)
|/
* abc0... base commmit (branch:master)

What I would like the result to look like: (branch:b is updated to a new commit)

* abc6... commit2 (branch:a, HEAD) 
* abc5... commit1 (branch:b)
* abc4... commit3
* abc0... base commmit (branch:master)
Dionysius answered 23/5, 2022 at 9:37 Comment(5)
Hey @torek is your comment here outdated? I think git's new update-refs feature solves this. (Released in v2.38 just a few months ago)Brunhilda
@DevinRhode: yes, it is outdated, but the update refs feature has a problem as currently implemented, so I don't recommend it yet. (The problem is that if you edit the instruction sheet to remove the update, Git winds up deleting the ref. There's a fix in Git 2.39 and 2.38.2, but until it's been out for at least a few months, preferably a year or so, I won't be recommending it.)Spinose
oof, I called that out in my answer. TY!Brunhilda
I think I noticed a bug with dropping in a update-refs line into your git-rebase-todo. If you have a worktree that has checked out old-pr, in another worktree you have "new-work-on-old-pr", and you are rebasing "new-work-on-old-pr" inside it's respective worktree, dropping a update-refs refs/heads/old-pr line inside your git-rebase-todo file led to the old-pr worktree getting put into a weird state with many "incoming" changes left in the working directory (quite strange dirty git status)Brunhilda
...I am using git-branchless now, a wrapper around git. The bug is probably due to using that.Brunhilda
B
9

Here is the general case of how git 2.39's new update-refs feature helps my rebase flow.

This feature is actually available from git 2.38, but it *had some bugs* - you should update to 2.39.0

First you should enable this feature with this config setting:

git config --global rebase.updateRefs true

While doing an interactive rebase, you can now easily tag any commit with a branch name. That branch name also does not need to already exist. This can make it easy to breakup a large sequence of commits into small PRs.

Given you run git rebase -i <base sha from master>, inside git-rebase-todo file:

pick 1688e8706 First
pick d8e19832e Second
pick b34be474e Third

You can "tag" each commit with a new branch name, and then push those branches, like so:

pick 1688e8706 First
update-ref refs/heads/first

pick d8e19832e Second
update-ref refs/heads/second

pick b34be474e Third
update-ref refs/heads/third

Note: You can mostly ignore the refs/heads/ prefix, everything after that is the actual branch name!

This is how git will edit the file for you, if you already have these branches pointing to these shas. You only have to do this once[1], to tie a branch name to a commit. Then, if you set git config rebase.updateRefs true, git-rebase will then automatically drop these update-ref refs/heads/branch-name lines into your git-rebase-todo file for you.

Then, after each rebase successfully completes, you'll see:

Successfully rebased and updated refs/heads/third
Updated the following refs with --update-refs:
        refs/heads/first
        refs/heads/second
        refs/heads/third

Then you can push each branch like so:

git push --force-with-lease origin first:refs/heads/first
git push --force-with-lease origin second:refs/heads/second
git push --force-with-lease origin third:refs/heads/third
git push --force-with-lease origin $(git branch --show-current):refs/heads/$(git branch --show-current)

Giving commits a stable handle is not radical by itself, but it's a manual step that would be horribly error-prone to do in userland. Now that git has implemented this, watch for higher level tools to give you whole new features!

[1]: If you git checkout second and then make changes, git will not update third. You should generally prefer to stay on the top of your stack, third in this case. If you do make changes to second and eventually want to rebase third, you should run:

git checkout third
git rebase --onto second d8e19832e

NOTE: d8e19832e is actually correct in this case, given my examples, this is the original sha from second in the history of third. Git rebase will take d8e19832e and everything below it, and throw it away, and try to apply the commit "Third" on the new second, and you may need to resolve conflicts along the way.

Brunhilda answered 7/10, 2022 at 3:54 Comment(5)
Good illustration of the feature I mentioned. Upvoted.Ethnogeny
Is there a way to push "previous refs" without specifying them. Currently one need to use a command like git push --atomic origin first second third ?Providence
@Providence I'm not sure exactly what you're looking for - maybe you're wondering if git could introduce a rebase.autoPushUpdatedRefs feature?Brunhilda
@DevinRhode Yes exactly. Could be with an explicit push option. And only on the current branch.Providence
Git-branchless is a wrapper around git that could implement such a feature. I would love this feature too! I've filed issues with ideas on their github before and they were pretty friendly :) github.com/arxanas/git-branchlessBrunhilda
E
1

git rebase would update refs to new commit that are picked and had references before rebase?

Before, no.

But while you are rebasing? Maybe.

With Git 2.38 (Q3 2022), "git rebase -i"(man) learns to update branches whose tip appear in the rebased range with --update-refs option.

See commit 7fefa1b (12 Jul 2022) by Junio C Hamano (gitster).
See commit 4611884, commit aa37f3e, commit 3113fed, commit b3b1a21, commit 89fc0b5, commit 900b50c, commit a97d791, commit d7ce9a2, commit f57fd48, commit aa7f2fd, commit 18ea595, commit 1bec4d1 (19 Jul 2022) by Derrick Stolee (derrickstolee).
(Merged by Junio C Hamano -- gitster -- in commit 3d8e3dc, 01 Aug 2022)

rebase: add --update-refs option

Signed-off-by: Derrick Stolee

When working on a large feature, it can be helpful to break that feature into multiple smaller parts that become reviewed in sequence.
During development or during review, a change to one part of the feature could affect multiple of these parts.
An interactive rebase can help adjust the multi-part "story" of the branch.

However, if there are branches tracking the different parts of the feature, then rebasing the entire list of commits can create commits not reachable from those "sub branches".
It can take a manual step to update those branches.

Add a new --update-refs option to 'git rebase -i'(man) that adds 'update-ref ' steps to the todo file whenever a commit that is being rebased is decorated with that .
At the very end, the rebase process updates all of the listed refs to the values stored during the rebase operation.

Be sure to iterate after any squashing or fixups are placed.
Update the branch only after those squashes and fixups are complete.
This allows a --fixup commit at the tip of the feature to apply correctly to the sub branch, even if it is fixing up the most-recent commit in that part.

This change update the documentation and builtin to accept the --update-refs option as well as updating the todo file with the 'update-ref' commands.
Tests are added to ensure that these todo commands are added in the correct locations.

This change does not include the actual behavior of tracking the updated refs and writing the new ref values at the end of the rebase process.
That is deferred to a later change.

git rebase now includes in its man page:

--update-refs

--no-update-refs

Automatically force-update any branches that point to commits that are being rebased.
Any branches that are checked out in a worktree are not updated in this way.

And:

rebase: update refs from 'update-ref' commands

Signed-off-by: Derrick Stolee

The previous change introduced the 'git rebase --update-refs'(man) option which added 'update-ref <ref>' commands to the todo list of an interactive rebase.

Teach Git to record the HEAD position when reaching these 'update-ref' commands.
The ref/before/after triple is stored in the $GIT_DIR/rebase-merge/update-refs file.
A previous change parsed this file to avoid having other processes updating the refs in that file while the rebase is in progress.

Not only do we update the file when the sequencer reaches these 'update-ref' commands, we then update the refs themselves at the end of the rebase sequence.
If the rebase is aborted before this final step, then the refs are not updated.
The 'before' value is used to ensure that we do not accidentally obliterate a ref that was updated concurrently (say, by an older version of Git or a third-party tool).

Result:

sequencer: notify user of --update-refs activity

Reported-by: Elijah Newren
Signed-off-by: Derrick Stolee

When the user runs 'git rebase -i --update-refs'(man), the end message still says only

Successfully rebased and updated <HEAD-ref>.

Update the sequencer to collect the successful (and unsuccessful) ref updates due to the --update-refs option, so the end message now says

Successfully rebased and updated <HEAD-ref>.
Updated the following refs with --update-refs:
efs/heads/first
efs/heads/third
Failed to update the following refs with --update-refs:
efs/heads/second

git rebase --update-refs(man) would delete references when all update-ref commands in the sequencer were removed, which has been corrected with Git 2.39 (Q4 2022).

See commit 44da9e0 (07 Nov 2022) by Victoria Dye (vdye).
(Merged by Taylor Blau -- ttaylorr -- in commit 35dc2cf, 18 Nov 2022)

rebase --update-refs: avoid unintended ref deletion

Reported-by: herr.kaste
Helped-by: Phillip Wood
Helped-by: Derrick Stolee
Signed-off-by: Victoria Dye
Signed-off-by: Taylor Blau

In b3b1a21 ("sequencer: rewrite update-refs as user edits todo list", 2022-07-19, Git v2.38.0-rc0 -- merge listed in batch #8), the 'todo_list_filter_update_refs()' step was added to handle the removal of 'update-ref' lines from a 'rebase-todo'.
Specifically, it removes potential ref updates from the "update refs state" if a ref does not have a corresponding 'update-ref' line.

However, because 'write_update_refs_state()' will not update the state if the 'refs_to_oids' list was empty, removing all 'update-ref' lines will result in the state remaining unchanged from how it was initialized (with all refs' "after" OID being null).
Then, when the ref update is applied, all refs will be updated to null and consequently deleted.

To fix this, delete the 'update-refs' state file when 'refs_to_oids' is empty.
Additionally, add a tests covering "all update-ref lines removed" cases.

Ethnogeny answered 20/8, 2022 at 18:41 Comment(4)
Yes, but watch out for the bug (fixed in 2.39 and 2.38.2) where update-refs can delete a ref...Spinose
@Spinose Do you have a reference for that bug?Ethnogeny
No, but it's in the release notes. Let's see what git log finds ... aha, 93a7bc8b285eaad24049bc862b4733d595a473f8 Oh, I see you already found it too!Spinose
@Spinose But... that is the same commit content I already mention at the end of this answer. (Cherry-picked for 2.38.2)Ethnogeny

© 2022 - 2024 — McMap. All rights reserved.