How to retrieve a single file from a specific revision in Git?
Asked Answered
T

12

1078

I have a Git repository and I'd like to see how some files looked a few months ago. I found the revision at that date; it's 27cf8e84bb88e24ae4b4b3df2b77aab91a3735d8. I need to see what one file looks like, and also save it as a ("new") file.

I managed to see the file using gitk, but it doesn't have an option to save it. I tried with command-line tools, the closest I got was:

git-show 27cf8e84bb88e24ae4b4b3df2b77aab91a3735d8 my_file.txt

However, this command shows a diff, and not the file contents. I know I can later use something like PAGER=cat and redirect output to a file, but I don't know how to get to the actual file content.

Basically, I'm looking for something like svn cat.

Talky answered 4/3, 2009 at 11:43 Comment(8)
The key here: git show (unhelpfully) uses different syntax with a colon. git show 2c7cf:my_file.txtMythopoeic
To further clarify, the above command is asking git to show two separate objects, a revision and a file. The accepted answer below, that uses a colon between the two items is asking for a specific file at a specific revision.Pigmentation
On *nix you don't need PAGER, just shell output redirection with >Colonial
possible duplicate of Is there a quick git command to see an old version of a file?Territorialize
Checat has an important comment, for those who want the content exported to some file. You need something like this: git show {sha}:my_file.txt > old_my_file.txtFireman
Based on the question title, this seems like the right answer. Based on the rest of the question content, this answer seems most useful. Took me a few minutes to realize that the two seem pretty different, hopefully this comment saves time for others.Lamentable
@Lamentable No quite anymore: that would be (today, 2019) git restore, not git checkout. See my edited answer.Intyre
If you're looking for the answer of retrieving all files under a sub-directory in Git history revision, read this: https://mcmap.net/q/11540/-git-checkout-old-version-of-directory-under-new-nameNonsense
I
1020

Using git show

To complete your own answer, the syntax is indeed

git show object
git show $REV:$FILE
git show somebranch:from/the/root/myfile.txt
git show HEAD^^^:test/test.py

The command takes the usual style of revision, meaning you can use any of the following:

  1. branch name (as suggested by ash)
  2. HEAD + x number of ^ characters
  3. The SHA1 hash of a given revision
  4. The first few (maybe 5) characters of a given SHA1 hash

Tip It's important to remember that when using "git show", always specify a path from the root of the repository, not your current directory position.

(Although Mike Morearty mentions that, at least with git 1.7.5.4, you can specify a relative path by putting "./" at the beginning of the path. For example:

git show HEAD^^:./test.py

)

Using git restore

With Git 2.23+ (August 2019), you can also use git restore which replaces the confusing git checkout command

git restore -s <SHA1>     -- afile
git restore -s somebranch -- afile

That would restore on the working tree only the file as present in the "source" (-s) commit SHA1 or branch somebranch.
To restore also the index:

git restore -s <SHA1> -SW -- afile

(-SW: short for --staged --worktree)


As noted in the comments by starwarswii

It lets you pipe the contents into a file, which is great if you want to just quickly compare files from a commit.

E.g. you can do:

git show 1234:path/to/file.txt > new.txt 
git show 1234~:path/to/file.txt > old.txt

then compare them.


Using low-level git plumbing commands

Before git1.5.x, this was done with some plumbing:

git ls-tree <rev>
show a list of one or more 'blob' objects within a commit

git cat-file blob <file-SHA1>
cat a file as it has been committed within a specific revision (similar to svn cat). use git ls-tree to retrieve the value of a given file-sha1

git cat-file -p $(git-ls-tree $REV $file | cut -d " " -f 3 | cut -f 1)::

git-ls-tree lists the object ID for $file in revision $REV, this is cut out of the output and used as an argument to git-cat-file, which should really be called git-cat-object, and simply dumps that object to stdout.


Note: since Git 2.11 (Q4 2016), you can apply a content filter to the git cat-file output.

