Git记录第一个父母并跟随

时间:2018-03-26 04:17:46

标签: git

我想获取与文件相关的合并提交列表+在重命名后跟随该文件。尽管有--first-parent--follow标志,但我无法找到实现此目的的方法。当它们一起使用时,它们似乎无法按预期工作。

举个例子,假设我有一个名为foo.txt的文件。

  1. master分支上,我将“hello”附加到此文件并提交消息commit: hello
  2. 我创建了一个名为branch-world的新分支,将“world”追加到foo.txt并提交消息commit: world
  3. 我转到master并运行git merge --no-ff branch-world
  4. 我创建了一个名为branch-rename的新banch,运行git mv foo.txt bar.txt,提交消息commit: rename
  5. 我转到master并运行git merge --no-ff branch-rename
  6. 我创建了一个没有内容的新虚拟文件dummy.txt,并使用消息commit: dummy
  7. 提交此文件

    鉴于以下步骤:

    • 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不会返回任何内容。

    有什么想法吗?

1 个答案:

答案 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

意味着查看分支br1br2br3提示提交,因此这三个提交(通过哈希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
  • 循环直到队列为空:
    1. 排队。
    2. 显示提交哈希和日志消息(或按格式选择的任何内容)。
    3. 如果不合并,请显示差异。
    4. 将父母安排到队列中。

我们可以通过添加任何选项-c--cc-m来更改此循环的第3步。但是,我们还可以更改整体循环 - 特别是第2步和第4步 - 使用bar.txt等路径名称,或--first-parent等选项,或{{1}等选项}和--since

真的,我们应该重新构建这个循环来读取:

  1. 排队。
  2. 决定是否完全显示。如果选择以显示:

    • 显示提交哈希和日志消息(或按格式选择的任何内容)。
    • 如果不是合并,或者--until-c--cc强制合并,则显示差异。
  3. 选定的父项置于队列中。

  4. 众多-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 diffgit 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才会看到对文件的更改。