我不清楚您是否需要从某个特定提交中向后或向前查看。
不过,所有这一切的关键是要认识到 Git 根本不关心分支 名称。 Git 关心的是提交图。每当您处理此类问题时,您都应该绘制图表。
以下是一些示例图。一轮o 节点代表提交。 * 是您手中拥有其 ID 的提交,我给出了“可能有趣”的提交单字母名称:
...--A--o--*--o--B--...--o <-- branchname
^ ^
2 before 2 after
这个图表很简单。提交A 在提交* 之前出现两个,所以如果你有* 的哈希——假设它是1234567——你可以写1234567~2。 ~2 的意思是“回去两个第一父母”(见下文)。查找提交 B 更加困难(见下文)。
但我们可能会遇到这样的混乱:
o--D--o <-- branch1
/
/ E--o <-- branch2
A--o / / \
\ / / \
...--B--o--*--o--F-...-o <-- branch3
\ /
o--C----o
这里,A 和 B 的提交都是在* 之前的两个步骤,而所有C、D、E 和F 都是两个步骤之后。
在 Git 中,查找“之前”的提交要容易得多,因为 Git 的内部箭头都指向“向后”。分支名称,它们都在右侧,因此指向分支上的 latest 提交——这实际上是分支在 Git 中的工作方式:名称指向 tip 提交,并且提示提交向后(在这些图中向左)指向更早的提交——简单地让 Git 开始查找提交。
给定一个提交,Git 会按照向后箭头指向该提交的父级。这里* 是一个合并提交,将A--o 和B--o 行放在一起。
* 的两个父级的顺序并未显示在图表本身中,但假设 A 行实际上是通过 second 父级找到的。假设* 又是1234567。然后1234567~2 是提交B(不是A),因为~ 只跟随first 父母。要查找A,您可以写1234567^2(在* 之前命名上面的o),然后添加^1 或~1 以返回一个父级到A。
换句话说,两个提交现在是1234567~2 和1234567^2~1。 (有更多的写法。)但最简单的方法是运行git log --graph --oneline 1234567 并观察图形——Git 会垂直绘制它,但你会看到两条线从* 出来,这会让你找到两者A 和 B。
如果有两种以上的方法可以达到两个以上“后退两步”的提交,您会在图表中看到这一点,就像我们对许多“前进两步”的提交所做的那样。但是......要找到“向前两步”的提交明显要困难得多。
同样,Git 从最新的(我们图纸中最右边的)tip 提交开始并向后工作。我画了三件东西,它们最终合并到 branch3,1 的提示中,但请注意,提交 D(这也是向前迈出的两步)only 可以通过以下方式找到从branch1向后搜索。
没有“向前搜索”
Git 无法从提交中“向前搜索”或“向前一步”。所有向前移动的操作(有一个!)实际上都是通过向后移动来工作的。基本上,我们从所有可能的起点开始,即所有分支名称/提示。2然后我们有Git list everything 返回我们想要的提交,当且仅当它们是我们正在查看的提交的 后代3 时打印这些提交的哈希 ID。
打印所有这些 ID 的命令是 git rev-list(实际上只是 git log,4 被告知只打印提交的哈希 ID 而不是记录提交。)flag 上面写着“给我后代”是--ancestry-path,它必须结合更多的信息。特别是,我们必须告诉 Git 哪个提交是“有趣的”提交,并且我们使用 ^ 前缀(这次不是后缀)来做到这一点:
git rev-list --ancestry-path ^1234567 --branches
^1234567 告诉 Git 哪个提交停止了图遍历 并且 将打印的修订集限制为 1234567 的后代。 --branches 告诉 Git,像往常一样,从所有分支提示中开始搜索。 (我们也可以添加--tags,或使用--all 表示所有引用:所有分支、所有标签、存储(如果有的话)以及其他可能存在的内容。但是,--branches ,或--branches --tags,可能就是你想要的。)
现在,git rev-list 的问题在于它只会溢出所有修订 ID。这不仅包括提交C、D、E 和F,还包括提交* 之后的所有“无趣”o 提交。对此有许多可能的解决方案,但也许最简单的方法是返回使用git log --graph --oneline:这将代替仅打印(完整)散列,打印(缩写)散列和提交消息,并绘制图形。现在您可以通过观察图表来查找“提前两次”的提交。
(当然,由于git log和git rev-list基本上是同一个命令,你也可以运行git rev-list --graph。不需要--oneline,因为无论如何输出都是每行一个提交ID。但是@ 987654390@ 是一个 plumbing 命令,用于脚本中,对用户不是很友好,而git log 用于与用户交互,并试图更直接有用。)
1这种三父合并是一种叫做“章鱼合并”的东西,在实践中并不常见。更典型的情况是,你会进行两次合并,一次将 branch2 合并到 branch3,另一次(或早或晚)将现在未命名的底行提交合并到 branch3。
2我们可能还想从标签开始。例如,考虑这个图形片段:
...--o--*--o--o <-- branch
\
o--o <-- tag: v0.98alpha
在这里,0.98alpha 版本被认为是“坏的”,并且可以从标签访问的两个提交不再位于 any 分支上。但是,如果事实证明这两个提交中的一个包含一些宝贵的数据或代码,您仍然可以通过从标记开始找到它,如果需要,可以向后工作到上一个提交。此处标记为* 的提交两步后退也在分支branch 上,因此即使没有从标记v0.98alpha 开始,您也会发现。
3在 Git 使用的这些有向图中,祖先是您可以通过从某个提交开始并向后工作可以达到的任何提交:提交本身,它的任何父母,任何父母的父母——任何祖父母——等等。这与你自己的“祖先”含义相同,只是在 Git 中,你被认为是你自己的祖先。
因为 Git 的箭头指向后方,所以很容易找到祖先。
“后代”一词的作用相同:您是自己的后代,但您的子孙后代也是您的后代,正如您所期望的那样。 Git 实际上无法直接找到这些孙辈,但是给定一对对提交,有一个非常简单的测试“是提交Y提交的后代 X":我们只是反转测试。 Y 要成为 X 的后代,X 必须是 Y 的祖先。由于 Git 可以回答“是祖先”的问题,我们反转测试并得到我们的答案。
4git log 和 git rev-list 之间存在一些显着差异,因此它们完全不是同一个命令。但是它们是从相同的源代码构建的,并且 可以 做完全相同的事情,具体取决于选项。他们只是默认设置了不同的选项:log 打印人类可读的东西,使用寻呼机,使用颜色输出等等;而rev-list 打印机器可读的东西并且不使用寻呼机。使一个人表现得像另一个人所需的标志也不总是显而易见的。