A collection of components for displaying Git information in your prompt
A version of this article appeared on viget.com
In version Git v2.38 (released Oct 3 2022), git-rebase learned a new --update-refs option. With --update-refs, rebasing will āAutomatically force-update any branches that point to commits that are being rebasedā (docs).
A standard rebase results in up to one branch pointing at a different commit than it did before
textA < B(main)\C - D(feature)
textA < B(main)\C - D(feature)
rebasing C off B ā for example with
shellgit checkout featuregit rebase main
shellgit checkout featuregit rebase main
āor the convenient form git rebase main featureā results in
textA < B(main) < C < D(feature)
textA < B(main) < C < D(feature)
Not much room for improvement there. But what if there are intermediate branches between the specified branchās fork point (A in the above example) and the branch youāre rebasing?
Hereās what happens in a multi-branch situation with a standard rebase:
textA < B(main)\C(other) - D(feature)
textA < B(main)\C(other) - D(feature)
followed by
shellgit rebase main feature
shellgit rebase main feature
results in
textA < B(main) < C' < D'(feature)\C(other)
textA < B(main) < C' < D'(feature)\C(other)
With --update-refs, git-rebase will also update all branches which start out pointing to commits that then get rebased. Hereās the difference:
textA < B(main)\C(other) - D(feature)
textA < B(main)\C(other) - D(feature)
followed by
shellgit rebase --update-refs main feature
shellgit rebase --update-refs main feature
textA < B(main) < C'(other) < D'(feature)
textA < B(main) < C'(other) < D'(feature)
Iām excited about this enhancement because of two scenarios I run into:
The first real-life scenario is during development. I sometimes build several branches upon each other. Maybe they are for dependent features; maybe itās one large feature, and Iām splitting it up to make code review more feasible.
textA(main) < B(first) < C(second-requires-first) < D(third-requires-second)
textA(main) < B(first) < C(second-requires-first) < D(third-requires-second)
(Iām keeping each branch to only one commit for legibility.)
Iām working at third-requires-second and make a change that belongs in first. Iām at
textA(main) < B(first) < C(second-requires-first) < D < goes-before-B(third-requires-second)
textA(main) < B(first) < C(second-requires-first) < D < goes-before-B(third-requires-second)
and want to be to
textA(main) < goes-before-B < B(first) < C(second-requires-first) < D(third-requires-second)
textA(main) < goes-before-B < B(first) < C(second-requires-first) < D(third-requires-second)
Before Git v2.38 my solution was
shell# Step 1.git rebase --interactive first~
shell# Step 1.git rebase --interactive first~
which would result in
textA(main) < goes-before-B' < B' < C' < D'(third-requires-second)\B(first) - C(second-requires-first)
textA(main) < goes-before-B' < B' < C' < D'(third-requires-second)\B(first) - C(second-requires-first)
Git surgery is required to point first to B' and to point second-requires-first to C'. You probably have your workflow. Maybe git-log or a Git graph UI to figure what B' and C' are relative to third-requires-second, or to look up their IDs, and then some git reset --hards or git branch -fs.
With Git v2.38, my solution is
shellgit rebase --interactive --update-refs first~
shellgit rebase --interactive --update-refs first~
which results in
textA(main) < goes-before-B < B(first) < C(second-requires-first) < D(third-requires-second)
textA(main) < goes-before-B < B(first) < C(second-requires-first) < D(third-requires-second)
The second real-life scenario is during the review/approval phase. Say I put these branches up for review at the same time (this isnāt a standard practice on my team, but our process is okay with it and it comes up from time to time).
Say a colleague requests a change in the first pull request. In the past, I might have added a commit to that branch
shellgit checkout first# hackgit commit
shellgit checkout first# hackgit commit
which would leave me at
textA(main) < goes-before-B < B < E(first)\C(second-requires-first) < D(third-requires-second)
textA(main) < goes-before-B < B < E(first)\C(second-requires-first) < D(third-requires-second)
To get to the goal
textA(main) < goes-before-B < B < E(first) < C(second-requires-first) < D(third-requires-second)
textA(main) < goes-before-B < B < E(first) < C(second-requires-first) < D(third-requires-second)
we could run
shellgit rebase --onto first first~ third-requires-secondgit branch -f second-requires-first third-requires-second~
shellgit rebase --onto first first~ third-requires-secondgit branch -f second-requires-first third-requires-second~
or my preference before git rebase --update-refs, git rebase --fork-point:
shellgit rebase --fork-point first second-requires-firstgit rebase --fork-point second-requires-first third-requires-second
shellgit rebase --fork-point first second-requires-firstgit rebase --fork-point second-requires-first third-requires-second
followed by pushing first and force pushing (--with-lease!) second-requires-first and third-requires-second.
With git rebase --update-refs we can instead run one command
shellgit rebase --fork-point --update-refs third-requires-second
shellgit rebase --fork-point --update-refs third-requires-second
(Or git rebase --onto first first~ third-requires-second. Shorter⦠but requires more knowledge of the context.)
The āduring developmentā scenario doesnāt only happen during development, and the āduring reviewā scenario doesnāt only happen during review. But without git rebase --update-refs it is reasonable to think of them as closely related but distinct problems. But with git rebase --update-refs the solutions are identical š By adopting git rebase --update-refs you reduce the set of branch management problems you need to have solutions for.
You can use git rebase --update-refs today. If youāre locked into an outdated version because youāre using a copy of Git that ships with your OS, youāll need to switch to a copy you manage yourself (for example, Homebrew users can brew install git).
--update-refs is smart about squashed commits! Say you want to make a fix to the first of several stacked branches
shell# A(main) < goes-before-B < B(first) < C(second-requires-first) < D(third-requires-second)# Need to change Bgit checkout third-requires-second# hackgit commitgit rebase -i first~# in the interactive rebase todo editor# make the new commit a 'fixup' commit# and move it follow B
shell# A(main) < goes-before-B < B(first) < C(second-requires-first) < D(third-requires-second)# Need to change Bgit checkout third-requires-second# hackgit commitgit rebase -i first~# in the interactive rebase todo editor# make the new commit a 'fixup' commit# and move it follow B
followed by pushes.
Bonus bonus
Instead of git commit that could be git commit --fixup=first. And with rebase.autoSquash set to true, Git will move the fixup commit to immediately following its target for you. More efficiency⦠but thatās beyond the scope of this article. checkout š„ the links!
Git Prompt Kit: Configurable, Fast Git Status Components For Custom Zsh Themes
A collection of components for displaying Git information in your prompt
Hometown: A Dynamic, Highly Configurable Git-Focused Zsh Theme
A fast zsh prompt that packs in a lot of Git status info
Tools, Services, & Office Equipment I Use
Preferences preferences preferences