See commit 3214594, commit 7bcf341 (09 Sep 2016), commit 7bcf341 (09 Sep 2016), and commit b9e62f6, commit 16dcc29 (24 Aug 2016) by Johannes Schindelin (dscho).
(Merged by Junio C Hamano -- gitster -- in commit 7889ed2, 21 Sep 2016)

git config diff.txt.textconv "tr A-Za-z N-ZA-Mn-za-m <"
git cat-file --textconv --batch

Note: "git cat-file --textconv" started segfaulting recently (2017), which has been corrected in Git 2.15 (Q4 2017)

See commit cc0ea7c (21 Sep 2017) by Jeff King (peff).
(Merged by Junio C Hamano -- gitster -- in commit bfbc2fc, 28 Sep 2017)


As noted by Kira in the comments:

If you are using LFS files, you need to run the output of show through smudge:

git show master:blends/bigfile.blend | git lfs smudge > blends/bigfile.blend
Intyre answered 4/3, 2009 at 12:22 Comment(26)
How do you save what's returned by git show? I want to essentially do a "save as" on it instead of overwriting my uncommitted local file.Protomartyr
@Protomartyr since git show essentially dump the content on the stdout (standard output), you could simply redirect that output to any file you want (tldp.org/LDP/abs/html/io-redirection.html).Intyre
Isn't there a way without redirection?Paviour
@Paviour not that I know of.Intyre
git checkout [branch | revision] filepath is the right commandDuty
@Duty but git checkout would override your file by another version, as opposed to git show, which allows you to save it under a different name, in order for you to get and see both (the current version and the old version). It is unclear from the question if the OP wants to replace its current version by an old one.Intyre
I would like to note that ^^^ can also be written more generally as ~~~ or, better, ~3. Using tildes also has the advantage of not triggering the file name matching of some shells (zsh, for instance).Byelaw
I don't have a sufficiently old git to check: does pre-1.5.x git rev-parse handle rev:path syntax? (In a more recent git you can git cat-file -p $REV:path. However, git show works for directory paths as well, so it's not just shorter, it's usually closer to what one wants.)Naara
git show produced the diff-like output the question specifically didn't want. Is there a cleaner way than git checkout which overwrites the file and changes the version the local repository points at?Impellent
@Impellent Did you try git show? (git-scm.com/docs/git-show): It does show the file content, not a "diff-like output".Intyre
@Impellent That is, when you use git show with a filename, it shows the full content of the file. When used with a commit alone, it shows a diff.Intyre
I used git show hash filename(relative path), git show hash filename(full path), then just to be sure I cd'd to the directory the file was in and used git show hash filename(in the directory). All three version returned the same result: diff output, not the file.Impellent
@Impellent Interesting. Please ask a separate question with the exact Git commands typed and Git version used: that will allow other users to see what is going on.Intyre
@Impellent I hope you have already solved this, but for the reference, git show FileName produces a diff-like output, but git show HEAD:FileName should give the committed file contents.Apportionment
This is incredibly complicated compared to git checkout [branch | revision] filepath.Gaiety
@AndrewKoster No, after testing git restore, I find it more intuitive than git checkout, which deals with files and branches. Here, restore is only for restoring files.Intyre
As a super specific example, in Password Store, you can view a previous version of the password you just edited by running pass git show HEAD~1:yourpasswordname.gpgFoxglove
Why actually bother with ` -- ` within the examples of git restore?Callisto
@Callisto to separate parameter from path arguments. A good habit to have. I documented it 11 years ago: https://mcmap.net/q/11419/-deleting-a-badly-named-git-branchIntyre
What is restoring the index for a file?Tem
@Tem git restore -s <SHA1> -SW -- afile would restore both the working tree, and the index, which means it is like restoring the file, and do a git add -- file to add what you have just restored to the index, ready to be committed.Intyre
+1 to git show. It lets you pipe the contents into a file, which is great if you want to just quickly compare files from a commit. e.g. you can do: git show 1234:path/to/file.txt > new.txt git show 1234~:path/to/file.txt > old.txt, then compare them.Viafore
@Starwarswii Good point, thank you. I have included your comment in the answer for more visibility.Intyre
Very helpful. I stumbled over this minor aspect: When using git on windows, make sure to use the forward slash / (U+002F) not the backslash \ (U+005C)Parrott
If you are using LFS files, you need to run the output of show through smudge. So git show master:blends/bigfile.blend | git lfs smudge > blends/bigfile.blendPerforate
@Perforate Thank you for your feedback, good point. I have included your comment in the answer for more visibility.Intyre
N
562

