git-checkout older revision of a file under a new name
Asked Answered
B

4

341

I have the file "main.cpp" open in my editor.

I want to see the previous revision of "main.cpp" in the editor too.

The way I do it now is like this.

close "main.cpp" in the editor

prompt> mv main.cpp tmp
prompt> git checkout HEAD^ main.cpp
prompt> mv main.cpp old_main.cpp
prompt> mv tmp main.cpp
prompt>

open "main.cpp" and "old_main.cpp" in the editor

Can it be simplified, so I don't have to close "main.cpp" in the editor?

What I'm hoping for is a variant of git-checkout that can do this.


UPDATE: im using git on mac osx 10.5.7

prompt> git --version
git version 1.6.0.4
prompt> 

UPDATE2: Jakub Narębski answer is:

prompt> git show HEAD^:dir1/dir2/dir3/main.cpp > old_main.cpp
prompt>

UPDATE3: Karmi's answer, for a specific revision:

prompt> git show 4c274dd91dc:higgs/Higgs.xcodeproj/project.pbxproj > old_project.pbxproj
prompt> 
Brachycephalic answered 20/5, 2009 at 14:52 Comment(5)
What editor do you use? Perhaps it has plugin / addon / module adding support for Git?Reannareap
I use textmate. It's has some git support, I haven't check though if it can do it.Brachycephalic
Textmate has git bundle: github.com/timcharper/git-tmbundle (you should have checked InterfacesFrontendsAndTools page on git wiki: git.or.cz/gitwiki )Reannareap
By the way, the VC interface in Emacs (for which Git also has support in the form of vc-git.el) has 'Show Other Version' command. If TexMate Git Bundle (git-tmbundle) doesn't have it, perhaps it would be worth to add it.Reannareap
I use symlinks for better keeping track of my project in TextMate. 25 dirs, 300 files. This helps hiding builddirs and other irrelevant dirs. However git/TextMate isn't too happy about these symlinks, so no :-(Brachycephalic
F
413

You can use git show for that:

git show HEAD^:main.cpp > old_main.cpp

(Note that there is colon [:] character between HEAD^ and main.cpp.) The <revision>:<path> syntax is described in git rev-parse manpage, next to last point in the "Specifying revisions" section:

<rev>:<path>, e.g. HEAD:README, :README, master:./README

A suffix : followed by a path names the blob or tree at the given path in the tree-ish object named by the part before the colon. :path (with an empty part before the colon) is a special case of the syntax described next: content recorded in the index at the given path. A path starting with ./ or ../ is relative to the current working directory. The given path will be converted to be relative to the working tree’s root directory. This is most useful to address a blob or tree from a commit or tree that has the same tree structure as the working tree.

Note that <path> here is FULL path relative to the top directory of your project, i.e. the directory with .git/ directory. (Or, to be more exact, to "<revision>", which in general can be any <tree-ish>, i.e. something that represents tree.)

If you want to use path relative to the current directory, you need to use ./<path> syntax (or ../path to go up from current directory).

Edit 2015-01-15: added information about relative path syntax


You can get in most cases the same output using low-level (plumbing) git cat-file command:

git cat-file blob HEAD^:main.cpp > old_main.cpp
Fugazy answered 20/5, 2009 at 15:27 Comment(14)
I'm interested in a full copy, but git-show shows me only the differences.. I have tried playing around with the --pretty option.. prompt> git show --pretty=fuller HEAD^ main.cpp but it didn't solve it.Brachycephalic
"git show HEAD^ main.cpp" (with space between HEAD^ and main.cpp) is DIFFERENT from "git show HEAD^:main.cpp" (with colon ':' between HEAD^ and main.cpp).Reannareap
Hmm, with colon I see this error, so I thought that the colon was a mistake. Yes it seems like colon is the way to go, but how do I resolve this? prompt> git show HEAD^:main.cpp fatal: ambiguous argument 'HEAD^:main.cpp': unknown revision or path not in the working tree. Use '--' to separate paths from revisions prompt>Brachycephalic
That probably means that you have given wrong PATHNAME (unfortunately because of "git show" magic git cannot give better error message). It should be FULL pathname relative to top directory of your project: $(git ls-tree -r --name-only HEAD^ | grep main.cpp)Reannareap
Absolute path from git root dir.. AHA. That works! prompt> git show HEAD^:dir1/dir2/dir3/main.cpp > old_main.cpp prompt> Thank you very much.Brachycephalic
What if you want to extend this problem to compare a directory or a tree containing a whole bunch of files? How would you do it?Unman
This is outside this question, which is about getting some other version of a file. Generally you use "git diff" (with appropriate options and limiters) for comparing directories.Reannareap
You may also want to scroll-bind the splits in vim: details on this stack overflow page.Stilbestrol
If you're working in DOS, instead of git reset --soft HEAD^ you'll need to use git reset --soft HEAD~1. The ^ is a continuation character in DOS so it won't work properly.Norvin
Instead of full path, from the docs: "A path starting with ./ or ../ is relative to the current working directory." Works on my 1.8.5 version.Scarecrow
@LVB, HEAD:./file etc. is available only in recent Git version. It was not present when writing this answer.Reannareap
@JakubNarębski Yep. It looks like it debuted in 1.7.4, so hopefully by now most people can take advantage of it.Scarecrow
This is a super-useful trick. I added it to my answer about how to check out any file from any commit hash, since it's so closely-related.Pinxit
This does not work when the file in question is a symlink. Instead, this creates a text file that has the symlink target. Symlinks are commonly used for example with git-annex, which manages large files in a separate content-addressable object store and replaces files with symlinks to that storeStravinsky
R
28

