我想获取与文件相关的合并提交列表+在重命名后跟随该文件。尽管有--first-parent
和--follow
标志,但我无法找到实现此目的的方法。当它们一起使用时,它们似乎无法按预期工作。
举个例子,假设我有一个名为foo.txt
的文件。
master
分支上,我将“hello”附加到此文件并提交消息commit: hello
branch-world
的新分支,将“world”追加到foo.txt
并提交消息commit: world
master
并运行git merge --no-ff branch-world
branch-rename
的新banch,运行git mv foo.txt bar.txt
,提交消息commit: rename
master
并运行git merge --no-ff branch-rename
dummy.txt
,并使用消息commit: dummy
鉴于以下步骤:
git log --oneline
向我提供了太多信息,因为我只想了解foo.txt
:
cf0c1e4 (HEAD -> master) commit: dummy
ca857ce Merge branch 'branch-rename'
45ab4bc (branch-rename) commit: rename
2057b9c Merge branch 'branch-world'
46605f3 (branch-world) commit: world
c52a91c commit: hello
git log --oneline -- bar.txt
未提供有关foo.txt
的任何信息:
45ab4bc (branch-rename) commit: rename
git log --oneline --follow -- bar.txt
提供子提交,包括重命名但不显示合并提交:
45ab4bc (branch-rename) commit: rename
46605f3 (branch-world) commit: world
c52a91c commit: hello
git log --oneline --first-parent -- bar.txt
提供合并提交但不检索与foo.txt
相关的提交:
ca857ce Merge branch 'branch-rename'
git log --oneline --follow --first-parent -- bar.txt
不会返回任何内容。
有什么想法吗?
答案 0 :(得分:1)
正如您在评论中指出的那样,您需要:
git log -m --oneline --follow --first-parent -- bar.txt
我认为这个是一个bug。 -m
解决问题的事实告诉我们,在使用--first-parent
时,Git应该执行隐含-m
,就像--follow
暗示-M
一样(找到重命名)。
让我们从git log
默认显示提交的方式开始。它以priority queue开头,将您在命令行中指定的所有提交放入其中:
git log br1 br2 br3
意味着查看分支br1
,br2
和br3
的提示提交,因此这三个提交(通过哈希ID)进入优先队列。如果您未指定开始提交,git log
将使用HEAD
。
然后从队列中获取下一个(队列前端)提交,该队列是具有最高优先级的队列。如果队列中只有一个提交 - 例如,当使用HEAD
运行时 - 那是一次提交,队列现在是空的。默认优先级是通过提交者日期时间戳,因此最高值日期 - 最远的一个 - 赢得此次比赛。如果队列中没有未来日期的提交,那么过去最少的提交将获胜。如果只有一次提交 - 通常情况 - 一次提交赢得优先级竞赛。
Git现在通过根据您的--pretty=
或--format=
指令打印其哈希ID和日志消息或其他详细信息来显示此提交。请注意,--oneline
只是--pretty=oneline --abbrev-commit
的缩写。
然后, 只要提交不是合并 ,Git会运行git diff <parent> <commit>
来显示这里的差异。您添加到git log
行的任何差异选项(例如--name-status
)都会影响此差异输出。但默认情况下,如果提交是合并,git log
只会继续进行最后一步。
现在git log
已经显示了提交,它将所有提交的父项放入优先级队列。如果提交是普通的(有一个父级),则队列长度现在与Git显示此提交之前的长度相同。如果它是 merge 提交,则会将两个或更多父项放入队列中;如果它是 root 提交,它什么都不做。
因此,序列的整体驱动因素是:
HEAD
。我们可以通过添加任何选项-c
,--cc
或-m
来更改此循环的第3步。但是,我们还可以更改整体循环 - 特别是第2步和第4步 - 使用bar.txt
等路径名称,或--first-parent
等选项,或{{1}等选项}和--since
。
真的,我们应该重新构建这个循环来读取:
决定是否完全显示。如果选择以显示:
--until
或-c
或--cc
强制合并,则显示差异。将选定的父项置于队列中。
众多-m
个选项选择特定提交,其中包括git log
,--since
,--until
,--author
等等on --grep
也是如此:它告诉Git选择修改命名文件的提交。
但是,在使用路径名时,-- bar.txt
会打开History Simplification,这至少在默认情况下会影响步骤3中的选择。特别是,当选择父进入队列时,Git做了一个非常聪明的技巧:它将一个父提交放入队列,没有修改你在上面列出的文件。命令行。换句话说,它完全修剪做更改文件的侧枝!
如果您想弄清楚没有更改合并文件的原因, 1 这根本不是您想要的。但是,如果您正在尝试弄清楚做了什么创建了该文件的特定版本,那么 就是您想要的,因为合并已经忽略< / em>来自分支机构的更改不以下。您正试图弄清楚为什么git log
中包含某些特定文字,并且分支没有最终将该文本放入其中,因此分支必须无趣!
这不是你的例子中发生的事情,但值得注意。可以添加bar.txt
以避免历史记录简化,或者添加各种其他标志来改变历史简化的发生方式,但这就是所有关于&#34; TREESAME&#34; 表示,在文档中。
1 特别是,如果您正在寻找更改,以为已进入,但似乎不在当前文件中,历史记录简化只会妨碍你,你应该使用--full-history
。
--full-history
和-m
现在是时候讨论组合差异,它与&#34; TREESAME&#34;的概念联系在一起。请记住,合并提交的定义是具有两个或更多父项(通常只有两个)的任何提交。请记住,--first-parent
通常仅比较两个提交,对于普通提交,git diff
和git show
比较父 - 一次计数 - 1提交是孩子的父母。但是,对于合并提交,至少有两个父母。我们应该比较哪一个?
Git对这种困境的回答是使用组合差异,其中Git将所有父母与孩子进行比较。为了使这更容易,Git首先传递消除文件的子(合并)提交版本匹配父版本的任何的所有文件。这里的理论是,由于git log
的合并版本与至少一个父版本匹配,因此您不需要立即查看更改 。 Git可能会关注那位家长,或者因为它看着所有的父母,或者你正在使用历史记录简化,这是我们关心的文件,所以我们将关注TREESAME的父母。< / p>
因此,如果bar.txt
的合并版本与每个不同,我们只会在组合差异中看到bar.txt
父母的bar.txt
版本。在这种情况下,Git将使用combined diff format显示更改。
Git 不在这里检查bar.txt
。组合差异代码在有或没有--first-parent
的情况下工作相同。这是我认为是一个错误的部分。
使用--first-parent
改变了重组循环的第3步:当将父节点添加到优先级队列时,Git仅添加每个合并的第一个父节点。因为它只是跟随第一个父级,所以看起来这应该完全禁用组合的差异代码,而不是--first-parent
参数。
-m
参数告诉Git将每个合并拆分为多个虚拟子项,以实现-m
目的。它假设(单个)合并提交实际上是多个普通提交,每个提交有一个父提交,但共享相同的源树,而不是对所有父对象做一个大的差异。这样每个git diff
只有两个提交:一个父,一个孩子。
将git diff
与-m
结合,--first-parent
将检查第一个父对照合并提交,这正是我们想要的。
git log
--follow
所做的是一种黑客行为。您只能将一个路径名称(例如--follow
)提供给bar.txt
。然后,这会启用重命名查找,就像您已在Git配置中指定--follow
或-M
或将--find-renames
设置为diff.renames
,或by default if you are using Git version 2.9 or newer。 / p>
当Git正在进行重命名查找和true
时,在决定是否显示提交后(步骤2),Git将更改它的(单个)名称如果特定目标文件已重命名,则查找。就我而言,在重现你的设置之后,我可以运行它:
--follow
上面的$ git log -m --oneline --follow --first-parent --name-status -- bar.txt
f2f5743 Merge branch 'branch-rename'
R100 foo.txt bar.txt
4cd490a Merge branch 'branch-world'
M foo.txt
4afa129 commit: hello
A foo.txt
是重命名检测的结果。 Git知道从这一点开始 - 即,在历史早期的任何地方提交 - 文件R100
现在已知为bar.txt
...所以现在,而不是寻找foo.txt
, Git开始寻找bar.txt
。
如果你使用foo.txt
让Git跟随&#34; side&#34;合并(父母双方),取消--full-history
选项,我们发现这里存在一种缺陷:
--first-parent
现在,在$ git log -m --oneline --follow --name-status -- bar.txt
f2f5743 (from 4cd490a) Merge branch 'branch-rename'
R100 foo.txt bar.txt
f31ad99 (branch-rename) commit: rename
D foo.txt
4cd490a (from 4afa129) Merge branch 'branch-world'
M foo.txt
9b4999d (branch-world) commit: world
M foo.txt
4afa129 commit: hello
A foo.txt
,我们实际上并没有删除 branch-rename
,我们只是没有拥有 a { {1}}。但是重命名检测机制被foo.txt
代码滥用了一些,所以当Git对其父foo.txt
进行提交--follow
的差异时,它不会注意这里有一个重命名:它只是看到父文件中的文件不存在(并且在子文件中)。
从根本上说,这里的问题是在Git显示合并提交时f31ad99
应用:它立即从新名称切换到旧名称。当遍历合并的任何一段,恰好使用新名称而不是旧名称时,它不会看到那里的文件。只有在遍历使用旧名称的合并段时,Git才会看到对文件的更改。