bigywater.tech
Git · 58

log 的 --graph 与历史简化

为什么 git log -- 某个文件 有时候少了一些改过它的提交?这是历史简化。这篇造一个两个分支都改了同一个文件、先后合并的历史,看 --full-history、--simplify-merges、--sparse 各自的结果,再学会读 --graph 的 ASCII 图。

3 vs 5
默认的 git log -- f 与 --full-history 显示的提交数;默认少了 Y1
TREESAME
合并提交的文件与某个 parent 完全一样,git 就只沿那个 parent 往下走
*-.
章鱼合并在 --graph 里的画法

实验历史

实测 · 本机执行输出
  $ git log --graph --format='%s' | sed -E 's/^/  /'
    * M3 改 hy
    * M2 改 f 为 3
    *   合并 y
    |\
    | * Y2 改 hy
    | * Y1 也把 f 改成 2
    * |   合并 x
    |\ \
    | * | X2 改 gx
    | * | X1 改 f 为 2
    | |/
    * / M1 改 g
    |/
    * R 起点

x、y 两个分支各自把 f 改成同样的值 2,先后合并进 main,之后 main 上又有 M2、M3。这是“两条分支做了相同改动”的典型情形。

历史简化

实测 · 本机执行输出
  $ git log --format='%s' -- f
  M2 改 f 为 3
  X1 改 f 为 2
  R 起点
  (X1 和 Y1 都把 f 改成了 2,但默认只显示 X1:合并 y 的提交的 f 与它的两个 parent 都一样,git 只沿第一个 parent 走,Y 那条分支被整条丢掉了。)

给 git log 加路径后,git 会简化历史:没改动这些路径的提交不显示;遇到合并提交,如果它的路径内容与某一个 parent 完全一样(TREESAME),就只沿那个 parent 走,另一条分支整条被忽略。这能让输出更简洁,但会隐藏掉其它分支里同样改过文件的提交。

--full-history

实测 · 本机执行输出
  git log                                    -- f  ( 3 个): M2 改 f 为 3 | X1 改 f 为 2 | R 起点
  git log --full-history                     -- f  ( 5 个): M2 改 f 为 3 | 合并 x | Y1 也把 f 改成 2 | X1 改 f 为 2 | R 起点
  git log --full-history --simplify-merges   -- f  ( 5 个): M2 改 f 为 3 | 合并 y | Y1 也把 f 改成 2 | X1 改 f 为 2 | R 起点
  git log --full-history --sparse            -- f  (10 个): M3 改 hy | M2 改 f 为 3 | 合并 y | 合并 x | M1 改 g | Y2 改 hy | Y1 也把 f 改成 2 | X2 改 gx | X1 改 f 为 2 | R 起点

--full-history 不简化合并,所有改动过路径的提交都显示(同时还显示相关的合并)。--simplify-merges 在此基础上去掉对这些路径来说多余的合并;--sparse 把无关的提交也留下。

--graph

实测 · 本机执行输出
  $ git log --graph --full-history --format='%s' -- f | sed -E 's/^/  /'
    * M2 改 f 为 3
    *   合并 y
    |\
    | * Y1 也把 f 改成 2
    * |   合并 x
    |\ \
    | |/
    |/|
    | * X1 改 f 为 2
    |/
    * R 起点

--graph 用 ASCII 把提交图画出来。与 --full-history 搭配,能看清每条改过文件的支线。

其它选项

实测 · 本机执行输出
  $ echo '--first-parent:'; git log --first-parent --format='%s' | tr '\n' '|'; echo; echo '--ancestry-path R..main:'; git log --ancestry-path --format='%s' $(git rev-list --max-parents=0 HEAD)..main | tr '\n' '|'; echo; echo '--ancestry-path X1..main:'; git log --ancestry-path --format='%s' $(git rev-parse x~1)..main | tr '\n' '|'; echo; echo '--simplify-by-decoration:'; git log --simplify-by-decoration --format='%s (%D)' | sed -E 's/HEAD -> //' | tr '\n' '|'; echo
  --first-parent:
  M3 改 hy|M2 改 f 为 3|合并 y|合并 x|M1 改 g|R 起点|
  --ancestry-path R..main:
  M3 改 hy|M2 改 f 为 3|合并 y|合并 x|M1 改 g|Y2 改 hy|Y1 也把 f 改成 2|X2 改 gx|X1 改 f 为 2|
  --ancestry-path X1..main:
  M3 改 hy|M2 改 f 为 3|合并 y|合并 x|X2 改 gx|
  --simplify-by-decoration:
  M3 改 hy (main)|M2 改 f 为 3 (tag: v1)|合并 y ()|Y2 改 hy (y)|X2 改 gx (x)|R 起点 ()|

--first-parent 只沿第一个 parent;--ancestry-path A..B 只显示 A 的后代同时又是 B 的祖先的提交;--simplify-by-decoration 只留带分支/标签的提交以及保持图连通的合并。

读图

实测 · 本机执行输出
  $ git log --graph --format='%s' | sed -E 's/^/  /'
    *-.   章鱼合并 p q r
    |\ \
    | | * r1
    | * | q1
    | |/
    * / p1
    |/
    * base

* 是提交,| 是延续的线,\\ 与 / 表示分出与合回,*-. 是有三个 parent 的章鱼合并。

分步演示

log 的 --graph 与历史简化
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。历史由脚本构造(X、Y 两个分支做完全相同的 f 改动,先后合并);提交时间固定;各选项的提交数来自 git log --format=%s 的行数
    • 没有验证:--full-history 与 --simplify-merges 在更复杂的真实历史上的区别;--show-pulls;--follow 与历史简化的关系;git log -L;--date-order、--topo-order 对 --graph 画法的影响;--graph 的颜色
    京ICP备2026067502号-1京公网安备11010502063487号