OFFSET偏移
← All articlesOFFSET / FIELD NOTESNO. 097

A Git scenario handbook: correcting, undoing, and recovering

Choose a recovery path based on the current operation and what data has been saved, without compounding data loss.

English translation · Code and commands follow the Chinese original.

IN THESE READING PATHSGit: from snapshots to collaboration and recovery ↗
About 5 min readStart reading

Recovery depends on whether content has been saved as objects, whether references and reflogs remain, and the state of the current operation. The presence of .git does not guarantee that uncommitted edits can be recovered.


Category 1: Commit fixes#

1. You just committed and realize you missed a file or wrote the wrong commit message

  • Symptom: You notice one second too late.
  • Remedy:
    git add <forgotten_file>
    git commit --amend --no-edit  # 合并到上一次提交,不改信息
    # 或者
    git commit --amend -m "新的正确信息"

2. You committed a large file that should not be there. How do you remove it completely?

  • Symptom: The .git directory suddenly balloons. Even after deleting the file, it still occupies space in history.
  • Remedy: First create an independent backup, then verify the scope of the history cleanup in a fresh clone. Git’s official documentation discourages filter-branch; git filter-repo is a better fit. For example, the following rewrites history containing that path:
# 在专用于历史清理的新克隆中,路径为仓库相对路径占位符
git filter-repo --path <large_file_path> --invert-paths

The following is retained only to explain a command from an older tutorial. Its arguments do not represent a recommended modern workflow, and it should not be run directly in a shared repository: bash git filter-branch --force --index-filter \ 'git rm --cached --ignore-unmatch <large_file_path>' \ --prune-empty --tag-name-filter cat -- --all After cleanup, check the branches, tags, and scope of affected objects first, and coordinate with collaborators to reclone or rebuild references. The older tutorial’s git push origin --force --all broadens what will be overwritten and must not be treated as an automatic next step. Before publishing, scope each ref explicitly and check server-side protections. Include each historical filename in the cleanup; filtering a single path may not cover previous names. If a secret has leaked, revoke or rotate it first; rewriting history cannot recall other people’s clones.


Category 2: Branch rescue#

3. You have spent hours writing code on the wrong branch but have not committed yet

  • Symptom: You meant to work on the feature branch but wrote a lot of code on master instead.
  • Remedy:
    git stash -u             # 1. 保存已追踪及未追踪内容(忽略文件另行处理)
    git switch <right_branch> # 2. 切换到正确分支
    git stash pop            # 3. 恢复现场

4. You need to fix an urgent bug halfway through development, but do not want to commit unfinished work

  • Symptom: The code does not compile, and you do not want to leave a throwaway commit.
  • Remedy: Use Stash (see Git 02).
    git stash -u             # 保存当前进度(含新文件)
    # ... 修 Bug ...
    git stash pop            # 回来继续写

Category 3: Undo & recovery#

5. You ran git reset --hard and lost all your code

  • Symptom: Your heart stops. That was a week’s work.
  • Remedy: Use reflog (see Git 04).
    git reflog               # 找到 reset 之前的那个哈希值 (例如 abc1234)
    git branch recovery/saved-tip abc1234 # 先为确认过的提交建立恢复引用

6. You want to undo code already pushed to the remote

  • Scenario A: The history rewrite has been coordinated, and replacing the remote branch tip is permitted.
    • Check the current state and make a backup first. The original tutorial’s git reset --hard HEAD~1 discards local changes, and git push -f overwrites remote history; neither should be treated as a routine undo step. If you only need to undo the last commit while keeping the files, use git reset --soft HEAD~1. When publishing rewritten history, use --force-with-lease with a scoped target and an explicit expected value (see Remote collaboration); you must still check remote permissions and the state of collaborators’ work.
  • Scenario B: A public branch where force pushes are prohibited.
    • Remedy: Use revert.
    git revert <commit_hash> # 产生一个“相反”的新提交,抵消之前的修改
    git push

Category 4: Sync & conflicts#

7. Pull fails and reports conflicts. How do you resolve them cleanly?

  • Symptom: Red warnings fill the screen, and your code is full of <<<<<<< HEAD.

  • Remedy:

    1. Manually edit the conflicting files and keep the correct code.
    2. git add <conflicted_file>.
    3. git merge --continue or git commit.
  • ** Abort the merge**: git merge --abort

    • Effect: Attempts to reconstruct the pre-merge state. If you had uncommitted changes when the merge began and edited them further, complete recovery may not be possible.
    • Cost: All the code you wrote to resolve conflicts manually will be lost! It is like turning back time and pretending the merge never happened. If you have already spent an hour resolving conflicts, be very careful not to run this command accidentally.
    • What next?: Check git status to confirm the operation state and retained changes, then choose to merge again, rebase according to your collaboration policy, or keep a separate branch. Pull may also use rebase; in that case, use git rebase --continue / git rebase --abort to continue or abort, rather than mixing in merge commands. git reset --hard origin/<branch> discards working-tree changes and moves the current branch. It does not fetch automatically and must not be used to bypass conflicts.

8. How do you sync a fork with the original author’s updates?

  • Symptom: Your repository is months behind the original author’s.
  • Remedy:
    git remote add upstream https://example.com/upstream/project.git
    git fetch upstream
    git switch master
    git merge upstream/master # 或者 rebase

The ultimate survival rules#

  1. Save promptly: WIP commits or an appropriate stash can preserve content, but reflogs expire and are not a permanent backup.
  2. Understand HEAD: Know where you are: which branch and which commit.
  3. Inspect before recovering: Once you find a candidate commit, create a recovery reference and inspect it first; do not repeatedly experiment with hard resets.


Three limits of recovery

reset --hard overwrites tracked content and may also overwrite or delete untracked files blocking target paths; it is not a general command for removing all untracked files. Committed objects can be recovered through references or reflogs while they remain, but uncommitted edits may never have been stored as objects. By default, reachable reflog entries expire after about 90 days and unreachable entries after about 30 days; the actual window depends on configuration, maintenance, and object pruning.

On shared branches, the usual approach is to preserve history by adding an inverse commit with git revert. Reverting a merge commit also requires selecting the correct mainline parent and understanding the effect on later merges. Before using any continue, abort, or cleanup command, make sure it matches the operation shown by status.

References & further reading

  1. Git reset ↗
  2. Git reflog ↗
  3. Git revert ↗
  4. Aborting a Git merge ↗
  5. Risks of Git’s older filtering tool ↗
  6. git-filter-repo author documentation ↗

Adapted from technical notes. Examples use generic names.

Continue along a reading path

Git: from snapshots to collaboration and recovery

  1. 01Git's underlying model: objects, snapshots, and references
  2. 02From the first add to undoing a change.
  3. 03Git branches: merging, conflicts, and saving work in progress
  4. 04Remote collaboration with Git: fetching, tracking, and two remotes
  5. 05Advanced Git: rebasing, cherry-picking, and bisecting
  6. 06A Git scenario handbook: correcting, undoing, and recovering
CONTINUE READING / NEXT ARTICLEsops principles: hybrid encryption and recipients

Understand the boundaries of encrypted version control, data keys, and authorization for multiple recipients.