git:通过提交查看对特定文件的更改 [英] git: See changes to a specific file by a commit

查看:98
本文介绍了git:通过提交查看对特定文件的更改的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想查看对文件位于第147行.因此,我逐行查询文件的提交方式:

git blame include/svl/itemset.hxx

这是git blame的修剪输出:

4b3a535ae3 include/svl/itemset.hxx         (Michael Stahl      2015-04-08 15:02:47 +0200 145)     SfxItemPool*                GetPool() const { return m_pPool; }
4b3a535ae3 include/svl/itemset.hxx         (Michael Stahl      2015-04-08 15:02:47 +0200 146)     const sal_uInt16*           GetRanges() const { return m_pWhichRanges;
 }
d210c6ccc3 svl/inc/svl/itemset.hxx         (Xiaofei Zhang      2010-07-29 10:56:19 +0800 147)     void                        SetRanges( const sal_uInt16 *pRanges );
d210c6ccc3 svl/inc/svl/itemset.hxx         (Xiaofei Zhang      2010-07-29 10:56:19 +0800 148)     void                        MergeRange( sal_uInt16 nFrom, sal_uInt16 nTo );
4b3a535ae3 include/svl/itemset.hxx         (Michael Stahl      2015-04-08 15:02:47 +0200 149)     const SfxItemSet*           GetParent() const { return m_pParent; }

现在,我想看看使用SHA d210c6ccc3对这些行所做的更改.基本上,我想查看此提交对文件所做的更改.所以我做到了:

`git show d210c6ccc3 include/svl/itemset.hxx`

但这似乎不能给我正确的输出,实际上它什么也没输出.谁能建议我可能会缺少什么?或者,也许还有其他更好的方法来了解所选提交对文件进行了哪些更改?

解决方案

eftshift0的答案是正确的(我已经支持).不过,这是为什么-以及这里可能出错的另一件事.

在大多数情况下,git show commit -- path是正确的,并且会向您显示:

  • 指定提交的日志消息,
  • 该特定文件的补丁程序,通过将该提交与其父文件进行比较而产生.

该修补程序将与git diff commit^1 commit -- path产生的修补程序相同. (请注意,这里的^1后缀是文字文本,而 commit 部分是要替换的哈希ID.后缀语法的意思是查找第一个父对象".您可以将该后缀添加到大多数提交选择器,尽管不是使用某些搜索模式的选择器.请参见 gitrevisions文档.)

有两个重要的例外.其中只有一种适用于此,因为git blame通常不归咎于合并,它会尝试跟踪将馈入的更改的 source .不过,我还是要提一下,因为git show在合并中的行为很有趣. :-)

