
A guide to shoehorning Next.js projects into the GitHub Pages ecosystem, with GitHub Actions for building and deploying on push and Imgix for image hosting
Henry Bley-Vroman
Posted
A version of this post 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
A < B(main) \ C - D(feature)Copied
rebasing C
off B
— for example with
shell
git checkout featuregit rebase mainCopied
—or the convenient form git rebase main feature
— results in
A < B(main) < C < D(feature)Copied
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:
A < B(main) \ C(other) - D(feature)Copied
followed by
shell
git rebase main featureCopied
results in
A < B(main) < C' < D'(feature) \ C(other)Copied
With --update-refs
, git-rebase
will also update all branches which start out pointing to commits that then get rebased. Here's the difference:
A < B(main) \ C(other) - D(feature)Copied
followed by
shell
git rebase --update-refs main featureCopied
A < B(main) < C'(other) < D'(feature)Copied
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.
A(main) < B(first) < C(second-requires-first) < D(third-requires-second)Copied
(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
A(main) < B(first) < C(second-requires-first) < D < goes-before-B(third-requires-second)Copied
and want to be to
A(main) < goes-before-B < B(first) < C(second-requires-first) < D(third-requires-second)Copied
Before Git v2.38 my solution was
shell
# Step 1.git rebase --interactive first~Copied
which would result in
A(main) < goes-before-B' < B' < C' < D'(third-requires-second) \ B(first) - C(second-requires-first)Copied
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 --hard
s or git branch -f
s.
With Git v2.38, my solution is
shell
git rebase --interactive --update-refs first~Copied
which results in
A(main) < goes-before-B < B(first) < C(second-requires-first) < D(third-requires-second)Copied
The second real-life 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
shell
git checkout first# hackgit commitCopied
which would leave me at
A(main) < goes-before-B < B < E(first) \ C(second-requires-first) < D(third-requires-second)Copied
To get to the goal
A(main) < goes-before-B < B < E(first) < C(second-requires-first) < D(third-requires-second)Copied
we could run
shell
git rebase --onto first first~ third-requires-secondgit branch -f second-requires-first third-requires-second~Copied
or my preference before git rebase --update-refs
, git rebase --fork-point
:
shell
git rebase --fork-point first second-requires-firstgit rebase --fork-point second-requires-first third-requires-secondCopied
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
shell
git rebase --fork-point --update-refs third-requires-secondCopied
(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 BCopied
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!
A guide to shoehorning Next.js projects into the GitHub Pages ecosystem, with GitHub Actions for building and deploying on push and Imgix for image hosting