1. Git 提交树简介

Git 作为版本控制系统,其核心在于其树状结构,其中最核心的是提交树(Commit Tree)。提交树记录了项目的所有历史变更,构成了 Git 的版本控制基础。

在 Git 中,我们常见的操作如创建分支、合并、回滚等,本质上都是在操作提交树。理解提交树的结构和导航方式,有助于我们更高效地使用 Git。

2. Git 的三棵树

Git 在设计上基于三种“树”结构:

  • HEAD 树(提交树):当前分支的最新提交快照
  • 暂存区(Staging Area):待提交的文件变更
  • 工作树(Working Tree):当前工作目录中的文件

这三棵树构成了 Git 的工作流程。我们通常在工作树中修改文件,将变更添加到暂存区,最后通过 git commit 提交到提交树。

示例查看状态:

$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   file3
        new file:   file4

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   file1

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        file2

3. 提交树结构

提交树由一系列的提交节点组成,每个提交都指向其父节点,从而形成一个树状结构。使用 git log 可以查看提交树的拓扑:

$ git log --all --decorate --oneline --graph
* 7763ee6 (tag: v0.1, branch1) branch feature
| * 24bfcf4 (branch2) secondary feature WIP
| * 1a063ad secondary branch modifications
|/
| * 435ac3e (branch3) experimental
|/
* 0ad45b6 branch modifications
| * 9369e35 (HEAD -> master) major modifications
| * 1724387 minor modifications
|/
* 84cbf13 restructuring
* ceb18d3 structuring
* 992361a init commit

这个结构展示了多个分支的提交历史,以及它们的合并关系。理解这种结构有助于我们在合并、变基等操作中避免踩坑。

4. 提交树的导航方式

4.1. 使用提交 ID 和引用(Ref)

每个提交都有一个唯一的 ID(SHA-1 哈希),我们也可以通过引用(如分支、标签)来定位提交:

$ git show-ref
7763ee672134e3fb174c72f03f07ae8ddf8ddf24 refs/heads/branch1
24bfcf443f3b17137af5a2422f5a12f56b7e8a4e refs/heads/branch2
...

这些引用可以用于定位特定提交,例如 git checkout branch1 就是切换到指向该提交的引用。

4.2. 相对引用操作符

Git 提供了相对引用操作符,可以方便地导航提交树:

  • HEAD^:HEAD 的第一个父提交
  • HEAD~2:HEAD 的祖父提交
  • HEAD~2^1:HEAD 的祖父提交的第一个父提交

示例:

$ git show --quiet --oneline HEAD^
1724387 minor modifications

这种方式在查看历史提交时非常有用,尤其是在写脚本或调试时。

4.3. 特殊引用(Special Refs)

Git 还维护了一些特殊的引用,用于记录关键操作:

  • HEAD:当前分支的最新提交
  • ORIG_HEAD:上一次 HEAD 的位置(用于恢复)
  • FETCH_HEAD:最近一次 fetch 的远程提交
  • MERGE_HEAD:正在合并的提交
  • CHERRY_PICK_HEAD:正在 cherry-pick 的提交

这些引用可以避免手动记录提交 ID,尤其在合并、变基等操作中非常有用。

4.4. 查找两个分支的最近共同祖先

使用 git merge-base 可以快速找到两个分支的最近共同祖先:

$ git merge-base branch1 branch2
0ad45b6db8bc17beeece36040392d6690c876162

这在合并冲突、代码审查等场景中非常实用。

4.5. 查找最早共同祖先或其他交汇点

如果想查找两个分支的最早共同祖先,可以使用如下命令组合:

$ diff --unified <(git rev-list --first-parent COMMIT_A) \
                 <(git rev-list --first-parent COMMIT_B) | \
  sed --quiet -e 's/^ //p' | head -1

这个命令利用 diff 找出两个提交历史的差异点,再提取第一个共同提交。

5. 使用 Tig 查看提交树

Tig 是一个终端界面工具,可以更直观地浏览 Git 提交树。

5.1. 安装

$ apt install tig

5.2. 基本使用

$ tig

进入后按 h 可查看帮助,支持多种视图切换:

  • m:主视图(main)
  • d:diff 视图
  • l:log 视图
  • t:tree 视图
  • s:status 视图
  • c:staging 视图

5.3. 查看完整提交树

$ tig --all

可以显示所有分支的提交历史,结构清晰,便于分析分支合并关系。

6. 使用 GitUI 查看提交树

GitUI 是另一个终端界面工具,支持 Linux、macOS 和 Windows。

6.1. 安装

$ curl --silent https://api.github.com/repos/extrawurst/gitui/releases/latest | grep -wo "https.*linux.*gz" | wget -qi -
$ tar xzvf gitui-linux-musl.tar.gz
$ chmod +x gitui
$ install gitui /usr/local/bin

6.2. 基本使用

$ gitui

支持 Tab 切换视图,使用方向键导航,按 Enter 查看提交详情。

6.3. 查看完整提交树

虽然 GitUI 没有直接显示树状结构的选项,但可以在 Log 视图中查看分支提交历史,并通过按键导航查看详细信息。

7. 使用 Lazygit 查看提交树

Lazygit 是目前最流行的 Git 终端界面工具之一。

7.1. 安装

$ LAZYGIT_VERSION=$(curl --silent 'https://api.github.com/repos/jesseduffield/lazygit/releases/latest' | perl -n0we 'print $1 if /"tag_name": "v(.*?)"/;')
$ curl --location --output lazygit.tar.gz "https://github.com/jesseduffield/lazygit/releases/latest/download/lazygit_${LAZYGIT_VERSION}_Linux_x86_64.tar.gz"
$ tar xf lazygit.tar.gz lazygit
$ install lazygit /usr/local/bin

7.2. 基本使用

$ lazygit

界面分为多个面板,支持 Tab 切换、方向键选择、? 查看帮助等。

7.3. 查看完整提交树

在 Status 视图中按 a 键,即可查看所有分支的提交树结构:

$ lazygit status

使用 PageUpPageDown 滚动查看完整提交历史。

8. 总结 ✅

本文介绍了 Git 提交树的基本结构和多种导航方式,包括:

  • 提交树的基本构成
  • 使用提交 ID 和引用操作提交树
  • 使用 git merge-base 查找分支交汇点
  • 使用 Tig、GitUI、Lazygit 等 TUI 工具更直观地查看提交树

建议:对于复杂项目,建议使用 TUI 工具辅助 Git 操作,提升效率并减少出错概率。
注意:不要依赖 Git 的默认行为,尤其是在合并、变基等操作前,务必确认当前 HEAD 和分支状态。
⚠️ 提醒:某些操作(如 resetrebase)会修改提交历史,谨慎使用。


原始标题:Understanding, Inspecting, and Working With the Commit Tree and Other Git Trees