如果您查看使用git show的合并提交,则默认情况下会看到合并的差异(所有父项"与合并提交的内容).在这种情况下,您可能希望直接回退到git diff,以便您可以指定要比较的父级(^1^2,如果是章鱼合并,甚至更多).原因是组合的diff故意忽略了任何与父提交中的版本相匹配的文件:这意味着从此刻起,存储库中的任何内容都来自两个文件中的 (或如果N> 2)为合并的边",则为N.

使用git blame,您正在寻找谁改变了什么".进行合并的人通常不是进行更改的人-因此,您应该继续在合并之后"查找确实更改过该文件的人.

第二个例外是在这里给您造成问题的一个例外,实际上是在开发过程中文件被重命名git blame工作方式的一种情况.

git blame分析文件更改时,例如include/svl/itemset.hxx,它一次从指定的任何提交开始后退一次提交.如果您没有选择自己的起点,则从HEAD开始,即当前提交.然后,它查看父提交(例如,通过git show).例如,如果当前提交922e935c8812是普通提交,并且其父级是22c6554c98e2,则它将提交922e935c881222c6554c98e2进行比较.如果22c6554c98e2的文件名是 ,则可能是相同的文件 ...但是,如果 not ,Git会尝试找出22c6554c98e2中的哪个文件include/svl/itemset.hxx相同.

在这种情况下,确切的事情发生在提交b9337e22ce1d处.在提交b9337e22ce1d中有一个名为include/svl/itemset.hxx的文件,但是在提交b9337e22ce1d^f4e1642a1761中,该文件名为svl/inc/svl/itemset.hxx.当从提交b9337e22ce1d退回到提交f4e1642a1761时,Git会检测到此重命名,然后git blame随身携带 new 的名称,从提交f4e1642a1761返回到提交d210c6ccc3.

但是,当您运行git show d210c6ccc3时,Git会直接跳转到d210c6ccc3(及其父级7f0993d43019).它不再知道HEAD中名为include/svl/itemset.hxx的文件在d210c6ccc3中名为svl/inc/svl/itemset.hxx的情况.因此,必须发现它,并将更早的名称传递给Git.

您可能想知道如何找到它.答案是使用git log --follow. git log--follow代码不是很好, 1 ,但是它是使用的相同代码,因此至少会产生相同的答案.这是我所做的:

$ git log --oneline --follow --name-status include/svl/itemset.hxx
00aa9f622c29 Revert "used std::map in SfxItemSet"
M       include/svl/itemset.hxx
afaa10da2572 make SfxItemSet with SAL_WARN_UNUSED
M       include/svl/itemset.hxx
[snip]
a7724966ab4f Bin comments that claim to say why some header is included
M       include/svl/itemset.hxx
b9337e22ce1d execute move of global headers
R100    svl/inc/svl/itemset.hxx include/svl/itemset.hxx

还有更早的第二次重命名.这是查找重命名的另一种较短的方法:

$ git log --oneline --follow --diff-filter=R --name-status include/svl/itemset.hxx
b9337e22ce1d execute move of global headers
R100    svl/inc/svl/itemset.hxx include/svl/itemset.hxx
e6b4345c7f40 #i103496#: split svtools in two libs, depending on whether the code needs vcl or not
R100    svtools/inc/svtools/itemset.hxx svl/inc/svl/itemset.hxx

如果d210c6ccc3e6b4345c7f40之前,则必须使用偶数更早的路径名,但是commit d210c6ccc3e6b4345c7f40的后代,而不是祖先. >


1 当合并中发生更改时,从根本上说,这确实需要同时遵循 both (或所有)输入.但是,Git无法(当前?)能够执行此操作:git log --followgit blame都实际上仅遍历文件最终来源"的最终版本.也就是说,如果我们查看包含合并文件F的典型合并提交M,则有两个输入M^1:FM^2:F,以及一个输出M:F.如果M:FM^1:F相同,则应完全忽略 M^2:F:M:F的所有内容均来自提供M^1:F的人员.如果M:FM^2:F相同,则应完全忽略 M^1:F:M:F的所有内容均来自提供M^2:F的人员.

请注意,这一次仅适用于一个文件,即使如此,它也仅在文件与两个输入之一完全匹配时有效.否则,我们应该查看 combined diff ,以了解如何从 both 输入修改文件.这是组合差异和历史简化"背后的逻辑.但是在极少数情况下,它过于简化,因此有时会出错.

I want to see the what changes were made to this file's at line 147. So i enquired the file line by line for commits by:

git blame include/svl/itemset.hxx

Here is the trimmed output of git blame:

4b3a535ae3 include/svl/itemset.hxx         (Michael Stahl      2015-04-08 15:02:47 +0200 145)     SfxItemPool*                GetPool() const { return m_pPool; }
4b3a535ae3 include/svl/itemset.hxx         (Michael Stahl      2015-04-08 15:02:47 +0200 146)     const sal_uInt16*           GetRanges() const { return m_pWhichRanges;
 }
d210c6ccc3 svl/inc/svl/itemset.hxx         (Xiaofei Zhang      2010-07-29 10:56:19 +0800 147)     void                        SetRanges( const sal_uInt16 *pRanges );
d210c6ccc3 svl/inc/svl/itemset.hxx         (Xiaofei Zhang      2010-07-29 10:56:19 +0800 148)     void                        MergeRange( sal_uInt16 nFrom, sal_uInt16 nTo );
4b3a535ae3 include/svl/itemset.hxx         (Michael Stahl      2015-04-08 15:02:47 +0200 149)     const SfxItemSet*           GetParent() const { return m_pParent; }

Now i want to to see what changes did that commit with SHA d210c6ccc3 to those lines. Basically i want to see the changes made by this commit to the file. So i did :

`git show d210c6ccc3 include/svl/itemset.hxx`

But this does not seem to give me right output, in fact it outputs nothing. Could anyone please suggest what could i be missing? Or maybe there is some other better way to know what changes were made to a file by a selected commit?

解决方案

eftshift0's answer is correct (and I've upvoted it). Here's why, though—along with the one other thing that can go wrong here.

In most cases, git show commit -- path would be correct and would show you:

  • the log message for the specified commit, and
  • a patch for that particular file, as produced by comparing that commit with its parent.

The patch would be the same as that produced by git diff commit^1 commit -- path. (Note that the ^1 suffix here is literal text while the commit part is a hash ID that you replace. The suffix syntax means "find the first parent". You can add this suffix to most commit selectors, although not to selectors that use certain search patterns. See the gitrevisions documentation.)

