`git log --follow --graph`跳过提交 [英] `git log --follow --graph` skips commits

查看:129
本文介绍了`git log --follow --graph`跳过提交的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

安装



  git version 2.11.0.windows.1 

下面是一个bash代码片段,用于复制我的测试存储库:

  git init 

#创建一个文件
echo Hello> a.txt
git add a.txt
git commit -m'第一次提交'

#在一个分支上更改
git checkout -b功能
回显Hi> a.txt
git commit -am'更改'

#将其重命名为
git checkout master
git mv a.txt b.txt
git commit -m'移动'

#合并两个变化
git merge --no-edit功能


$ b

最后, git log --graph --pretty = oneline --abbrev-commit 打印:

  * 06b5bb7合并分支'功能'
| \
| * 07ccfb6更改
* | 448ad99移动
| /
* 31eae74第一次提交






问题



现在,我想获得完整日志 b.txt - b.txt )。

git log --graph --pretty = oneline --abbrev-commit --follow - b.txt 打印:

  ... 
* | 1a07e48 Move
| /
* 5ff73f6第一次提交

如您所见, Change commit没有列出,尽管它修改了文件。



我认为我已经通过 - graph 隐式使用 - topo-order ,因为添加 - date-order 会将提交返回,但这可能是机会。 $ c> -m 显示合并提交(这是正常的)和更改提交,但合并提交重复:

  * 36c80a8(来自1a07e48)合并分支'功能'
| \
| | 36c80a8(来自05116f1)合并分支'功能'
| * 05116f1更改
* | 1a07e48 Move
| /
* 5ff73f6第一次提交



问题



我错过了什么来解释我目睹的奇怪行为?

如何通过重命名来干净地显示更改文件的所有提交? 低级的实现 - 遵循,加上 git log 通常甚至不会看到合并内部。 p>

基本上, - 遵循通过更改要查找的文件的名称在内部工作。它不会记住两个名称,所以当线性化算法(通过优先级队列进行的宽度优先搜索)沿着合并的另一条边进行时,它的名称就会错误。您认为提交访问的顺序很重要,因为当Git推断重命名时,Git会更改它正在搜索的文件的名称。



在此图中就像你运行脚本几次,因为哈希已经改变了 - 这里的哈希值来自第一个示例):

  * 06b5bb7合并分支'功能'
| \
| * 07ccfb6更改
* | 448ad99 Move
| /
* 31eae74第一次提交

git log 会访问commit 06b5bb7 ,并将 448ad99 队列中的> 07ccfb6 。使用默认的拓扑订单,它将接下来访问 448ad99 ,检查差异并查看重命名。它现在正在寻找 a.txt 而不是 b.txt 。提交 448ad99 被选中,所以 git log 会将其打印到输出中; Git将 31eae74 添加到访问队列中。接下来,Git访问 07ccfb6 ,但它现在正在查找 a.txt ,因此未选择此提交。 Git在访问队列中添加了 31eae74 (但它已经存在,所以这是一个无操作)。最后,Git访问 31eae74 ;将该提交的树与空树比较,Git找到了一个添加的 a.txt ,所以这个提交被选中。



请注意,如果Git在 448ad99 之前访问过 07ccfb6 ,它会选择两者,因为在 b.txt 。

c $ c> flag通过将一个合并拆分为两个单独的内部虚拟提交(使用同一棵树,但将(from ...))添加到他们的名字,以便能够分辨哪个虚拟提交是由哪个父代产生的)。这具有保留两个拆分合并和查看它们的差异的副作用(因为拆分合并的结果是两个普通的非合并提交)。所以现在请注意,这将在第二个示例中使用新的存储库及其新的不同哈希值--Git访问提交 36c80a8(来自1a07e48),diffs 1a07e48 vs 36c80a8 ,看到对 b.txt 的更改并选择提交,访问队列中的 1a07e48 。接下来,它访问commit 36c80a8(来自05116f1),差异 05116f1 vs 36c80a8 ,并在访问队列中放置 05116f1 。其余部分在这里相当明显。


如何通过重命名来干净地显示更改文件的所有提交?


Git的答案是你不能,至少不能使用内置Git的内容。



您可以(有时)通过添加 - cc -c 添加到 git log 命令中。这使得 git log 查看合并提交内部,做Git调用 combined diff 。但无论如何,这并不一定有效,因为隐藏在文档的不同部分是这个关键句子:


请注意,组合diff (注意, ... 字面上存在,在 git log 的输出):

  $ git log --graph --oneline --follow --cc  -  b.txt 
* e5a17d7(HEAD - > master)合并分支'功能'
| \
| |
...
* | 52e75c9移动
| /
| diff --git a / a.txt b / b.txt
|相似指数100%
|从a.txt重命名
|重命名为b.txt
* 7590cfd第一次提交
diff --git a / a.txt b / a.txt
新文件模式100644
索引0000000..e965047
--- / dev / null
+++ b / a.txt
@@ -0,0 +1 @@
+ Hello

然而,基本上,您需要 git log 来更多地了解文件在合并提交时重命名,并使用旧文件名查找任何一段脚本中的旧名称,并使用新名称查找任何脚的新名称。这需要在每个合并中内部使用 git log (大部分) -m 选项 - 即每个合并合并为N个单独的差异,每个父亲一个差异,以便找出哪些腿具有重命名的内容 - 然后保留哪个名称用于合并的哪些分支。但是,当叉子回来时,也就是说,当合并的多条腿(我们反方向的叉子)重新合并时,不清楚哪个名称是正确的名称!


