---
title: "Git 提交突然消失：用 reflog 找回改写前的分支"
date: 2026-11-13
slug: git-reflog-recover-commit
canonical: https://kernelranch.com/blog/2026/11/git-reflog-recover-commit
markdown_url: https://kernelranch.com/blog/2026/11/git-reflog-recover-commit.md
tags: ["git", "recovery", "debugging"]
---执行 git commit --amend 后突然发现原提交还有一份需要保留的内容。当前分支只显示新提交，但本地 reflog 可能保存分支移动前的提交 ID。先确认旧提交的内容，然后为它创建救援分支。

## 1. 在临时仓库模拟一次 amend

保存为 reflog_lab.py，运行 python reflog_lab.py。需要 Git 在 PATH 中。脚本只改动临时仓库。

```python
from pathlib import Path
import subprocess
from tempfile import TemporaryDirectory

with TemporaryDirectory() as tmp:
    def git(*args):
        return subprocess.check_output(["git", *args], cwd=tmp, text=True).strip()

    git("init", "-q")
    git("config", "core.logAllRefUpdates", "true")
    git("config", "user.name", "Reflog Lab")
    git("config", "user.email", "lab@example.com")
    git("config", "commit.gpgsign", "false")
    git("config", "core.hooksPath", str(Path(tmp) / "empty-hooks"))
    path = Path(tmp) / "notes.txt"
    path.write_text("first draft", encoding="utf-8")
    git("add", "notes.txt")
    git("commit", "-qm", "first draft")
    original = git("rev-parse", "HEAD")

    path.write_text("revised draft", encoding="utf-8")
    git("add", "notes.txt")
    git("commit", "--amend", "-qm", "revise draft")
    revised = git("rev-parse", "HEAD")
    assert original != revised
    candidate = git("rev-parse", "HEAD@{1}")
    assert candidate == original
    assert git("show", f"{candidate}:notes.txt") == "first draft"

    git("branch", "rescue/draft", candidate)
    assert git("show", "rescue/draft:notes.txt") == "first draft"
    assert git("show", "HEAD:notes.txt") == "revised draft"
    print("reflog: old commit found")
    print("rescue/draft: first draft")
    print("current HEAD: revised draft")
```

本地实测输出如下。

```text
reflog: old commit found
rescue/draft: first draft
current HEAD: revised draft
```

## 2. 在实际仓库恢复已有提交

先运行 `git reflog --date=iso`，根据操作时间与 amend、reset 或 rebase 记录找候选。查看特定分支的移动记录时，使用 `git reflog show <分支>`。演练中的 `HEAD@{1}` 刚好指向上一次提交，平时的 checkout、reset 等操作会不断推进编号，因此找到候选后应抄下完整 ID。

| 操作 | 命令 |
|---|---|
| 检查候选提交及改动 | `git show <提交ID>` |
| 建立可以长期引用的分支 | `git branch rescue/draft <提交ID>` |
| 比较救援分支和当前版本 | `git diff HEAD rescue/draft` |

救援分支建立后，可以选择从旧版本恢复特定文件，或把需要的改动合入当前分支。reflog 属于本地仓库，重新克隆通常无法提供旧机器上的操作记录。记录可能过期，已经被清理的对象无法用此法找回。未进入提交的编辑应先查看编辑器历史和备份。

## 小结

先从 reflog 找提交 ID，再从该 ID 校验内容并创建救援引用。恢复过程中可以保留当前分支，等 diff 确认后再决定如何合入。

## 数据来源与检索时间

检索时间 2026-11-13。

| 来源 | URL | 对应内容 |
|---|---|---|
| Git reflog 手册 | https://git-scm.com/docs/git-reflog | 引用历史、时间选择器与过期行为 |

## 核验说明

- Git 2.54.0.windows.1 实测 amend 后按完整 ID 恢复旧提交。案例覆盖已提交的文件。