If you wish to replace/overwrite the content of a file in your current branch with the content of the file from a previous commit or a different branch, you can do so with these commands:

git checkout 08618129e66127921fbfcbc205a06153c92622fe path/to/file.txt

or

git checkout mybranchname path/to/file.txt

You will then have to commit those changes in order for them to be effective in the current branch.

Nippon answered 18/11, 2010 at 19:41 Comment(6)
simplest solution and this is what git-checkout is designed for - specifying the pathname means only the matching file is checked out. From git-checkout man page: git checkout master~2 MakefileSquill
Then, how do you go back to the previous state before you run this command?Arvo
@Arvo if you are coming from the HEAD state it would be as simple as git checkout HEAD -- [full path].Zug
Note that this overwrites the existing file in that path, whereas the git show SHA1:PATH solution only prints to stdout.People
Nice! I wouldn't have been able to figure this out by looking at git help checkout. I had to checkout a subdirectory as of a certain date, and using this approach, I could get this syntax working: git checkout @{YYYY-MM-DD} sub-dirPerfectible
Silly mistake of mine, but if you use windows (sourcetree/mingw) note the path separator is / and not \ Sigler
T
170

You need to provide the full path to the file:

git show 27cf8e84bb88e24ae4b4b3df2b77aab91a3735d8:full/repo/path/to/my_file.txt
Talky answered 4/3, 2009 at 11:46 Comment(4)
doesn't have to be full path. Path from git root directory (those who came in git show --name-only is enough tooHassle
Erm, full path from repository root. Take a better look at example I've given. There is no leading slash before "full".Finitude
FYI, if you are in a subdir, you can use ./filename.ext successfully too.Ferrule
I think the point is, if you're in full/repo/path/to and you try: git show 27cf8e84:my_file.txt, you will be rewarded with a message like: fatal: Path 'full/repo/path/to/my_file.txt' exists, but not 'my_file.txt'. Did you mean '27cf8e84:full/repo/path/to/my_file.txt' aka '27cf8e84:./my_file.txt'? It's like, Git could have helped directly, but chose to be pedantic here.Interfertile
A
140

The easiest way is to write:

git show HASH:file/path/name.ext > some_new_name.ext

where:

  • HASH is the Git revision SHA-1 hash number
  • file/path/name.ext is name of the file you are looking for
  • some_new_name.ext is path and name where the old file should be saved

Example

git show 27cf8e84bb88e24ae4b4b3df2b77aab91a3735d8:my_file.txt > my_file.txt.OLD

This will save my_file.txt from revision 27cf8e as a new file with name my_file.txt.OLD

It was tested with Git 2.4.5.

If you want to retrieve deleted file you can use HASH~1 (one commit before specified HASH).

EXAMPLE:

git show 27cf8e84bb88e24ae4b4b3df2b77aab91a3735d8~1:deleted_file.txt > deleted_file.txt
Arietta answered 7/7, 2015 at 13:55 Comment(3)
Additional Info: You can get the HASH e.g. with git logSough
@Sough Thanks. I got all the HASH history for a particular file using git log file/path/name.extJunette
This is the right syntax. Important moments: 1. : makes Git output the file at revision (while a space in the same place would show diff). 2. The file/path/name.ext path has to be relative to the repository root.Feral
P
19
git checkout {SHA1} -- filename

this command get the copied file from specific commit.

