If I want to see how foo.bar
looked like in some certain commit
then I can invoke:
git show :foo.bar
There's a package called git-timemachine that makes the process of viewing previous versions of a file almost completely seamless; see the link for installation instructions and a demo. (If you are already using MELPA, just do M-x package-install
RET git-timemachine
RET).
The way it works is, you call M-x git-timemachine
RET from a buffer visiting a tracked file. Then you can:
p
Visit previous historic versionn
Visit next historic versionw
Copy the abbreviated hash of the current historic versionW
Copy the full hash of the current historic versionq
Exit the time machine.
Note that if you know the hash of the commit you want to visit, the custom command from @phils' solution will serve you better for that specific use case. But for navigating between different versions of a file I find that using git-timemachine
is even easier than using the functionality that VC provides.
You can of course bind git-timemachine
to a key binding of your choice.
The canonical way to do that in Emacs is to use VC: C-x v ~
from the file's buffer will ask you for a revision and then show that file as it was at that revision. It should work for any control system supported by VC, such as Git, Bzr, ...
If you are viewing the commit in magit you can just press Enter on the file or part of the file you are interested in.
That's bound to log-view-find-revision
, and if we look at the code we see the critical bit is:
(switch-to-buffer (vc-find-revision file revision)))
So we could wrap that in a custom function like so:
(defun my-vc-visit-file-revision (file revision)
"Visit FILE as it was at REVISION."
(interactive
(list (expand-file-name
(read-file-name (if (buffer-file-name)
(format "File (%s): " (file-name-nondirectory
(buffer-file-name)))
"File: ")))
(read-string "Revision: ")))
(require 'vc)
(switch-to-buffer
(vc-find-revision file revision)))
Edit: Stefan has provided a better answer, but if you liked being able to select the file as well as the revision, here's a version of my function which maintains the interactive file selection, but uses the code from vc-revision-other-window
for the revision handling.
I concluded that using other-window by default does indeed make more sense, so I've done the same here -- unless you provide a prefix argument in which case it uses the current window.
(defun my-vc-visit-file-revision (file rev)
"Visit revision REV of FILE in another window.
With prefix argument, uses the current window instead.
If the current file is named `F', the revision is named `F.~REV~'.
If `F.~REV~' already exists, use it instead of checking it out again."
;; based on `vc-revision-other-window'.
(interactive
(let ((file (expand-file-name
(read-file-name
(if (buffer-file-name)
(format "File (%s): " (file-name-nondirectory
(buffer-file-name)))
"File: ")))))
(require 'vc)
(list file (if (vc-backend file)
(vc-read-revision
"Revision to visit (default is working revision): "
(list file))
(vc-read-revision "Revision to visit: " t
(or (vc-deduce-backend)
(vc-responsible-backend file)))))))
(require 'vc)
(let ((revision (if (string-equal rev "")
(if (vc-backend file)
(vc-working-revision file)
(error "No revision specified for unregistered file %s"
file))
rev))
(backend (or (vc-backend file)
(vc-deduce-backend)
(vc-responsible-backend file)))
(visit (if current-prefix-arg
'switch-to-buffer
'switch-to-buffer-other-window)))
(condition-case err
(funcall visit (vc-find-revision file revision backend))
;; The errors which can result when we request an invalid combination of
;; file and revision tend to be opaque side-effects of some unexpected
;; failure within the backend; so we simply trap everything and signal a
;; replacement error indicting the assumed cause.
(error (error "File not found at revision %s: %s" revision file)))))
I bind this command to C-xvC-f