Setup

git version 2.11.0.windows.1

Here is a bash snippet to reproduce my test repository:

git init

# Create a file
echo Hello > a.txt
git add a.txt
git commit -m 'First commit'

# Change it on one branch
git checkout -b feature
echo Hi > a.txt
git commit -am 'Change'

# Rename it on the other
git checkout master
git mv a.txt b.txt
git commit -m 'Move'

# Merge both changes
git merge --no-edit feature

At the end, git log --graph --pretty=oneline --abbrev-commit prints:

*   06b5bb7 Merge branch 'feature'
|\
| * 07ccfb6 Change
* | 448ad99 Move
|/
* 31eae74 First commit


Problem

Now, I want to get the full log for b.txt (ex-b.txt).
git log --graph --pretty=oneline --abbrev-commit --follow -- b.txt prints:

...
* | 1a07e48 Move
|/
* 5ff73f6 First commit

As you can see, the Change commit is not listed, even though it did modify the file.

I think I have tracked it down to the implicit use of --topo-order by --graph, since adding --date-order brings the commit back, but that might be chance.

Additionally, adding -m shows the merge commit (which is fine) and the Change commit, but then the merge commit is duplicated:

*   36c80a8 (from 1a07e48) Merge branch 'feature'
|\
| | 36c80a8 (from 05116f1) Merge branch 'feature'
| * 05116f1 Change
* | 1a07e48 Move
|/
* 5ff73f6 First commit

Question

What am I missing to explain the weird behaviour I'm witnessing?
How can I display cleanly all of the commits that changed a file, following through renames?

解决方案

You're being bitten by git log's cheap and sleazy implementation of --follow, plus the fact that git log often doesn't even look inside merges.

Fundamentally, --follow works internally by changing the name of the file it's looking for. It does not remember both names, so when the linearization algorithm (breadth first search via priority queue) goes down the other leg of the merge, it has the wrong name. You are correct that the order of commit visits matters since it's when Git deduces a rename that Git changes the name of the file it's searching for.

In this graph (it looks like you ran the script several times because the hashes changed—the hashes here are from the first sample):

*   06b5bb7 Merge branch 'feature'
|\
| * 07ccfb6 Change
* | 448ad99 Move
|/
* 31eae74 First commit

git log will visit commit 06b5bb7, and put 448ad99 and 07ccfb6 on the queue. With the default topo order it will next visit 448ad99, examine the diff, and see the rename. It is now looking for a.txt instead of b.txt. Commit 448ad99 is selected, so git log will print it to the output; and Git adds 31eae74 to the visit queue. Next, Git visits 07ccfb6, but it is now looking for a.txt so this commit is not selected. Git adds 31eae74 to the visit queue (but it's already there so this is a no-op). Finally, Git visits 31eae74; comparing that commit's tree to the empty tree, Git finds an added a.txt so this commit gets selected.

Note that had Git visited 07ccfb6 before 448ad99, it would have selected both, because at the start it is looking for b.txt.

The -m flag works by "splitting" a merge into two separate internal "virtual commits" (with the same tree, but with the (from ...) added to their "names" so as to be able to tell which virtual commit resulted from which parent). This has the side effect of retaining both of the split merges and looking at their diffs (since the result of splitting this merge is two ordinary non-merge commits). So now—note that this uses your new repository with its new different hashes in the second sample—Git visits commit 36c80a8 (from 1a07e48), diffs 1a07e48 vs 36c80a8, sees a change to b.txt and selects the commit, and puts 1a07e48 on the visit queue. Next, it visits commit 36c80a8 (from 05116f1), diffs 05116f1 vs 36c80a8, and puts 05116f1 on the visit queue. The rest is fairly obvious from here.

How can I display cleanly all of the commits that changed a file, following through renames?

The answer for Git is that you can't, at least not using what is built in to Git.

You can (sometimes) get a little closer by adding --cc or -c to your git log command. This makes git log look inside merge commits, doing what Git calls a combined diff. But this doesn't necessarily work anyway, because, hidden away in a different part of the documentation is this key sentence:

Note that combined diff lists only files which were modified from all parents.

Here is what I get with --cc added (note, the ... is literally there, in git log's output):

$ git log --graph --oneline --follow --cc -- b.txt
*   e5a17d7 (HEAD -> master) Merge branch 'feature'
|\  
| | 
... 
* | 52e75c9 Move
|/  
|   diff --git a/a.txt b/b.txt
|   similarity index 100%
|   rename from a.txt
|   rename to b.txt
* 7590cfd First commit
  diff --git a/a.txt b/a.txt
  new file mode 100644
  index 0000000..e965047
  --- /dev/null
  +++ b/a.txt
  @@ -0,0 +1 @@
  +Hello

Fundamentally, though, you'd need git log to be much more aware of file renames at merge commits, and to have it look for the old name down any leg using the old file name, and the new name down any leg using the new name. This would require that git log use (most of) the -m option internally on each merge—i.e., split each merge into N separate diffs, one per parent, so as to find which legs have what renames—and then keep a list of which name to use down which branches of merges. But when the forks come back together, i.e., when the multiple legs of the merge (which becomes a fork in our reverse direction) rejoin, it's not clear which name is the correct name to use!

这篇关于`git log --follow --graph`跳过提交的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