Just to add to Jakub's answer: you don't even have to redirect the output to a file with >, if you are only interested in skimming the file contents in the terminal. You can just run $ git show 58a3db6:path/to/your/file.txt.

Rania answered 20/5, 2009 at 21:18 Comment(0)
N
11

Single file use case

In order to get consistent checkout behavior including autocrlf etc., use a secondary folder (TEMP as example) and restore the file state from an older / different <commit> like this:

git --work-tree TEMP/ restore main.cpp -s <commit>
mv TEMP/main.cpp old_main.cpp

Use an alias to make it a one-line command

git restore-as old_main.cpp main.cpp -s <commit>

Create the alias:

git config --global alias.restore-as "!f() { git --work-tree /tmp/ restore $2 $3 $4 $5 $6 && mv -iv /tmp/$2 $1; }; f"

(Best replace /tmp/ with a directory reserved for such operations - e.g. /tmp/gitmv - after creating that.)

Note:

git show <commit>:main.cpp > old_main.cpp

.. will just produce a raw read from the repository.

Use a 2nd work tree - linked or anonymous

A long-term parallel working tree (linked / known to the repository with main worktree) can be used via git-worktree (new since git v2.6.7) and can have its HEAD on a different branch / <commit> :

git worktree add [<options>] <new-worktree-path> [<commit-ish>]

The worktree could be created without initial checkout (--no-checkout), and subsequently sparse-checkout could be configured, or just selected individual files / sub-dirs could be retrieved via git restore -s <commit> <file(s)/sub-dir>

Similarly an extra anonymous worktree (sharing the HEAD) could be created, by simply puting a file .git into it with content

gitdir: <MAIN-REPO-WORKTREE>/.git
Nurseryman answered 29/1, 2021 at 14:2 Comment(6)
As for me, this is the best approach, because I still use native way to get file content. With number of solutions through "git show" I'm still worry about encoding and other things. With this one I can save file at revision as I'm looking for. Thanks!Urina
Note: in the first approach ("Singular use case") "TEMP" folder should exist.Urina
Is the "Singular use case" approach dependent on an updated git version? I get an error that 'restore' is not a git command.Toscano
@Basya, restore is rather old. Git 2.23 Adds Switch and Restore CommandsNurseryman
The first command here is what I was looking for - how to extract a particular file from another commit into a temp folder. You don't have to extract the entire work tree. But is there any way to also specify the final location and filename to extract it with?Salaried
@Salaried , you can use a git alias restore-as e.g. to make the 2 liner (restore + mv) into a comfortable one-liner. I've added this here - and use it myself frequently. It asks for confirmation in case of an overwrite. The files paths are relative from the project root (not cwd when its deeper in)Nurseryman
A
0

I couldn't get @kxr's answer to work. And I find this to be simpler:

# Rename the current file.
mv path/to/file.txt path/to/file_current.txt`

# Checkout some other version of that file.
git checkout <commit> -- path/to/file.txt

This is better than the accepted answer which uses git show because it preserves invisible characters.

NOTE: in the code above, it's the "other version" of the file that will keep the original file name, and the current file is the one you have to rename.

Here is a git alias that does this in one line. This command actually makes the current file keep the original file name:

restore-as = "!mv $1 $1_tmp && git checkout $3 -- $1 && git restore --staged $1 && mv $1 $2 && mv $1_tmp $1 #" (see here for why we need the # suffix)

This can then be used as: git restore-as path/to/file.txt path/to/file_old.txt <commit>

Anesthesiology answered 21/12, 2022 at 21:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.