简体   繁体   中英

Find when a “deleted by us” file was deleted in git

I have two branches: master , and a long-running feature branch, called feature . I continually merge from master into feature .

One of my merge conflicts is:

Unmerged paths:
  (use "git add/rm <file>..." as appropriate to mark resolution)
    deleted by us:   foo.file

When merging, I saw the following message:

CONFLICT (modify/delete): foo.file deleted in HEAD and modified in origin/master. Version origin/master of foo.file left in tree.

I'm interpreting this to mean that the file was updated on master but deleted on feature . I'd like to see which feature commit deleted it. Following Find when a file was deleted in git , I tried:

$ git log --oneline --name-status --full-history -- foo.file
0e051cd636 (feature) Merge from master
ee5c4f1ccc (feature) Merge from master
c200d5d8b2 (master) Add foo.file
A   foo.file

I was hoping to see a D foo.file line from --name-status , but I didn't. And when I did show on the two merge commits, I got empty output:

$ git show 0e051cd636 -- foo.file
$ git show ee5c4f1ccc -- foo.file
$ 

This command also gives empty output:

$ git log --diff-filter=D --summary foo.file
$

I also tried rev-list as suggested in the linked question, but it had empty output as well:

$ git rev-list -n 1 HEAD -- foo.file
$

So, how can I figure out when this file was deleted?

What about to list all files that have been deleted from a git repository:

git log --diff-filter=D --summary

So you see all deleted file with the commit-hash where you delete the file.

from git diff documentation

TL;DR: add -m to your git log command.

The git log command has a nasty little habit / flaw here: when it encounters merge commits, it doesn't bother running a git diff on them, by default. This applies to both git log -p (the "show me a diff" variant) and git log -- foo.file . You've (correctly) inserted --full-history to defeat git log 's other bad habit, of turning on History Simplification when used with a file name—sometimes this is OK, but not in the case you care about—but it's still being lazy about merge commits.

The big hammer here, equivalent to --full-history in terms of making git log do all the work, is to use -m . This tells Git to (virtually) split each merge. The basic problem is this:

  • Every commit, including a merge commit, stores a snapshot of all files. 1
  • But we usually want to see what changed in the files that changed ( git log -p ).

For ordinary non-merge commits, that's easy enough: Git just extracts both commits—the parent and the child—into a temporary area (in memory and with shortcuts) and compares the two snapshots. For each file that's the same, it does nothing at all. For any file that is different—including "new in child, does not exist in parent" or "gone from child, exists in parent but was removed"—Git can now compare the files' content and produce a diff, or—with --name-status –just tell us that the files is modified, added, or deleted as appropriate.

But a merge commit, by definition, has at least two parents. Which one should Git extract-and-compare? If we compare the child against its first parent, we'll get one set of files and changes; if we compare the child against its second parent, we get a different set of files and changes. 2 Which one should git log show?

The default, cheat-y answer for git log is: don't bother!

That is, git log just says to itself: Oh, hey, a merge commit... those are tough! I'll just not bother doing anything and hope nobody notices.

If the file is deleted because of the merge, and git log doesn't say anything, you never see the file getting deleted.

The -m flag tells git log : When you come across a merge commit—a commit that has n ≥ 2 parents—do one separate git diff of each parent vs the child, as if there were n separate one-parent commits. This is easy to do: there's no "what do we do about multiple parents" problem any more. So this is an ordinary diff and will show you the deletion, if there is a deletion.

There are other flags you can use to force git log to work harder, but they won't necessarily help for this particular case. With -m being the Big Hammer, it always works; it just may produce more output than you need. If you're a Git Ninja you can go for surgical approaches with -c or --cc and/or --first-parent and/or selective history simplification. But -m --full-history does the trick.


1 More precisely, every commit stores a snapshot of all the files that are in that commit. Said this way it sounds redundant: the commit stores the files that it stores. The idea here, though, is to distinguish these important cases: does a commit store a diff from a previous commit, or does it store a snapshot? The answer is that it stores a snapshot. If we did not have merge commits, this might not matter, but we do have merge commits, so it does end up being important.

(Even then, if Git always stored a diff against the previous first-parent, instead of storing snapshots, we could build what we need. So we're back to "maybe it doesn't matter". But in fact, Git literally stores snapshots, so we might as well just describe how it works, rather than theoretical equivalents. :-) )

2 If the two parents are different commits, but both parents have the same snapshot, we will actually get the same set of changes from both comparisons. That's pretty rare though.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM