I have a Git repository with n commits.
I have a file that I need, and that used to be in the repository, and that I suddenly look for and think "Oh! Where'd that file go?"
Is there a (series of) Git command(s) that will tell me that "file really_needed.txt was deleted at commit n-13"?
In other words, without looking at every individual commit, and knowing that my Git repo has every change of every file, can I quickly find the last commit that HAS that file, so I can get it back?
To show the commits that changed a file, even if the file was deleted, run this command:
git log --full-history -- [file path]
If you want to see only the last commit, which deleted the file, use -1
in addition to the command above:
git log --full-history -1 -- [file path]
See also my article: Which commit deleted a file.
Short answer:
git log --full-history -- your_file
will show you all commits in your repo's history, including merge commits, that touched your_file
. The last (top) one is the one that deleted the file.
Some explanation:
The --full-history
flag here is important. Without it, Git performs "history simplification" when you ask it for the log of a file. The docs are light on details about exactly how this works and I lack the grit and courage required to try to figure it out from the source code, but the git-log docs have this much to say:
Default mode Simplifies the history to the simplest history explaining the final state of the tree. Simplest because it prunes some side branches if the end result is the same (i.e. merging branches with the same content)
This is obviously concerning when the file whose history we want is deleted, since the simplest history explaining the final state of a deleted file is no history. Is there a risk that git log
without --full-history
will simply claim that the file was never created? Unfortunately, yes. Here's a demonstration:
mark@lunchbox:~/example$ git init
Initialised empty Git repository in /home/mark/example/.git/
mark@lunchbox:~/example$ touch foo && git add foo && git commit -m "Added foo"
[master (root-commit) ddff7a7] Added foo
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 foo
mark@lunchbox:~/example$ git checkout -b newbranch
Switched to a new branch 'newbranch'
mark@lunchbox:~/example$ touch bar && git add bar && git commit -m "Added bar"
[newbranch 7f9299a] Added bar
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 bar
mark@lunchbox:~/example$ git checkout master
Switched to branch 'master'
mark@lunchbox:~/example$ git rm foo && git commit -m "Deleted foo"
rm 'foo'
[master 7740344] Deleted foo
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 foo
mark@lunchbox:~/example$ git checkout newbranch
Switched to branch 'newbranch'
mark@lunchbox:~/example$ git rm bar && git commit -m "Deleted bar"
rm 'bar'
[newbranch 873ed35] Deleted bar
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 bar
mark@lunchbox:~/example$ git checkout master
Switched to branch 'master'
mark@lunchbox:~/example$ git merge newbranch
Already up-to-date!
Merge made by the 'recursive' strategy.
mark@lunchbox:~/example$ git log -- foo
commit 77403443a13a93073289f95a782307b1ebc21162
Author: Mark Amery
Date: Tue Jan 12 22:50:50 2016 +0000
Deleted foo
commit ddff7a78068aefb7a4d19c82e718099cf57be694
Author: Mark Amery
Date: Tue Jan 12 22:50:19 2016 +0000
Added foo
mark@lunchbox:~/example$ git log -- bar
mark@lunchbox:~/example$ git log --full-history -- foo
commit 2463e56a21e8ee529a59b63f2c6fcc9914a2b37c
Merge: 7740344 873ed35
Author: Mark Amery
Date: Tue Jan 12 22:51:36 2016 +0000
Merge branch 'newbranch'
commit 77403443a13a93073289f95a782307b1ebc21162
Author: Mark Amery
Date: Tue Jan 12 22:50:50 2016 +0000
Deleted foo
commit ddff7a78068aefb7a4d19c82e718099cf57be694
Author: Mark Amery
Date: Tue Jan 12 22:50:19 2016 +0000
Added foo
mark@lunchbox:~/example$ git log --full-history -- bar
commit 873ed352c5e0f296b26d1582b3b0b2d99e40d37c
Author: Mark Amery
Date: Tue Jan 12 22:51:29 2016 +0000
Deleted bar
commit 7f9299a80cc9114bf9f415e1e9a849f5d02f94ec
Author: Mark Amery
Date: Tue Jan 12 22:50:38 2016 +0000
Added bar
Notice how git log -- bar
in the terminal dump above resulted in literally no output; Git is "simplifying" history down into a fiction where bar
never existed. git log --full-history -- bar
, on the other hand, gives us the commit that created bar
and the commit that deleted it.
To be clear: this issue isn't merely theoretical. I only looked into the docs and discovered the --full-history
flag because git log -- some_file
was failing for me in a real repository where I was trying to track a deleted file down. History simplification might sometimes be helpful when you're trying to understand how a currently-existing file came to be in its current state, but when trying to track down a file deletion it's more likely to screw you over by hiding the commit you care about. Always use the --full-history
flag for this use case.
git log
output itself, it's not at all obvious that the last commit has deleted the file. I also tried git log --name-status --full-history -- file_name
and git log -p --stat --full-history -- file_name
, but neither explicitly indicates that the file was removed in the latest commit. This seems like a bug.
mkdir somedir && cd somedir && git init && touch foo && git add foo && git commit -m "Added foo" && git checkout -b newbranch && touch bar && git add bar && git commit -m "Added bar" && git checkout master && git rm foo && git commit -m "Deleted foo" && git checkout newbranch && git rm bar && git commit -m "Deleted bar" && git checkout master && git merge newbranch && git log --name-status --full-history -- bar
includes D bar
and A bar
for me in the log output with Git 2.12.2. Do you not see those lines in the output? What version do you have?
git version 2.15.1
Yes, your command sequence does report D bar
and A bar
. Perhaps my issue is particular to my file's history. I was tracing the history of a .htaccess
file that got gitignore'ed and removed. I finally figured that out and added the file back. When I include --name-status
in the git log
command, I see two A .htaccess
entries (since I added it back in the latest commit) but no D .htaccess
. So it seems in some cases, even though a file has been removed from the repository, git log
will not show an explicit D file_name
entry.
.htaccess
got added in a commit X but then not included in the merge commit that brought X onto master? That's the only thing I can think of that I could possibly argue ought to look like a file having been added and never deleted and yet still not being present. Would be interesting to try and figure out an MCVE, then figure out whether it's a Git bug, and if not, whether it's possible to tweak my answer to handle your case.
You can find the last commit which deleted file as follows:
git rev-list -n 1 HEAD -- [file_path]
Further information is available here
git rev-list -n 1 HEAD -- '*myfile.txt'
Git log but you need to prefix the path with --
Eg:
dan-mac:test dani$ git log file1.txt
fatal: ambiguous argument 'file1.txt': unknown revision or path not in the working tree.
dan-mac:test dani$ git log -- file1.txt
commit 0f7c4e1c36e0b39225d10b26f3dea40ad128b976
Author: Daniel Palacio <danpal@gmail.com>
Date: Tue Jul 26 23:32:20 2011 -0500
foo
I've just added a solution here (is there a way in git to list all deleted files in the repository?) for finding the commits of deleted files by using a regexp:
git log --diff-filter=D --summary | sed -n '/^commit/h;/\/some_dir\//{G;s/\ncommit \(.*\)/ \1/gp}'
This returns everything deleted within a directory named some_dir
(cascading). Any sed regexp there where \/some_dir\/
is will do.
OSX (thanks to @triplee and @keif)
git log --diff-filter=D --summary | sed -n -e '/^commit/h' -e '\:/:{' -e G -e 's/\ncommit \(.*\)/ \1/gp' -e }
sed: 1: "/^commit/h;/\/some_dir\ ...": bad flag in substitute command: '}'
sed
is apparently not always good with semicolons as command separators. Try changing them into newlines, or switch to sed -n -e '/^commit/h' -e '\:/some_dir/:{' -e G -e 's/\ncommit \(.*\)/ \1/gp' -e }
git log --diff-filter=D --summary | sed -n -e '/^commit/h' -e '\:/:{' -e G -e 's/\ncommit \(.*\)/ \1/gp' -e }
worked for me on OSX.
This command didn't work for me:
git log --full-history -1 -- [file path]
This is the command that worked:
git log --follow -- [file path]
Try:
git log --stat | grep file
grep -C 10
to get +-10 lines around the file name (which should include the git hash).
This works for me:
git log -- **/Myfile.cs
If include --full-history switch, it will show a lot of changes have nothing to do with the file. Not sure why.
Success story sharing
git log -- */<<filename>>.<<file extension>>
not knowing the entire file path.