📝 Originally published (in Japanese) at forge.workstyle.tech.
TL;DR A shared worktree had staged changes across 20 files, with 56 insertions and 1,305 deletions. The files and commit history showed that the changes reflected a state from about three weeks earlier. I preserved the old state on a branch, then used reset --hard and pull --ff-only to update the worktree.
The conclusion first
What remained in the shared working tree after git stash pop was an old state from about three weeks earlier, not new edits. Stashing the staged changes did not update the files in the working tree, so after stashing them, I ran reset --hard and pull --ff-only. I couldn’t determine why the fourth stash attempt was the only one that failed.
Why I Didn’t Mistake the Diff for New Edits
First, I compared the staged content with the files on disk. The index is where changes are held before they go into the next commit. There were no differences between the working tree and the index, and there were no untracked files.
In other words, this wasn’t a case of old changes lingering only in the index. The files on disk themselves were in an old state. The files shown as deleted in the diff were also missing from disk.
So I stopped treating the staged content as new work. All three missing files had been incorporated by two days before the incident. They covered the test harness, usage measurement, and context hints.
The main branch’s HEAD (the name pointing to the current commit) had been stuck for about three weeks since the merge commit on the day of the incident. The size of the diff alone didn’t explain what had happened. By looking at the commit timeline alongside whether the files existed, I could tell this was a leftover from an earlier state, not a recent edit.
Why Stashing Alone Doesn’t Restore the Workspace
If you stash the changes and stop there, the main working tree still contains the old tree. A build from that state will use the old version again, causing the next rollback.
Stashing is a way to preserve the leftovers without losing them. It is separate from restoring the working tree to its current state. Running only one of these steps leaves the shared working tree unusable for the next task.
I decided to treat preserving the old state and updating the main working tree to the latest version as a single recovery process.
How I Preserved the Old Changes and Updated the Main Branch
First, I saved the remaining work as a commit on a named branch, which serves as a marker for a separate line of work. Keeping the backup branch means the contents can be reviewed later. I did not delete it.
Next, I ran reset --hard to align the working tree with HEAD. Then I updated it with pull --ff-only. The result was a 196-commit fast-forward, with no merge commit.
I also kept the stash instead of deleting it. This let me preserve the old contents while moving the main working tree forward.
Why Did stash pop Fail Only on the Fourth Try?
From what I heard later, the same steps succeeded three times that day and failed for the first time on the fourth. I was told that the failure displayed The stash entry is kept. The reason it failed only on the fourth try could not be determined, so I can’t claim to know the cause.
What I could confirm this time was that the shared working tree still contained a state from about three weeks earlier, and that the files on disk were stale as well as the index. I also learned that recovery isn’t complete just by stashing the changes.
“Restoring” by preserving the old state and “updating” by bringing the main working tree up to date can be treated as separate steps. But in a shared working tree, recovery meant completing both. After stashing the changes, I updated the main tree, so we didn’t move on to the next build with the old tree still in place.
I’ll write about the caveats of tools for checking the state in a separate article.
Key takeaways
- Check the diff size with
git diff --shortstat. - Check the worktree and index differences, and look for untracked files.
- Compare file presence, when changes were integrated, and HEAD.
- Preserve old changes on a branch, then run
reset --hardandpull --ff-only. - Keep the branch and stash for later inspection.
Top comments (0)