简体   繁体   中英

How to recover code (a file) from a Git repository that has been deleted since

I have implemented a GUI tool that shows me all commits a certain object (let's call it "foo") was changed by. I can then select two commits and compare the two versions.

For that I need to recover the code (file) from the commits in question, obviously. I am using

show {commitHash}:/pathto/foo --find-renames

to recover the code from the two commits selected in the GUI.

This works fine as long as the code (the file) still exists. If it does not exist any more then I get the error message "Getting the revision for {commitHash} failed! Details: fatal: option --find-rename must come before non-option arguments" which is BTW not exactly helpful but anyway...

So my question is what to use instead of show , or which parameters to use in case show can actually do this in some other way.

You can run git cat-file blob COMMIT:RELATIVE-PATH . That should work for any revision COMMIT which points to a commit, provided that you specify a valid relative file name in that revision .

If the file doesn't exist in that revision, then you'll get something like the following:

fatal: Not a valid object name HEAD:not-here

Note that there is no option to consider renames here; this is a revision specification (see gitrevisions(7) ) which resolves directly to a blob name.

If you want to follow renames of the file, you're going to have to do a lot more scripting work. You can find the file name that a file was renamed to by doing something like the following:

git log -M --follow --diff-filter=R --format=tformat:%H -- RELATIVE-PATH | \
    head -n1 | \
    xargs git diff-tree -M --diff-filter=R --raw -z

That will show all the files that have been renamed in that commit; parse out the name that you want, and continue on with a new git log invocation, revision, and pathname in case the file has been renamed again.

Overall, this latter part may be more performant using a wrapper around libgit2's revision walker.

[given] all commits a certain object was changed by [...] I can then select two commits and compare the two versions.

So, you select two commits and want to follow any renames from the older to the newer when diffing?

When you generate this list, the easiest way to track the names will be

git log --raw --follow --oneline -- path/to/file

which will show you the pathnames and blob id's. Here's the recent history for git-legacy-stash.sh for instance:

7b556aa4b8 legacy stash: fix "rudimentary backport of -q"
:100755 100755 8a8c4a9270 f60e9b3e87 M  git-legacy-stash.sh
90a462725e stash: optionally use the scripted version again
:100755 100755 789ce2f41d 8a8c4a9270 R097       git-stash.sh    git-legacy-stash.sh
8d8e9c2a94 stash: add back the original, scripted `git stash`
:000000 100755 0000000000 789ce2f41d A  git-stash.sh
40af146834 stash: convert `stash--helper.c` into `stash.c`
:100755 000000 695f1feba3 0000000000 D  git-stash.sh
64fe9c26a4 stash: convert save to builtin
:100755 100755 51d7a06601 695f1feba3 M  git-stash.sh
d553f538b8 stash: convert push to builtin
:100755 100755 a9b3064ff0 51d7a06601 M  git-stash.sh
d4788af875 stash: convert create to builtin
:100755 100755 ff5556ccb0 a9b3064ff0 M  git-stash.sh
41e0dd55c4 stash: convert store to builtin
:100755 100755 d0318f859e ff5556ccb0 M  git-stash.sh

showing the old and new mode numbers and blob ids, the nature of the change, and any old and new name.

Then you can use the commit id and path in each commit, like

git diff 41e0dd5:git-stash.sh 7b556aa:git-legacy-stash.sh

or if for some reason you don't want text conversions and filters applied you can just use the blob id's directly.

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