Pizarro answered 28/9, 2019 at 19:42 Comment(1)
+1, I honestly have no idea - the top answer lists 5 different, vastly more convoluted approaches than a simple checkout command...Stidham
R
13

In Windows, with Git Bash:

  • in your workspace, change dir to the folder where your file lives
  • git show cab485c83b53d56846eb883babaaf4dff2f2cc46:./your_file.ext > old.ext
Reel answered 2/3, 2015 at 16:17 Comment(0)
U
9

In addition to all the options listed by other answers, you can use git reset with the Git object (hash, branch, HEAD~x, tag, ...) of interest and the path of your file:

git reset <hash> /path/to/file

In your example:

git reset 27cf8e8 my_file.txt

What this does is that it will revert my_file.txt to its version at the commit 27cf8e8 in the index while leaving it untouched (so in its current version) in the working directory.

From there, things are very easy:

  • you can compare the two versions of your file with git diff --cached my_file.txt
  • you can get rid of the old version of the file with git restore --staged file.txt (or, prior to Git v2.23, git reset file.txt) if you decide that you don't like it
  • you can restore the old version with git commit -m "Restore version of file.txt from 27cf8e8" and git restore file.txt (or, prior to Git v2.23, git checkout -- file.txt)
  • you can add updates from the old to the new version only for some hunks by running git add -p file.txt (then git commit and git restore file.txt).

Lastly, you can even interactively pick and choose which hunk(s) to reset in the very first step if you run:

git reset -p 27cf8e8 my_file.txt

So git reset with a path gives you lots of flexibility to retrieve a specific version of a file to compare with its currently checked-out version and, if you choose to do so, to revert fully or only for some hunks to that version.


Edit: I just realized that I am not answering your question since what you wanted wasn't a diff or an easy way to retrieve part or all of the old version but simply to cat that version.

Of course, you can still do that after resetting the file with:

git show :file.txt

to output to standard output or

git show :file.txt > file_at_27cf8e8.txt

But if this was all you wanted, running git show directly with git show 27cf8e8:file.txt as others suggested is of course much more direct.

I am going to leave this answer though because running git show directly allows you to get that old version instantly, but if you want to do something with it, it isn't nearly as convenient to do so from there as it is if you reset that version in the index.

Underneath answered 13/9, 2020 at 23:58 Comment(4)
What if the file is on another branch?Hymnology
So I want to retrieve a specific version of a specific file on a specific other branch. is this possible?Hymnology
Yes. Specific version and specific branch: you just need to find the corresponding hash first with, for instance git log --graph --oneline --all. Specific file: give the file path.Underneath
Remember that this will revert the file to the version corresponding to the hash in the index. Not in the working tree. If you want to restore that version in the working tree, you then need to run git commit and git restore <file>, as I mention in the answer.Underneath
P
8

And to nicely dump it into a file (on Windows at least) - Git Bash:

$ echo "`git show 60d8bdfc:src/services/LocationMonitor.java`" >> LM_60d8bdfc.java

The " quotes are needed so it preserves newlines.

