Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using emacs (and magit?) to visit a file in given commit/branch/etc

Tags:

git

emacs

magit

If I want to see how foo.bar looked like in some certain commit <COMMIT_ID> then I can invoke:

git show <COMMIT_ID>:foo.bar

Nice... How can I do it in emacs? Using magit? Using vc? Say I am visiting the file foo.bar and I want to look up how it looked in <COMMIT_ID>.

like image 933
Dror Avatar asked Aug 21 '14 07:08

Dror


4 Answers

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, ...

like image 128
Stefan Avatar answered Oct 23 '22 16:10

Stefan


  • C-xvl to view the file's history.
  • n and p to move between commits.
  • f to visit the file as of the commit at point.

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

like image 39
phils Avatar answered Oct 23 '22 16:10

phils


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 version
  • n Visit next historic version
  • w Copy the abbreviated hash of the current historic version
  • W Copy the full hash of the current historic version
  • q 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.

like image 9
itsjeyd Avatar answered Oct 23 '22 17:10

itsjeyd


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.

like image 7
Robin Green Avatar answered Oct 23 '22 15:10

Robin Green