There are two important exceptions. Only one of these applies here, because git blame normally does not blame a merge, it tries to trace the source of the change that fed into the merge. Still, I want to mention it, because git show's behavior on merges is ... interesting. :-)

If you look at a merge commit with git show, you will see, by default, a combined diff ("all parents" vs the merge commit's content). In this case you may wish to fall back directly on git diff, so that you can specify the parent you want to compare (^1, ^2, and even more if this is an octopus merge). The reason is that combined diffs deliberately omit any file that matches the version in one of the parent commits: this means that whatever is in the repository from that point forward, it came from one of the two (or N if N > 2) "sides" of the merge.

With git blame, you are looking for "who changed what". The person who did the merge is often not the person who made the change—so you should keep going "behind the merge" to find who really changed it.

The second exception is the one that caused a problem for you here, and it's really more a case of the way git blame works when files get renamed during development.

When git blame is analyzing changes to a file, such as include/svl/itemset.hxx, it steps back one commit at at time, starting from whichever commit you specify. If you don't select your own starting point, it starts from from HEAD, i.e., the current commit. It then looks at the parent commit(s) (as if via git show for instance). For instance, if the current commit 922e935c8812 is an ordinary commit and its parent is 22c6554c98e2, it compares commit 922e935c8812 to 22c6554c98e2. If 22c6554c98e2 has a file of the same name, that's probably the same file ... but if not, Git tries to figure out which file in 22c6554c98e2 is the same file as include/svl/itemset.hxx.

In this case, that exact thing happens at commit b9337e22ce1d. There is a file named include/svl/itemset.hxx in commit b9337e22ce1d, but in commit b9337e22ce1d^ or f4e1642a1761, the file is named svl/inc/svl/itemset.hxx. Git detects this rename when stepping back from commit b9337e22ce1d to commit f4e1642a1761, and git blame then carries the new name back with it from commit f4e1642a1761 to commit d210c6ccc3.

When you run git show d210c6ccc3, however, Git jumps directly to d210c6ccc3 (and its parent 7f0993d43019). It no longer knows that the file named include/svl/itemset.hxx in HEAD is named svl/inc/svl/itemset.hxx in d210c6ccc3. So you must discover this, and pass the earlier name to Git.

You might wonder how you can find this. The answer is to use git log --follow. The --follow code for git log is not great,1 but it's the same code that git blame uses, so it produces the same answers, at least. Here is what I did:

$ git log --oneline --follow --name-status include/svl/itemset.hxx
00aa9f622c29 Revert "used std::map in SfxItemSet"
M       include/svl/itemset.hxx
afaa10da2572 make SfxItemSet with SAL_WARN_UNUSED
M       include/svl/itemset.hxx
[snip]
a7724966ab4f Bin comments that claim to say why some header is included
M       include/svl/itemset.hxx
b9337e22ce1d execute move of global headers
R100    svl/inc/svl/itemset.hxx include/svl/itemset.hxx

There's a second rename even earlier. Here's another, shorter way to find only the renames:

$ git log --oneline --follow --diff-filter=R --name-status include/svl/itemset.hxx
b9337e22ce1d execute move of global headers
R100    svl/inc/svl/itemset.hxx include/svl/itemset.hxx
e6b4345c7f40 #i103496#: split svtools in two libs, depending on whether the code needs vcl or not
R100    svtools/inc/svtools/itemset.hxx svl/inc/svl/itemset.hxx

If d210c6ccc3 came "before" e6b4345c7f40, you would have to use the even-earlier path name—but commit d210c6ccc3 is a descendant of (comes after) e6b4345c7f40, not an ancestor.


1When changes occur at merges, this really, in a fundamental sense, requires following both (or all) input commits simultaneously. However, Git is not (currently?) capable of doing this: both git log --follow and git blame really only traverse whichever parent the final version of the file "came from". That is, if we look at a typical merge commit M containing merged file F, there are two inputs M^1:F and M^2:F, and one output, M:F. If M:F is the same as M^1:F, we should ignore M^2:F entirely: all the contents of M:F are those from whoever provided M^1:F. If M:F is the same as M^2:F, we should ignore M^1:F entirely: all the contents of M:F are those from whoever provided M^2:F.

Note that this only works for one file at a time, and even then, it only works if the file exactly matches one of the two inputs. Otherwise, we should look at the combined diff to see how the file got modified from both inputs. This is the logic behind both combined diffs and History Simplification. But it's over-simplified in some rare cases, and hence sometimes gets things wrong.

这篇关于git:通过提交查看对特定文件的更改的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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