I keep finding git behaviours the same way: set up the case, run the command, then check the state I actually cared about instead of trusting the success message. Guides pair rerere.enabled
with rerere.autoupdate
when the second one removes the checkpoint the first one earns you. GitHub reports mergeable while the reviewer is still reading stale bytes. --autosquash
prints Successfully rebased and updated
and leaves a fixup!
commit sitting in history.
Copy the config. The reasoning is why you would leave one of them off.
This started as a comment on Sylwia Laskowska's git list. She asked me to turn it into a post. The first four items are from ordinary rebasing. The rest I measured this week on a stacked-PR repo.
rerere
does not learn from git add
alone
git config rerere.enabled true
rerere
is reuse recorded resolution. It remembers how you resolved a conflict and replays it when the same one appears. On a long rebase that is resolving something once instead of five times.
CONFLICT (content): Merge conflict in f.txt
Staged 'f.txt' using previous resolution.
Resolve, git add, then abort immediately, and it recorded nothing. The next attempt conflicts identically. That bit me twice before I understood it.
Two things are going on and my test could not separate them, so here are both. git add
does not record the postimage — git commit
, git rebase --continue
, or an explicit git rerere
do. And git rebase --abort
runs rerere clear
, which wipes the in-flight metadata anyway.
What I could measure, on git 2.39.5: resolve + git add
-
abort, and the next attempt conflicts identically. Resolve +
git add -
explicit
git rerere -
abort, and it replays — before the rebase goes anywhere. So "it learns when the rebase finishes" is the wrong model either way.
I taught it a resolution in one.txt
. Then I created the same conflicting hunk in a completely different file, two.txt
, on a different branch. It replayed the one.txt answer into two.txt.
On a rebase that is usually what you want. It is not always what you want. If the right answer differs between those two places, it will quietly give you the first one.
rerere.autoupdate
I kept seeing them paired. I checked the index in both modes:
autoupdate off -> UU two.txt still conflicted, you must read it and git add
autoupdate on -> M two.txt fully staged, nothing asks you to look
Autoupdate off is the checkpoint that catches a replay you did not want. Leave it off and rerere
still writes the resolution into the file. You just have to look at it before adding.
If it got it wrong:
git rerere forget <path> # drop what it learned
git checkout -m <path> # bring the conflict markers back
--autosquash
can succeed and still leave the fixup in history
git commit --fixup abc1234
git rebase -i --autosquash abc1234~1
First marks it. Second reorders and squashes it. The todo list opens already correct.
Two sharp edges.
Bare git rebase -i --autosquash
with no range fails outright on a branch with no upstream, so you need the base.
And if the range does not reach far enough back to include the target, it does not warn you. It prints Successfully rebased and updated
and leaves the fixup!
commit sitting in your history.
git log --oneline | grep fixup!
Worth running after. The rebase step rewrites history — git commit --fixup
just adds a commit — so the usual rule about rebasing shared commits applies to the second line, not the first.
This one I did not set up on purpose. I had a stack: main ← #3 ← #4 ← #2
. After #3 and #4 merged, I retargeted #2 onto main
. GitHub immediately showed base: main
, mergeable, clean.
The new base commit was still not an ancestor of my head.
I measured it. Head b1cf109
versus main
at e6dc136
: GitHub compare status diverged
, behind by 8. After git merge origin/main
(commit b4ac271
): behind by 0.
git fetch origin main
git merge-base --is-ancestor origin/main HEAD && echo "head contains main" || echo "STALE — merge first"
Exit 1 is the stale case. GitHub's "Change base" button is not "Update branch." Change base retargets what the PR is compared against. Update branch, or git merge origin/main
, is what actually brings the commits in.
Those SHAs are from a private stack, so this one is a field note rather than something you can clone and rerun. The check itself is two lines and works anywhere.
That head was what the automated reviewer had to work from. It carried an old implementation I had already replaced on main
, and I burned a cycle chasing a finding about code that only still existed on that stale branch — before I thought to check whether the branch contained what I thought it contained.
The fix is one merge, then the ancestor check. MERGEABLE
does not mean "this head contains current main."
main
A link to /blob/main/path/file
shows whatever main
says today, and 404s outright if the path later moves. A link to /blob/<sha>/path/file
is immutable. Both may return 200 right now. Only one of them still means the same thing next month.
I hit this the same week. main
still carried an old write-up while the corrected file only existed on a branch. I published the SHA-pinned URL so a reader could open the bytes I was citing, not whatever landed on default later.
If you are citing evidence, cite the sha.
git worktree
instead of stashing, if you have untracked files you cannot lose
Stash does not save untracked files unless you remember -u
. I had an untracked scratch file I needed to keep through a week of branch hops. Worktrees solved it without the dance: each branch in its own directory, one object store, untracked files stay put.
git worktree add -b some/branch /tmp/scratch origin/main
When you are done: git worktree remove
. If you have ever needed to fix one branch while another is mid-edit, this is the command.
Same disease as the rest of the list, incidentally. git stash
reports success and your untracked file is simply not in it.
--autosquash
prints success while leaving a fixup!
behind. A retargeted PR reports MERGEABLE
while its head is eight commits behind the branch it now claims as base. rerere.autoupdate
stages the replay cleanly and removes the unmerged path you would otherwise have been forced to look at.
In each case the message was true and the state was not what I assumed. And each one needed a different thing checked: history for the leftover fixup!
, ancestry for the stale head, the index for UU
versus M
.
Which is the actual lesson, and it is not "check the index":
A success message tells you the command completed according to its own contract. It does not tell you the repository now satisfies the condition you actually cared about. Those are different sentences. Go check the one you cared about.
Measured on git 2.39.5 locally; the GitHub compare behaviour is as of this week. Items 1–4 are reproducible on any repo in about two minutes. Item 5 is a field note from a private stack.
This post exists because Sylwia Laskowska wrote a git list good enough that I went and tested the rebase section against it, and then told me the comment should be its own post. She was right, and I would not have written it otherwise. Go read hers.