{"slug": "git-said-it-succeeded-the-state-said-otherwise", "title": "Git Said It Succeeded. The State Said Otherwise.", "summary": "A developer discovered several misleading success messages in Git, including cases where rerere fails to record resolutions after an abort, autoupdate hides conflict checkpoints, and --autosquash can report success while leaving fixup commits behind. The findings, based on tests with Git 2.39.5, highlight the importance of verifying actual repository state rather than trusting command output.", "body_md": "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`\n\nwith `rerere.autoupdate`\n\nwhen the second one removes the checkpoint the first one earns you. GitHub reports mergeable while the reviewer is still reading stale bytes. `--autosquash`\n\nprints `Successfully rebased and updated`\n\nand leaves a `fixup!`\n\ncommit sitting in history.\n\nCopy the config. The reasoning is why you would leave one of them off.\n\nThis started as a comment on [Sylwia Laskowska's git list](https://dev.to/sylwia-lask/10-git-commands-youll-wish-you-knew-earlier-4fcp). 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.\n\n`rerere`\n\ndoes not learn from `git add`\n\nalone\n\n```\ngit config rerere.enabled true\n```\n\n`rerere`\n\nis 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.\n\n```\nCONFLICT (content): Merge conflict in f.txt\nStaged 'f.txt' using previous resolution.\n```\n\n**Resolve, git add, then abort immediately, and it recorded nothing.** The next attempt conflicts identically. That bit me twice before I understood it.\n\nTwo things are going on and my test could not separate them, so here are both. `git add`\n\ndoes not record the postimage — `git commit`\n\n, `git rebase --continue`\n\n, or an explicit `git rerere`\n\ndo. And `git rebase --abort`\n\nruns `rerere clear`\n\n, which wipes the in-flight metadata anyway.\n\nWhat I could measure, on git 2.39.5: resolve + `git add`\n\n+ abort, and the next attempt conflicts identically. Resolve + `git add`\n\n+ explicit `git rerere`\n\n+ abort, and it replays — **before the rebase goes anywhere.** So \"it learns when the rebase finishes\" is the wrong model either way.\n\nI taught it a resolution in `one.txt`\n\n. Then I created the same conflicting hunk in a completely different file, `two.txt`\n\n, on a different branch. **It replayed the one.txt answer into two.txt.**\n\nOn 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.\n\n`rerere.autoupdate`\n\nI kept seeing them paired. I checked the index in both modes:\n\n``` php\nautoupdate off  ->  UU two.txt    still conflicted, you must read it and git add\nautoupdate on   ->  M  two.txt    fully staged, nothing asks you to look\n```\n\n**Autoupdate off is the checkpoint that catches a replay you did not want.** Leave it off and `rerere`\n\nstill writes the resolution into the file. You just have to look at it before adding.\n\nIf it got it wrong:\n\n```\ngit rerere forget <path>   # drop what it learned\ngit checkout -m <path>     # bring the conflict markers back\n```\n\n`--autosquash`\n\ncan succeed and still leave the fixup in history\n\n```\ngit commit --fixup abc1234\ngit rebase -i --autosquash abc1234~1\n```\n\nFirst marks it. Second reorders and squashes it. The todo list opens already correct.\n\nTwo sharp edges.\n\nBare `git rebase -i --autosquash`\n\nwith no range **fails outright** on a branch with no upstream, so you need the base.\n\nAnd if the range does not reach far enough back to include the target, **it does not warn you.** It prints `Successfully rebased and updated`\n\nand leaves the `fixup!`\n\ncommit sitting in your history.\n\n```\ngit log --oneline | grep fixup!\n```\n\nWorth running after. **The rebase step rewrites history** — `git commit --fixup`\n\njust adds a commit — so the usual rule about rebasing shared commits applies to the second line, not the first.\n\nThis one I did not set up on purpose. I had a stack: `main ← #3 ← #4 ← #2`\n\n. After #3 and #4 merged, I retargeted #2 onto `main`\n\n. GitHub immediately showed `base: main`\n\n, mergeable, clean.\n\n**The new base commit was still not an ancestor of my head.**\n\nI measured it. Head `b1cf109`\n\nversus `main`\n\nat `e6dc136`\n\n: GitHub compare status `diverged`\n\n, **behind by 8**. After `git merge origin/main`\n\n(commit `b4ac271`\n\n): **behind by 0**.\n\n```\ngit fetch origin main\ngit merge-base --is-ancestor origin/main HEAD && echo \"head contains main\" || echo \"STALE — merge first\"\n```\n\nExit 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`\n\n, is what actually brings the commits in.\n\nThose 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.\n\nThat head was what the automated reviewer had to work from. It carried an old implementation I had already replaced on `main`\n\n, 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.\n\nThe fix is one merge, then the ancestor check. `MERGEABLE`\n\ndoes not mean \"this head contains current main.\"\n\n`main`\n\nA link to `/blob/main/path/file`\n\nshows whatever `main`\n\nsays today, and 404s outright if the path later moves. A link to `/blob/<sha>/path/file`\n\nis immutable. Both may return 200 right now. Only one of them still means the same thing next month.\n\nI hit this the same week. `main`\n\nstill 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.\n\nIf you are citing evidence, cite the sha.\n\n`git worktree`\n\ninstead of stashing, if you have untracked files you cannot lose\nStash does not save untracked files unless you remember `-u`\n\n. 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.\n\n```\ngit worktree add -b some/branch /tmp/scratch origin/main\n```\n\nWhen you are done: `git worktree remove`\n\n. If you have ever needed to fix one branch while another is mid-edit, this is the command.\n\nSame disease as the rest of the list, incidentally. `git stash`\n\nreports success and your untracked file is simply not in it.\n\n`--autosquash`\n\nprints success while leaving a `fixup!`\n\nbehind. A retargeted PR reports `MERGEABLE`\n\nwhile its head is eight commits behind the branch it now claims as base. `rerere.autoupdate`\n\nstages the replay cleanly and removes the unmerged path you would otherwise have been forced to look at.\n\n**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!`\n\n, ancestry for the stale head, the index for `UU`\n\nversus `M`\n\n.\n\nWhich is the actual lesson, and it is not \"check the index\":\n\n**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.\n\n*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.*\n\n*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.*", "url": "https://wpnews.pro/news/git-said-it-succeeded-the-state-said-otherwise", "canonical_source": "https://dev.to/kenielzep97/git-said-it-succeeded-the-state-said-otherwise-34ka", "published_at": "2026-08-30 01:44:59+00:00", "updated_at": "2026-08-30 02:22:17.171353+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Git", "Sylwia Laskowska"], "alternates": {"html": "https://wpnews.pro/news/git-said-it-succeeded-the-state-said-otherwise", "markdown": "https://wpnews.pro/news/git-said-it-succeeded-the-state-said-otherwise.md", "text": "https://wpnews.pro/news/git-said-it-succeeded-the-state-said-otherwise.txt", "jsonld": "https://wpnews.pro/news/git-said-it-succeeded-the-state-said-otherwise.jsonld"}}