我想获取与文件相关的合并提交列表+在重命名文件时跟踪该文件。--first-parent尽管有和标志,我找不到实现此目标的方法--follow。当它们一起使用时,它们似乎无法按预期工作。
举个例子,假设我有一个名为foo.txt.
master分支上,我将“hello”附加到该文件并提交消息commit: hellobranch-world,将“world”附加到foo.txt 并提交消息commit: worldmaster并跑git merge --no-ff branch-worldbranch-rename、 run git mv foo.txt bar.txt、 commit 的新银行,并包含以下消息commit: renamemaster并跑git merge --no-ff branch-renamedummy.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
Run Code Online (Sandbox Code Playgroud)git log --oneline -- bar.txt不提供有关以下内容的任何信息foo.txt:
45ab4bc (branch-rename) commit: rename
Run Code Online (Sandbox Code Playgroud)git log --oneline --follow -- bar.txt提供子提交,包括重命名但不显示合并提交:
45ab4bc (branch-rename) commit: rename
46605f3 (branch-world) commit: world
c52a91c commit: hello
Run Code Online (Sandbox Code Playgroud)git log --oneline --first-parent -- bar.txt提供合并提交,但不检索与以下内容相关的提交foo.txt:
ca857ce Merge branch 'branch-rename'
Run Code Online (Sandbox Code Playgroud)git log --oneline --follow --first-parent -- bar.txt不返回任何内容。
任何想法?
正如您在评论中指出的,您需要:
\n\ngit log -m --oneline --follow --first-parent -- bar.txt\nRun Code Online (Sandbox Code Playgroud)\n\n我认为这是一个错误。-m解决这个问题的事实告诉我们,当使用 时--first-parent,Git 可能应该执行隐含的,与隐含的-m方式相同(查找重命名)。--follow-M
让我们git log从默认情况下如何显示提交开始。它从一个优先级队列开始,将您在命令行上指定的所有提交放入其中:
git log br1 br2 br3\nRun Code Online (Sandbox Code Playgroud)\n\n意味着查看分支、和的尖端提交,因此这三个提交(按哈希 ID)进入优先级队列。如果您不指定起始提交,则使用.br1br2br3git logHEAD
然后,它从队列中取出下一个(队列前面的)提交,这是具有最高优先级的提交。如果队列中只有一项提交\xe2\x80\x94,例如,当使用HEAD\xe2\x80\x94 运行时,这就是一项提交,并且队列现在为空。默认优先级是按提交者日期时间戳确定的,因此值最高的日期\xe2\x80\x94(距离未来最远的日期\xe2\x80\x94)赢得了这场比赛。如果队列中没有未来日期的提交,则最早进入过去的提交将获胜。如果只有一个提交\xe2\x80\x94(通常情况\xe2\x80\x94),则一个提交会赢得优先级竞赛。
Git 现在通过打印其哈希 ID 和日志消息或根据您的--pretty=或--format=指令的其他详细信息来显示此提交。请注意,这--oneline只是 的简写--pretty=oneline --abbrev-commit。
然后,只要提交不是合并,Git 就会运行 agit diff <parent> <commit>来向您显示此处的差异。添加到该git log行的任何 diff 选项(例如 )--name-status都会影响此 diff 输出。但默认情况下,如果提交是合并,git log则仅继续执行最后一步。
现在已经git log显示了提交,它将所有提交的父级放入优先级队列中。如果提交是普通提交(有一个父提交),则队列长度现在与 Git 显示这一提交之前的长度相同。如果是合并提交,则会将两个或更多父级放入队列中;如果它是根提交,则不会执行任何操作。
因此,该序列的总体驱动因素是这样的:
\n\nHEAD如果没有则添加。我们可以通过添加任何选项-c、--cc或来更改此循环的第 3 步-m。但是,我们还可以使用路径名(如、 或选项(如 )或选项(如和 ))更改整体循环\xe2\x80\x94步骤 2 和 4,特别是\xe2\x80\ x94 。bar.txt--first-parent--since--until
实际上,我们应该重构这个循环:
\n\n决定是否完全显示它。如果选择显示:
\n\n-cor--cc或强制-m,则显示 diff。将选定的父母放入队列中。
许多git log选项选择特定的提交,其中包括--since、--until、--author、--grep等。这-- bar.txt也是如此:告诉 Git 选择修改指定文件的提交。
不过,当使用路径名时,git log会打开History Simplification,这\xe2\x80\x94至少在默认情况下\xe2\x80\x94也会影响步骤 3 中的选择。特别是,当选择要放入队列的父级时,Git 做了一个非常聪明的技巧:它将未修改您在命令行上列出的文件的父级提交放入队列中。换句话说,它完全修剪了确实更改了文件的侧分支!
如果您试图找出为什么合并过程中某些内容没有更改文件,1这根本不是您想要的。但是,如果您试图弄清楚是什么创建了文件的特定版本,这就是您想要的,因为合并忽略了它不遵循的分支的更改。你试图弄清楚为什么bar.txt其中有一些特定的文本,而分支最终没有将该文本放入其中,所以该分支一定是无趣的!
这不是您的示例中发生的情况,但值得注意。人们可以添加--full-history以避免历史简化,或添加各种其他标志来改变历史简化发生的方式,但这就是文档中有关“TREESAME”的所有措辞的含义。
1特别是,如果您正在寻找您认为已进行但似乎不在当前文件中的更改,历史简化只会妨碍您,您应该使用--full-history.
-m和--first-parent现在是时候讨论组合差异了,它与“TREESAME”的概念相关。请记住,合并提交的定义是具有两个或多个父级(通常只有两个)的任何提交。请记住,git diff通常只比较两个提交,对于普通提交,git show请将git log父级\xe2\x80\x94(即父级\xe2\x80\x94)的一次计数提交与子级进行比较。但是,对于合并提交,至少有两个父级。我们应该比较哪一个?
Git 解决这个困境的方法是使用组合差异,Git 将所有父项与子项进行比较。为了使这一过程变得更容易,Git 会执行第一遍消除文件的子(合并)提交版本与任何父版本匹配的所有文件。这里的理论是,由于合并的版本至少与一个父版本匹配,因此您现在bar.txt不需要查看更改。Git 可能会遵循该父级,要么因为它正在查看所有父级,要么因为您正在使用历史简化,并且这是我们关心的文件,因此我们将遵循 TREESAME 父级。
因此,如果合并版本与每个父版本的不同,我们只会bar.txt在组合差异中看到。在这种情况下,Git 将使用组合 diff 格式显示更改。bar.txtbar.txt
Git不检查--first-parent这里。组合的 diff 代码在有或没有--first-parent. 在我看来,这部分是一个错误。
使用--first-parent我们重构循环的 alters 步骤 3:将父级添加到优先级队列时,Git 仅添加每个合并的第一个父级。因为它只会跟随第一个父级,所以看起来这应该完全禁用组合的差异代码,就像参数一样-m。
该-m参数告诉 Git 将每个合并拆分为多个虚拟子项以达到git diff目的。它没有对所有父项进行大的差异,而是假装(单个)合并提交实际上是多个普通提交,每个提交都有一个父项,但共享相同的源树。这样每个人git diff只有两个提交:一个父项,一个子项。
-m与结合使用--first-parent,git log将根据合并提交检查第一个父级,这正是我们想要的。
--follow所做--follow的就是一种黑客行为。您只能给出一个路径名,例如bar.txt, to --follow。然后,这将启用重命名查找,就像您在 Git 配置中指定了-M或--find-renames或 设置diff.renames为一样,或者默认情况下(如果您使用的是 Git 版本 2.9 或更高版本)。true
当 Git 进行重命名查找和--follow操作时,在决定是否显示提交(步骤 2)后,如果特定目标文件已重命名,Git 将更改它正在查找的(单个)名称。就我而言,在重现您的设置后,我可以运行以下命令:
$ git log -m --oneline --follow --first-parent --name-status -- bar.txt\nf2f5743 Merge branch \'branch-rename\'\nR100 foo.txt bar.txt\n4cd490a Merge branch \'branch-world\'\nM foo.txt\n4afa129 commit: hello\nA foo.txt\nRun Code Online (Sandbox Code Playgroud)\n\n以上R100是重命名检测的结果。Git 知道从此时开始 \xe2\x80\x94 即,在历史记录 \xe2\x80\x94 的任何早期提交中,该文件bar.txt现在被称为... 所以现在, Git 开始寻找foo.txt而不是寻找。bar.txtfoo.txt
如果您使用--full-history让 Git 遵循合并的两个“双方”(双方父级),消除该--first-parent选项,我们会发现这里存在一种缺陷:
$ git log -m --oneline --follow --name-status -- bar.txt \nf2f5743 (from 4cd490a) Merge branch \'branch-rename\'\nR100 foo.txt bar.txt\nf31ad99 (branch-rename) commit: rename\nD foo.txt\n4cd490a (from 4afa129) Merge branch \'branch-world\'\nM foo.txt\n9b4999d (branch-world) commit: world\nM foo.txt\n4afa129 commit: hello\nA foo.txt\nRun Code Online (Sandbox Code Playgroud)\n\n现在,在 中,branch-rename我们实际上并没有删除 ,我们只是根本foo.txt没有a 。但是重命名检测机制被代码滥用了,所以当 Git对其父级进行提交差异时,它不会注意到这里有一个重命名:它只是看到该文件是\父母那里不存在(而孩子那里)。foo.txt--followf31ad994cd490a
从根本上来说,这里的问题是在 Git 显示合并提交时--follow出现的:它立即从新名称切换到旧名称。当遍历恰好使用新名称而不是旧名称的合并的任何分支时,它将看不到那里的文件。只有当遍历使用旧名称的合并分支时,Git 才会看到文件的更改。