4

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)
Tomi Kokkonen
  • 356
  • 2
  • 10
  • No, there isn't. I have wanted such a thing in the past, and wrote some very messy scripts that handled a few special cases *I* cared about, but it's hard to do correctly in general. – torek May 23 '22 at 19:00
  • 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) – Devin Rhode Dec 20 '22 at 21:18
  • 1
    @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.) – torek Dec 21 '22 at 10:46
  • oof, I called that out in my answer. TY! – Devin Rhode Dec 21 '22 at 17:18
  • 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) – Devin Rhode Apr 20 '23 at 03:31
  • ...I am using git-branchless now, a wrapper around git. The bug is probably due to using that. – Devin Rhode Apr 20 '23 at 03:32

2 Answers2

6

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.

Devin Rhode
  • 23,026
  • 8
  • 58
  • 72
  • Good illustration of the feature I mentioned. Upvoted. – VonC Oct 07 '22 at 06:25
  • 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` ? – bric3 Aug 21 '23 at 13:29
  • @bric3 I'm not sure exactly what you're looking for - maybe you're wondering if git could introduce a `rebase.autoPushUpdatedRefs` feature? – Devin Rhode Aug 21 '23 at 16:07
  • @DevinRhode Yes exactly. Could be with an explicit push option. And only on the current branch. – bric3 Aug 22 '23 at 12:12
  • 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 :) https://github.com/arxanas/git-branchless – Devin Rhode Aug 22 '23 at 13:06
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.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • Yes, but watch out for the bug (fixed in 2.39 and 2.38.2) where update-refs can *delete* a ref... – torek Dec 21 '22 at 10:48
  • @torek Do you have a reference for that bug? – VonC Dec 21 '22 at 11:16
  • No, but it's in the release notes. Let's see what `git log` finds ... aha, [`93a7bc8b285eaad24049bc862b4733d595a473f8`](https://github.com/git/git/commit/93a7bc8b285eaad24049bc862b4733d595a473f8) Oh, I see you already found it too! – torek Dec 21 '22 at 11:21
  • @torek But... that is the same commit content I already mention at the end of this answer. (Cherry-picked for 2.38.2) – VonC Dec 21 '22 at 11:24