Particle answered 1/1, 2014 at 16:56 Comment(10)
Nice one. +1. Good addition on the git show syntax I mention above.Intyre
I really don't understand why you would use echo, with or without the quotes. And I don't understand why you would want the append form of output redirection. Wouldn't it be better to simply write: git show 60d8bdfc:src/services/LocationMonitor.java > LM_60d8bdfc.java If for some reason you actually wanted to force dos-style line endings, you could pipe it through unix2dos. But I've never found it the least bit useful to retain dos line-endings on Windows, as any text tools other than notepad that I've used on Windows handle unix-style lines just fine.Girosol
git show 60d8bdfc:src/services/LocationMonitor.java >> LM_60d8bdfc.java worked for me.Donal
@Mike: are you on windows ?Particle
git show 60d8bdfc:./src/services/LocationMonitor.java > LM_60d8bdfc.java works for me in Ubuntu bash. Note the ./ after :Darindaring
do not use double quotes because if your file characters which look like a shell variable, i.e. $LANG, it will be replaced. @LưuVĩnhPhúc is saver. also do not use >> It will append the file if it existed and might to lead to errorsIdempotent
Based on the comments above, it looks like $ echo '`git show 60d8bdfc:src/services/LocationMonitor.java`' > LM_60d8bdfc.java is what the command should look like, to preserve newline format on Windows.Musaceous
@Musaceous Except now it won't actually run git show (if I understand correctly). Git Bash is just plain old bash underneath, so git show 60d8bdfc:src/services/LocationMonitor.java > LM_60d8bdfc.java should work just fine.Foxglove
@Ale, Hmm.. I am not sure why I concluded that - I failed to cite my sources correctly. I do not know if any comments have been deleted, or not. I vaguely recall seeing additional info, perhaps from another post? You may be correct. However, there are some caveats to "Git Bash is just plain old bash underneath". Not all bash commands are available in git bash. Also, IIRC, I think on windows an extra slash may be sometimes necessary, or single quotes to accommodate spaces in dir names. I have also run into a difference between using single vs double quotes. Unless that's only for config files?Musaceous
@Musaceous Yeah, there are some differences in the environment but the shell (and so the syntax and features like output redirect) should work in the same way as on *nix systems ;-)Foxglove
H
5

This will help you get all deleted files between commits without specifying the path, useful if there are a lot of files deleted.

git diff --name-only --diff-filter=D $commit~1 $commit | xargs git checkout $commit~1
Hedge answered 24/7, 2014 at 2:34 Comment(0)
R
2

Using git show $REV:$FILE as it was already pointed out by others is probably the right answer. I'm posting another answer because when I tried this method, I sometimes got the following error from git:

fatal: path 'link/foo' exists on disk, but not in 'HEAD'

The problem occurs when parts of the path to the file are a symbolic link. In those cases, the git show $REV:$FILE method will not work. Steps to reproduce:

$ git init .
$ mkdir test
$ echo hello > test/foo
$ ln -s test link
$ git add .
$ git commit -m "initial commit"
$ cat link/foo
hello
$ git show HEAD:link/foo
fatal: path 'link/foo' exists on disk, but not in 'HEAD'

The problem is, that utilities like realpath don't help here, because the symlink might not exist in the current commit anymore. I don't know about a good general solution. In my case, I knew that the symlink could only exist in the first component of the path, so I solved the problem by using the git show $REV:$FILE method twice. This works, because when git show $REV:$FILE is used on a symlink, then its target gets printed:

$ git show HEAD:link
test

Whereas with directories, the command will output a header, followed by the directory content:

$ git show HEAD:test
tree HEAD:test

foo

So in my case, I just checked the output of the first call to git show $REV:$FILE and if it was only a single line, then I replaced the first component of my path with the result to resolve the symlink through git.

Ridenhour answered 23/12, 2020 at 11:42 Comment(0)
D
0

Get the file from a previous commit through checking-out previous commit and copying file.

  • Note which branch you are on: git branch
  • Checkout the previous commit you want: git checkout 27cf8e84bb88e24ae4b4b3df2b77aab91a3735d8
  • Copy the file you want to a temporary location
  • Checkout the branch you started from: git checkout theBranchYouNoted
  • Copy in the file you placed in a temporary location
  • Commit your change to git: git commit -m "added file ?? from previous commit"
Demonstrable answered 22/7, 2011 at 15:5 Comment(1)
I am not sure why this answer got so many downvotes. It certainly isn't pretty and it clearly isn't the best way to do this. But it does get there.Underneath
M
-2

If you prefer to use GUI to do this, the following will work if you know when approximately the file got changed.

On Visual Studio with Git Extension installed:

  1. Git -> View Branch History
  2. Right click on the commit where the file got changed, then choose View Commit Details
  3. On list of changes on the right, click on the file of interest. You'll see side by side comparison of full file content before and after the change. Just select all and copy the file content and paste to anywhere you want.
Magallanes answered 12/7, 2022 at 16:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.