Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

git: See changes to a specific file by a commit

Tags:

git

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?

like image 894
Rohan Kumar Avatar asked May 29 '17 14:05

Rohan Kumar


People also ask

How can I see the diff of a commit?

To see the diff for a particular COMMIT hash, where COMMIT is the hash of the commit: git diff COMMIT~ COMMIT will show you the difference between that COMMIT 's ancestor and the COMMIT .


2 Answers

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.

like image 101
torek Avatar answered Sep 28 '22 02:09

torek


You have to provide the path in that revision:

git show d210c6ccc3 -- svl/inc/svl/itemset.hxx 

That should do

like image 32
eftshift0 Avatar answered Sep 28 '22 00:09

eftshift0