简而言之: 您需要使用--first-parent
(请参阅git rev-list
文档) 和其他一些选择条件。
Git不会记录“提交实际上是在哪个分支上进行的”。例如,假设您在分支B
上并执行以下操作:
$ git checkout --detach B
[message about "detached head"]
[edit some file here, and git add result]
$ git commit -m message
$ git branch -f B HEAD
$ git checkout B
这将在分支 B
的尖端添加一个新的提交,即使该提交是在没有任何分支的情况下进行的。或者,假设有两个分支B1
和B2
都指向提交C7
:
... <- C5 <- C6 <- C7 <
如果您创建了一个新的提交
C8
,其父提交为
C7
,然后使分支
B2
指向
C8
,那么该提交现在就在分支
B2
上。如果您在分支
B1
上创建了新的提交,则
B1
也会指向
C8
,但是如果您使用
git reset
将
B1
移动回指向
C7
,则不再可能确定该提交最初是在
B1
而不是
B2
上创建的。
1
尽管如此,如果您在合并分支时保持良好的一致性,您可以在提交图中执行图遍历,查找“介于”特定合并之间的提交,然后通过一个特定的父链来检测这些提交最可能是在哪个分支上创建的。例如,在描述的情况下,使用
feature
和
master
,假设提交是一致地在
feature
上进行的,然后使用
--no-ff
将
feature
定期合并到
master
中,以确保每个合并提交都是真正的合并提交,而不仅仅是快进提交。然后,图形模式将如下所示:
... --X--*------*-o---o-----*--* <-- master
\ / \ / /
A--B--C--D--E--F--G--H <-- feature
在这种情况下,您可以通过从feature
的顶端(提交H
)开始遍历仅第一个父提交来查找可能在feature
上创建的提交。这些是A
到H
,然后丢弃可通过仅遵循master
上的第一个父提交找到的提交。第二个标准消除了上面标记为X
的提交(以及任何先前的提交)。 (请注意,在合并提交E
的第一个父级是D
,而其第二个父级位于master
上左侧的o
提交之上。对于master
上的所有合并,第一个父级在同一行上,而第二个父级则在feature
下方并向左。)
这是否足够取决于您(和其他人)在提交过程中的纪律性。如果master
有自己的内部分支和合并,则可能会出现以下类似的图形:
... --X---Y---*-----*-o---o---------*--* <-- master
\ \ / / \ / /
\ Z / \ / /
\ / \ / /
A--B--C--D------E--F--G--H <-- feature
现在你不能像以前那样轻松地使用第一个/非第一个父测试,因为提交Y或Z必定是主分支上合并的第二个父提交,但请注意,没有经过E的情况下,Z是不可从feature到达的,E是一个直接在feature上的合并提交,其第二个父提交在主分支上。换句话说,我使用了有点奇怪的短语“直接在”一个分支来确定可以通过从分支尖端开始,并向后遍历仅第一个父代找到的提交。 对于feature这些仍然是A到H,加上不幸的X及其(第一个)父代。git rev-list命令是git的核心命令之一 - 它是git rev-parse的大姐版本,后者也是非常核心的命令 - 具有明确实现此“直接在”概念的标志,即--first-parent。rev-list命令遍历提交图的某些部分:您告诉它要从哪个提交开始,它会找到该提交的父提交,以及那些父提交的父提交等。如果指定--first-parent,它仅查找每个提交的第一个父提交。(显然,这仅影响合并提交,因为根据定义,非合并提交最多只有一个父提交。)在您的特定情况下,您还表示希望消除合并到您的feature分支中的合并提交,即直接在feature上的合并提交。要做到这一点,请简单地告诉rev-list在其输出中排除合并提交,使用--no-merges。消除提交X及更早提交的难度有点大,但事实证明,并不是那么难,前提是您知道包含类似于X的提交的分支以及您想要排除的分支。--not参数(包括几种替代拼写)告诉rev-list要排除哪些提交。所以:
git rev-list --first-parent feature --not master --no-merges
识别“直接在”功能上的提交列表(从
A
到
H
),但不直接在
master
(
X
及更早)上,然后仅列出不是合并的提交(因此消除提交
E
)。
您可能还想进一步简化此操作,例如,通过使用提交时间戳(
--since
和
--until
)来完成。您可能(或可能不)还想更改列出修订版本的顺序,例如使用
--topo-order
按图形顺序而不是日期顺序进行排序。
将此与
git log
结合使用
幸运的是,
git log
有效地使用您的修订说明符调用
git rev-list
。因此,您可以直接将这些相同的说明符提供给
git log
,而无需先使用
git rev-list
,然后以某种方式将结果提供给
git log
。事实上,在确定要使用哪些限制器(例如
--since
和
--until
之类的内容)时,
git log
要容易得多,因为一堆原始SHA-1对于人类来说并不是很有用。
注1:实际上,reflog(保留分支动态历史记录)确实可以让您知道,但仅限于一台本地计算机;问题说必须在另一台系统上进行此操作,在该系统上reflog不可用。无论如何,reflog条目会在一段时间后过期,这里默认为90天。
注2:当然,除非您使用
--no-walk
。