+++ /dev/null
-;;; git-timemachine-autoloads.el --- automatically extracted autoloads
-;;
-;;; Code:
-
-(add-to-list 'load-path (directory-file-name
- (or (file-name-directory #$) (car load-path))))
-
-\f
-;;;### (autoloads nil "git-timemachine" "git-timemachine.el" (0 0
-;;;;;; 0 0))
-;;; Generated autoloads from git-timemachine.el
-
-(autoload 'git-timemachine-toggle "git-timemachine" "\
-Toggle git timemachine mode.
-
-\(fn)" t nil)
-
-(autoload 'git-timemachine "git-timemachine" "\
-Enable git timemachine for file of current buffer.
-
-\(fn)" t nil)
-
-(autoload 'git-timemachine-switch-branch "git-timemachine" "\
-Enable git timemachine for current buffer, switching to GIT-BRANCH.
-
-\(fn GIT-BRANCH)" t nil)
-
-(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "git-timemachine" '("git-timemachine-")))
-
-;;;***
-\f
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; coding: utf-8
-;; End:
-;;; git-timemachine-autoloads.el ends here
+++ /dev/null
-;;; -*- no-byte-compile: t -*-
-(define-package "git-timemachine" "20181204.1546" "Walk through git revisions of a file" '((emacs "24.3")) :commit "3a4a7f11af613090122d1d4bb8823eaeed93398f" :keywords '("git") :authors '(("Peter Stiernström" . "peterNOSPAMstiernstrom.se")) :maintainer '("Peter Stiernström" . "peterNOSPAMstiernstrom.se") :url "https://gitlab.com/pidu/git-timemachine")
+++ /dev/null
-;;; git-timemachine.el --- Walk through git revisions of a file
-
-;; Copyright (C) 2014 Peter Stiernström
-
-;; Author: Peter Stiernström <peterNOSPAMstiernstrom.se>
-;; Version: 4.8
-;; Package-Version: 20181204.1546
-;; URL: https://gitlab.com/pidu/git-timemachine
-;; Keywords: git
-;; Package-Requires: ((emacs "24.3"))
-
-;; This file is not part of GNU Emacs
-
-;; This program is free software: you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation, either version 3 of the License, or
-;; (at your option) any later version.
-
-;; This program is distributed in the hope that it will be useful,
-;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-;; GNU General Public License for more details.
-
-;; You should have received a copy of the GNU General Public License
-;; along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;;; Use git-timemachine to browse historic versions of a file with p
-;;; (previous) and n (next).
-
-;;; Code:
-
-(require 'vc-git)
-(require 'cl-lib)
-
-(defcustom git-timemachine-abbreviation-length 12
- "Number of chars from the full sha1 hash to use for abbreviation."
- :type 'integer
- :group 'git-timemachine)
-
-(defcustom git-timemachine-show-minibuffer-details t
- "Non-nil means that details of the commit (its hash and date)
-will be shown in the minibuffer while navigating commits."
- :type 'boolean
- :group 'git-timemachine)
-
-(defface git-timemachine-commit
- '((default :weight bold))
- "Face for git timemachine commit sha"
- :group 'git-timemachine)
-
-(defface git-timemachine-minibuffer-detail-face
- '((((class color) (background dark))
- :foreground "yellow")
- (((class color) (background light))
- :foreground "yellow4"))
- "How to display the minibuffer detail"
- :group 'git-timemachine)
-
-(defface git-timemachine-minibuffer-author-face
- '((((class color) (background dark))
- :foreground "orange")
- (((class color) (background light))
- :foreground "DarkOrange4"))
- "How to display the author in minibuffer"
- :group 'git-timemachine)
-
-(defcustom git-timemachine-minibuffer-detail
- 'subject
- "What to display when `git-timemachine-show-minibuffer-details` is t.
-Available values are:
-`commit` : The SHA hash of the commit
-`subject`: The subject of the commit message"
- :type '(radio (const :tag "Commit SHA" commit) (const :tag "Commit Subject" subject))
- :group 'git-timemachine)
-
-(defcustom git-timemachine-show-author
- t
- "Prepend author to minibuffer details."
- :type 'boolean
- :group 'git-timemachine)
-
-(defcustom git-timemachine-global-git-arguments
- '("-c" "log.showSignature=false" "--no-pager")
- "Common arguments for all git commands."
- :type 'list
- :group 'git-timemachine)
-
-(defcustom git-timemachine-quit-to-invoking-buffer
- t
- "Switch to invoking buffer on ‘git-timemachine-quit’."
- :type 'boolean
- :group 'git-timemachine)
-
-(defvar-local git-timemachine-directory nil)
-(defvar-local git-timemachine-revision nil)
-(defvar-local git-timemachine-file nil)
-(defvar-local git-timemachine--revisions-cache nil)
-
-(defun git-timemachine-completing-read-fn (&rest args)
- "Apply ARGS to `ido-completing-read' if available and fall back to `completing-read'."
- (cond
- ((fboundp 'ivy-read) (apply 'ivy-read args))
- ((fboundp 'ido-completing-read) (apply 'ido-completing-read args))
- (t (apply 'completing-read args))))
-
-(defun git-timemachine--process-file (&rest args)
- "Run ‘process-file’ with ARGS and ‘git-timemachine-global-git-arguments’ applied."
- (apply #'process-file vc-git-program nil t nil (append git-timemachine-global-git-arguments args)))
-
-(defun git-timemachine--revisions (&optional git-branch)
- "List git revisions of current buffers file.
-
-When passed a GIT-BRANCH, lists revisions from that branch."
- (if git-timemachine--revisions-cache
- git-timemachine--revisions-cache
- (setq git-timemachine--revisions-cache
- (prog2
- (message "Fetching Revisions...")
- (let ((default-directory git-timemachine-directory)
- (file git-timemachine-file))
- (with-temp-buffer
-
- (unless (zerop (if git-branch
- (git-timemachine--process-file "log" git-branch "--name-only" "--follow" "--pretty=format:%H%x00%ar%x00%ad%x00%s%x00%an" "--" file)
- (git-timemachine--process-file "log" "--name-only" "--follow" "--pretty=format:%H%x00%ar%x00%ad%x00%s%x00%an" "--" file)))
- (error "Git log command exited with non-zero exit status for file: %s" file))
-
- (goto-char (point-min))
- (let ((lines)
- (commit-number (/ (1+ (count-lines (point-min) (point-max))) 3)))
- (while (not (eobp))
- (let ((line (buffer-substring-no-properties (line-beginning-position) (line-end-position))))
- (string-match "\\([^\0]*\\)\0\\([^\0]*\\)\0\\([^\0]*\\)\0\\(.*\\)\0\\(.*\\)" line)
- (let ((commit (match-string 1 line))
- (date-relative (match-string 2 line))
- (date-full (match-string 3 line))
- (subject (match-string 4 line))
- (author (match-string 5 line)))
- (forward-line 1)
- (let ((file-name (buffer-substring-no-properties (line-beginning-position) (line-end-position))))
- (push (list commit file-name commit-number date-relative date-full subject author) lines))))
- (setq commit-number (1- commit-number))
- (forward-line 2))
- (nreverse lines))))
- (message "Fetching Revisions...done")))))
-
-(defun git-timemachine-show-current-revision ()
- "Show last (current) revision of file."
- (interactive)
- (git-timemachine-show-revision (car (git-timemachine--revisions))))
-
-(defun git-timemachine-show-latest-revision-in-branch (git-branch)
- "Show last (current) revision of file in GIT-BRANCH."
- (interactive "MGit branch: ")
- (git-timemachine-show-revision (car (git-timemachine--revisions git-branch))))
-
-(defun git-timemachine--next-revision (revisions)
- "Return the revision following the current revision in REVISIONS."
- (or (cadr (cl-member (car git-timemachine-revision) revisions :key #'car :test #'string=))
- (car (reverse revisions))))
-
-(defun git-timemachine-show-previous-revision ()
- "Show previous revision of file."
- (interactive)
- (let ((new-line nil)
- (curr-revision git-timemachine-revision)
- (new-revision (git-timemachine--next-revision (git-timemachine--revisions)))
- (cursor-win-pos (git-timemachine--get-cursor-position)))
- (setq new-line (git-timemachine--find-new-current-line curr-revision new-revision (line-number-at-pos)))
- (git-timemachine-show-revision new-revision)
- (forward-line (- new-line (line-number-at-pos)))
- (git-timemachine--set-cursor-position cursor-win-pos)))
-
-(defun git-timemachine-show-next-revision ()
- "Show next revision of file."
- (interactive)
- (let ((new-line nil)
- (curr-revision git-timemachine-revision)
- (new-revision (git-timemachine--next-revision (reverse (git-timemachine--revisions))))
- (cursor-win-pos (git-timemachine--get-cursor-position)))
- (setq new-line (git-timemachine--find-new-current-line curr-revision new-revision (line-number-at-pos)))
- (git-timemachine-show-revision new-revision)
- (forward-line (- new-line (line-number-at-pos)))
- (git-timemachine--set-cursor-position cursor-win-pos)))
-
-(defun git-timemachine-show-revision-fuzzy ()
- "Show the revision with the chosen commit message."
- (interactive)
- (let* ((revisions (git-timemachine--revisions))
- (wanted
- (funcall #'git-timemachine-completing-read-fn "Commit message: "
- (mapcar (apply-partially #'nth 5) revisions))))
- (git-timemachine-show-revision
- (cl-find wanted revisions
- :key (apply-partially #'nth 5)
- :test #'equal))))
-
-(defun git-timemachine-show-nth-revision (rev-number)
- "Show the REV-NUMBER revision."
- (interactive "nEnter revision number: ")
- (let* ((revisions (reverse (git-timemachine--revisions)))
- (num-revisions (length revisions))
- (curr-revision git-timemachine-revision)
- (new-revision (nth (1- rev-number) revisions))
- (new-line nil)
- (cursor-win-pos (git-timemachine--get-cursor-position)))
- (if (not new-revision)
- (message "Only %d revisions exist." num-revisions)
- (setq new-line (git-timemachine--find-new-current-line curr-revision new-revision (line-number-at-pos)))
- (git-timemachine-show-revision new-revision)
- (forward-line (- new-line (line-number-at-pos)))
- (git-timemachine--set-cursor-position cursor-win-pos))))
-
-(defun git-timemachine-show-revision (revision)
- "Show a REVISION (commit hash) of the current file."
- (when revision
- (let ((current-position (point))
- (commit (car revision))
- (revision-file-name (nth 1 revision))
- (commit-index (nth 2 revision))
- (date-relative (nth 3 revision))
- (date-full (nth 4 revision))
- (subject (nth 5 revision)))
- (setq buffer-read-only nil)
- (erase-buffer)
- (let ((default-directory git-timemachine-directory)
- (process-coding-system-alist (list (cons "" (cons buffer-file-coding-system default-process-coding-system)))))
- (git-timemachine--process-file "show" (concat commit ":" revision-file-name)))
- (setq buffer-read-only t)
- (set-buffer-modified-p nil)
- (let* ((revisions (git-timemachine--revisions))
- (n-of-m (format "(%d/%d %s)" commit-index (length revisions) date-relative)))
- (setq mode-line-buffer-identification
- (list (propertized-buffer-identification "%12b") "@"
- (propertize (git-timemachine-abbreviate commit) 'face 'git-timemachine-commit) " name:" revision-file-name" " n-of-m)))
- (setq git-timemachine-revision revision)
- (goto-char current-position)
- (when git-timemachine-show-minibuffer-details
- (git-timemachine--show-minibuffer-details revision)))))
-
-(defun git-timemachine--show-minibuffer-details (revision)
- "Show details for REVISION in minibuffer."
- (let* ((date-relative (nth 3 revision))
- (date-full (nth 4 revision))
- (author (if git-timemachine-show-author (concat (nth 6 revision) ": ") ""))
- (sha-or-subject (if (eq git-timemachine-minibuffer-detail 'commit) (car revision) (nth 5 revision))))
- (message "%s%s [%s (%s)]"
- (propertize author 'face 'git-timemachine-minibuffer-author-face)
- (propertize sha-or-subject 'face 'git-timemachine-minibuffer-detail-face) date-full date-relative)))
-
-(defun git-timemachine--find-new-current-line (curr-revision new-revision current-line)
- "Return the new current line after a revision jump."
- (let* ((revisions (reverse (git-timemachine--revisions)))
- (current-commit (car curr-revision))
- (curr-rev-number (+ (or (cl-position curr-revision revisions) 0) 1))
- (new-commit (car new-revision))
- (new-rev-number (+ (or (cl-position new-revision revisions) 0) 1))
- (new-line nil)
- (file git-timemachine-file)
- (reverse (< curr-rev-number new-rev-number)))
- ;; If no commit change, do nothing
- (if (= curr-rev-number new-rev-number)
- current-line
- ;; Get new current line number using `git-blame`
- (with-temp-buffer
- (if reverse
- (git-timemachine--process-file "blame" "--reverse" "-n" (format "-L %s,%s" current-line current-line) file (format "%s..%s" current-commit new-commit))
- (git-timemachine--process-file "blame" "-n" (format "-L %s,%s" current-line current-line) file (format "%s..%s" new-commit current-commit)))
- (goto-char (point-min))
- ;; If end-of-buffer problem
- (when (search-forward-regexp "^fatal: file .+ has only .+ lines" nil t)
- (setq current-line (- current-line 1))
- (erase-buffer)
- (if reverse
- (git-timemachine--process-file "blame" "--reverse" "-n" (format "-L %s,%s" current-line current-line) file (format "%s..%s" current-commit new-commit))
- (git-timemachine--process-file "blame" "-n" (format "-L %s,%s" current-line current-line) file (format "%s..%s" new-commit current-commit))))
- (goto-char (point-min))
- (search-forward-regexp "^[^ ]+ \\([^ ]+\\)")
- (setq new-line (string-to-number (match-string 1)))
- ;; In case git blame doesn't give what we expect
- (when (= new-line 0) (setq new-line current-line))
- new-line))))
-
-(defun git-timemachine--get-cursor-position ()
- "Return the cursor visual line number with respect to the
-current window first line"
- (let* ((win-point-min (save-excursion (move-to-window-line 0) (point)))
- (cur-pos (count-screen-lines win-point-min (point))))
- cur-pos))
-
-(defun git-timemachine--set-cursor-position (POS)
- "Set the cursor position to the POS visual line with
-respect to the window first line"
- (recenter POS))
-
-(defun git-timemachine-abbreviate (revision)
- "Return REVISION abbreviated to `git-timemachine-abbreviation-length' chars."
- (substring revision 0 git-timemachine-abbreviation-length))
-
-(defun git-timemachine-quit ()
- "Exit the timemachine."
- (interactive)
- (let ((parent-buffer-name buffer-file-name))
- (kill-buffer)
- (let ((parent-buffer (find-buffer-visiting parent-buffer-name)))
- (when (and parent-buffer git-timemachine-quit-to-invoking-buffer)
- (switch-to-buffer parent-buffer nil t)))))
-
-(defun git-timemachine-blame ()
- "Call magit-blame on current revision."
- (interactive)
- (if (fboundp 'magit-blame)
- (let ((magit-buffer-revision (car git-timemachine-revision)))
- (magit-blame))
- (message "You need to install magit for blame capabilities")))
-
-(defun git-timemachine-kill-revision ()
- "Kill the current revisions abbreviated commit hash."
- (interactive)
- (let ((revision (car git-timemachine-revision)))
- (message revision)
- (kill-new revision)))
-
-(defun git-timemachine-kill-abbreviated-revision ()
- "Kill the current revisions full commit hash."
- (interactive)
- (let ((revision (git-timemachine-abbreviate (car git-timemachine-revision))))
- (message revision)
- (kill-new revision)))
-
-(defun git-timemachine-show-commit ()
- "Show commit for current revision."
- (interactive)
- (let ((rev (car git-timemachine-revision)))
- (if (fboundp 'magit-revision-mode)
- (progn
- (with-temp-buffer
- (save-excursion (magit-mode-setup #'magit-revision-mode rev nil nil nil))))
- (message "You need to install magit to show commit"))))
-
-(define-minor-mode git-timemachine-mode
- "Git Timemachine, feel the wings of history."
- :init-value nil
- :lighter " Timemachine"
- :keymap
- '(("p" . git-timemachine-show-previous-revision)
- ("n" . git-timemachine-show-next-revision)
- ("g" . git-timemachine-show-nth-revision)
- ("t" . git-timemachine-show-revision-fuzzy)
- ("q" . git-timemachine-quit)
- ("w" . git-timemachine-kill-abbreviated-revision)
- ("W" . git-timemachine-kill-revision)
- ("b" . git-timemachine-blame)
- ("c" . git-timemachine-show-commit))
- :group 'git-timemachine)
-
-(defun git-timemachine-validate (file)
- "Validate that there is a FILE and that it belongs to a git repository.
-Call with the value of 'buffer-file-name."
- (unless file
- (error "This buffer is not visiting a file"))
- (unless (vc-git-registered file)
- (error "This file is not git tracked")))
-
-(defun git-timemachine--start (get-revision-fn)
- "Setup a timemachine buffer and populate it from the result of GET-REVISION-FN."
- (setq git-timemachine--revisions-cache nil)
- (git-timemachine-validate (buffer-file-name))
- (let ((git-directory (expand-file-name (vc-git-root (buffer-file-name))))
- (file-name (buffer-file-name))
- (timemachine-buffer (format "timemachine:%s" (buffer-name)))
- (cur-line (line-number-at-pos))
- (cursor-position (git-timemachine--get-cursor-position))
- (new-line nil)
- (mode major-mode)
- (coding-system buffer-file-coding-system))
- (with-current-buffer (get-buffer-create timemachine-buffer)
- (switch-to-buffer timemachine-buffer)
- (setq buffer-file-name file-name)
- (setq buffer-file-coding-system coding-system)
- (funcall mode)
- (setq git-timemachine-directory git-directory
- git-timemachine-file (file-relative-name file-name git-directory)
- git-timemachine-revision nil)
- (funcall get-revision-fn)
- (setq new-line (git-timemachine--find-new-current-line git-timemachine-revision (list "HEAD" "" 0 "" "" "" "") cur-line)) ;; Allow to stay on the same line
- (goto-char (point-min))
- (forward-line (- new-line 1))
- (git-timemachine--set-cursor-position cursor-position)
- (git-timemachine-mode))))
-
-;;;###autoload
-(defun git-timemachine-toggle ()
- "Toggle git timemachine mode."
- (interactive)
- (if (bound-and-true-p git-timemachine-mode)
- (git-timemachine-quit)
- (git-timemachine)))
-
-;;;###autoload
-(defun git-timemachine ()
- "Enable git timemachine for file of current buffer."
- (interactive)
- (git-timemachine--start #'git-timemachine-show-current-revision))
-
-;;;###autoload
-(defun git-timemachine-switch-branch (git-branch)
- "Enable git timemachine for current buffer, switching to GIT-BRANCH."
- (interactive (list (git-timemachine-completing-read-fn "Branch to switch to: "(vc-git-branches))))
- (git-timemachine--start (lambda () (git-timemachine-show-latest-revision-in-branch git-branch))))
-
-(provide 'git-timemachine)
-
-;;; git-timemachine.el ends here
--- /dev/null
+;;; git-timemachine-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path))))
+\f
+;;;### (autoloads nil "git-timemachine" "git-timemachine.el" (23994
+;;;;;; 46479 777399 176000))
+;;; Generated autoloads from git-timemachine.el
+
+(autoload 'git-timemachine-toggle "git-timemachine" "\
+Toggle git timemachine mode.
+
+\(fn)" t nil)
+
+(autoload 'git-timemachine "git-timemachine" "\
+Enable git timemachine for file of current buffer.
+
+\(fn)" t nil)
+
+(autoload 'git-timemachine-switch-branch "git-timemachine" "\
+Enable git timemachine for current buffer, switching to GIT-BRANCH.
+
+\(fn GIT-BRANCH)" t nil)
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; git-timemachine-autoloads.el ends here
--- /dev/null
+;;; -*- no-byte-compile: t -*-
+(define-package "git-timemachine" "20190730.849" "Walk through git revisions of a file" '((emacs "24.3") (transient "0.1.0")) :commit "391eb61050de321101e631fcf373fc70ec6e7700" :keywords '("vc") :authors '(("Peter Stiernström" . "peterNOSPAMstiernstrom.se")) :maintainer '("Peter Stiernström" . "peterNOSPAMstiernstrom.se") :url "https://gitlab.com/pidu/git-timemachine")
--- /dev/null
+;;; git-timemachine.el --- Walk through git revisions of a file
+
+;; Copyright (C) 2014 Peter Stiernström
+
+;; Author: Peter Stiernström <peterNOSPAMstiernstrom.se>
+;; Version: 4.11
+;; Package-Version: 20190730.849
+;; URL: https://gitlab.com/pidu/git-timemachine
+;; Keywords: vc
+;; Package-Requires: ((emacs "24.3") (transient "0.1.0"))
+
+;; This file is not part of GNU Emacs
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Use git-timemachine to browse historic versions of a file with p
+;;; (previous) and n (next).
+
+;;; Code:
+
+(require 'vc-git)
+(require 'cl-lib)
+(require 'transient)
+
+(defcustom git-timemachine-abbreviation-length 12
+ "Number of chars from the full sha1 hash to use for abbreviation."
+ :type 'integer
+ :group 'git-timemachine)
+
+(defcustom git-timemachine-show-minibuffer-details t
+ "Non-nil means that details of the commit (its hash and date)
+will be shown in the minibuffer while navigating commits."
+ :type 'boolean
+ :group 'git-timemachine)
+
+(defface git-timemachine-commit
+ '((default :weight bold))
+ "Face for git timemachine commit sha"
+ :group 'git-timemachine)
+
+(defface git-timemachine-minibuffer-detail-face
+ '((((class color) (background dark))
+ :foreground "yellow")
+ (((class color) (background light))
+ :foreground "yellow4"))
+ "How to display the minibuffer detail"
+ :group 'git-timemachine)
+
+(defface git-timemachine-minibuffer-author-face
+ '((((class color) (background dark))
+ :foreground "orange")
+ (((class color) (background light))
+ :foreground "DarkOrange4"))
+ "How to display the author in minibuffer"
+ :group 'git-timemachine)
+
+(defcustom git-timemachine-minibuffer-detail
+ 'subject
+ "What to display when `git-timemachine-show-minibuffer-details` is t.
+Available values are:
+`commit` : The SHA hash of the commit
+`subject`: The subject of the commit message"
+ :type '(radio (const :tag "Commit SHA" commit) (const :tag "Commit Subject" subject))
+ :group 'git-timemachine)
+
+(defcustom git-timemachine-show-author
+ t
+ "Prepend author to minibuffer details."
+ :type 'boolean
+ :group 'git-timemachine)
+
+(defcustom git-timemachine-global-git-arguments
+ '("-c" "log.showSignature=false" "--no-pager")
+ "Common arguments for all git commands."
+ :type 'list
+ :group 'git-timemachine)
+
+(defcustom git-timemachine-quit-to-invoking-buffer
+ t
+ "Switch to invoking buffer on ‘git-timemachine-quit’."
+ :type 'boolean
+ :group 'git-timemachine)
+
+(defvar-local git-timemachine-directory nil)
+(defvar-local git-timemachine-revision nil)
+(defvar-local git-timemachine-file nil)
+(defvar-local git-timemachine--revisions-cache nil)
+
+(defun git-timemachine-completing-read-fn (&rest args)
+ "Apply ARGS to `ido-completing-read' if available and fall back to `completing-read'."
+ (cond
+ ((fboundp 'ivy-read) (apply 'ivy-read args))
+ ((fboundp 'ido-completing-read) (apply 'ido-completing-read args))
+ (t (apply 'completing-read args))))
+
+(defun git-timemachine--process-file (&rest args)
+ "Run ‘process-file’ with ARGS and ‘git-timemachine-global-git-arguments’ applied."
+ (apply #'process-file vc-git-program nil t nil (append git-timemachine-global-git-arguments args)))
+
+(defun git-timemachine--revisions (&optional git-branch)
+ "List git revisions of current buffers file.
+
+When passed a GIT-BRANCH, lists revisions from that branch."
+ (if git-timemachine--revisions-cache
+ git-timemachine--revisions-cache
+ (setq git-timemachine--revisions-cache
+ (prog2
+ (message "Fetching Revisions...")
+ (let ((default-directory git-timemachine-directory)
+ (file git-timemachine-file))
+ (with-temp-buffer
+
+ (unless (zerop (if git-branch
+ (git-timemachine--process-file "log" git-branch "--name-only" "--follow" "--pretty=format:%H%x00%ar%x00%ad%x00%s%x00%an" "--" file)
+ (git-timemachine--process-file "log" "--name-only" "--follow" "--pretty=format:%H%x00%ar%x00%ad%x00%s%x00%an" "--" file)))
+ (error "Git log command exited with non-zero exit status for file: %s" file))
+
+ (goto-char (point-min))
+ (let ((lines)
+ (commit-number (/ (1+ (count-lines (point-min) (point-max))) 3)))
+ (while (not (eobp))
+ (let ((line (buffer-substring-no-properties (line-beginning-position) (line-end-position))))
+ (string-match "\\([^\0]*\\)\0\\([^\0]*\\)\0\\([^\0]*\\)\0\\(.*\\)\0\\(.*\\)" line)
+ (let ((commit (match-string 1 line))
+ (date-relative (match-string 2 line))
+ (date-full (match-string 3 line))
+ (subject (match-string 4 line))
+ (author (match-string 5 line)))
+ (forward-line 1)
+ (let ((file-name (buffer-substring-no-properties (line-beginning-position) (line-end-position))))
+ (push (list commit file-name commit-number date-relative date-full subject author) lines))))
+ (setq commit-number (1- commit-number))
+ (forward-line 2))
+ (nreverse lines))))
+ (message "Fetching Revisions...done")))))
+
+(defun git-timemachine-show-current-revision ()
+ "Show last (current) revision of file."
+ (interactive)
+ (git-timemachine-show-revision (car (git-timemachine--revisions))))
+
+(defun git-timemachine-show-latest-revision-in-branch (git-branch)
+ "Show last (current) revision of file in GIT-BRANCH."
+ (interactive "MGit branch: ")
+ (git-timemachine-show-revision (car (git-timemachine--revisions git-branch))))
+
+(defun git-timemachine--next-revision (revisions)
+ "Return the revision following the current revision in REVISIONS."
+ (or (cadr (cl-member (car git-timemachine-revision) revisions :key #'car :test #'string=))
+ (car (reverse revisions))))
+
+(defun git-timemachine-show-previous-revision ()
+ "Show previous revision of file."
+ (interactive)
+ (let ((new-line nil)
+ (curr-revision git-timemachine-revision)
+ (new-revision (git-timemachine--next-revision (git-timemachine--revisions)))
+ (cursor-win-pos (git-timemachine--get-cursor-position)))
+ (setq new-line (git-timemachine--find-new-current-line curr-revision new-revision (line-number-at-pos)))
+ (git-timemachine-show-revision new-revision)
+ (forward-line (- new-line (line-number-at-pos)))
+ (git-timemachine--set-cursor-position cursor-win-pos)))
+
+(defun git-timemachine-show-next-revision ()
+ "Show next revision of file."
+ (interactive)
+ (let ((new-line nil)
+ (curr-revision git-timemachine-revision)
+ (new-revision (git-timemachine--next-revision (reverse (git-timemachine--revisions))))
+ (cursor-win-pos (git-timemachine--get-cursor-position)))
+ (setq new-line (git-timemachine--find-new-current-line curr-revision new-revision (line-number-at-pos)))
+ (git-timemachine-show-revision new-revision)
+ (forward-line (- new-line (line-number-at-pos)))
+ (git-timemachine--set-cursor-position cursor-win-pos)))
+
+(defun git-timemachine-show-revision-fuzzy ()
+ "Show the revision with the chosen commit message."
+ (interactive)
+ (let* ((revisions (git-timemachine--revisions))
+ (wanted
+ (funcall #'git-timemachine-completing-read-fn "Commit message: "
+ (mapcar (apply-partially #'nth 5) revisions))))
+ (git-timemachine-show-revision
+ (cl-find wanted revisions
+ :key (apply-partially #'nth 5)
+ :test #'equal))))
+
+(defun git-timemachine-show-nth-revision (rev-number)
+ "Show the REV-NUMBER revision."
+ (interactive "nEnter revision number: ")
+ (let* ((revisions (reverse (git-timemachine--revisions)))
+ (num-revisions (length revisions))
+ (curr-revision git-timemachine-revision)
+ (new-revision (nth (1- rev-number) revisions))
+ (new-line nil)
+ (cursor-win-pos (git-timemachine--get-cursor-position)))
+ (if (not new-revision)
+ (message "Only %d revisions exist." num-revisions)
+ (setq new-line (git-timemachine--find-new-current-line curr-revision new-revision (line-number-at-pos)))
+ (git-timemachine-show-revision new-revision)
+ (forward-line (- new-line (line-number-at-pos)))
+ (git-timemachine--set-cursor-position cursor-win-pos))))
+
+(defun git-timemachine-show-revision (revision)
+ "Show a REVISION (commit hash) of the current file."
+ (when revision
+ (let ((current-position (point))
+ (commit (car revision))
+ (revision-file-name (nth 1 revision))
+ (commit-index (nth 2 revision))
+ (date-relative (nth 3 revision))
+ (date-full (nth 4 revision))
+ (subject (nth 5 revision)))
+ (setq buffer-read-only nil)
+ (erase-buffer)
+ (let ((default-directory git-timemachine-directory)
+ (process-coding-system-alist (list (cons "" (cons buffer-file-coding-system default-process-coding-system)))))
+ (git-timemachine--process-file "show" (concat commit ":" revision-file-name)))
+ (setq buffer-read-only t)
+ (set-buffer-modified-p nil)
+ (let* ((revisions (git-timemachine--revisions))
+ (n-of-m (format "(%d/%d %s)" commit-index (length revisions) date-relative)))
+ (setq mode-line-buffer-identification
+ (list (propertized-buffer-identification "%12b") "@"
+ (propertize (git-timemachine-abbreviate commit) 'face 'git-timemachine-commit) " name:" revision-file-name" " n-of-m)))
+ (setq git-timemachine-revision revision)
+ (goto-char current-position)
+ (when git-timemachine-show-minibuffer-details
+ (git-timemachine--show-minibuffer-details revision))
+ (git-timemachine--erm-workaround))))
+
+(defun git-timemachine--erm-workaround ()
+ "Workaround for enhanced ruby mode not detecting revision change."
+ (when (eq major-mode 'enh-ruby-mode)
+ (ignore-errors (erm-reset-buffer))))
+
+(defun git-timemachine--show-minibuffer-details (revision)
+ "Show details for REVISION in minibuffer."
+ (let* ((date-relative (nth 3 revision))
+ (date-full (nth 4 revision))
+ (author (if git-timemachine-show-author (concat (nth 6 revision) ": ") ""))
+ (sha-or-subject (if (eq git-timemachine-minibuffer-detail 'commit) (car revision) (nth 5 revision))))
+ (message "%s%s [%s (%s)]"
+ (propertize author 'face 'git-timemachine-minibuffer-author-face)
+ (propertize sha-or-subject 'face 'git-timemachine-minibuffer-detail-face) date-full date-relative)))
+
+(defun git-timemachine--find-new-current-line (curr-revision new-revision current-line)
+ "Return the new current line after a revision jump.
+
+Given CURR-REVISION and NEW-REVISION determine if we need to updated CURRENT-LINE."
+ (let* ((revisions (reverse (git-timemachine--revisions)))
+ (current-commit (car curr-revision))
+ (curr-rev-number (+ (or (cl-position curr-revision revisions) 0) 1))
+ (new-commit (car new-revision))
+ (new-rev-number (+ (or (cl-position new-revision revisions) 0) 1))
+ (new-line nil)
+ (file git-timemachine-file)
+ (reverse (< curr-rev-number new-rev-number)))
+ ;; If no commit change, do nothing
+ (if (= curr-rev-number new-rev-number)
+ current-line
+ ;; Get new current line number using `git-blame`
+ (with-temp-buffer
+ (if reverse
+ (git-timemachine--process-file "blame" "--reverse" "-n" (format "-L %s,%s" current-line current-line) file (format "%s..%s" current-commit new-commit))
+ (git-timemachine--process-file "blame" "-n" (format "-L %s,%s" current-line current-line) file (format "%s..%s" new-commit current-commit)))
+ (goto-char (point-min))
+ ;; If end-of-buffer problem
+ (when (search-forward-regexp "^fatal: file .+ has only .+ lines" nil t)
+ (setq current-line (- current-line 1))
+ (erase-buffer)
+ (if reverse
+ (git-timemachine--process-file "blame" "--reverse" "-n" (format "-L %s,%s" current-line current-line) file (format "%s..%s" current-commit new-commit))
+ (git-timemachine--process-file "blame" "-n" (format "-L %s,%s" current-line current-line) file (format "%s..%s" new-commit current-commit))))
+ (goto-char (point-min))
+ (search-forward-regexp "^[^ ]+ \\([^ ]+\\)")
+ (setq new-line (string-to-number (match-string 1)))
+ ;; In case git blame doesn't give what we expect
+ (when (= new-line 0) (setq new-line current-line))
+ new-line))))
+
+(defun git-timemachine--get-cursor-position ()
+ "Return the cursor visual line number with respect to the current window first line."
+ (let* ((win-point-min (save-excursion (move-to-window-line 0) (point)))
+ (cur-pos (count-screen-lines win-point-min (point))))
+ cur-pos))
+
+(defun git-timemachine--set-cursor-position (POS)
+ "Set the cursor position to the POS visual line with respect to the window first line."
+ (recenter POS))
+
+(defun git-timemachine-abbreviate (revision)
+ "Return REVISION abbreviated to `git-timemachine-abbreviation-length' chars."
+ (substring revision 0 git-timemachine-abbreviation-length))
+
+(defun git-timemachine-quit ()
+ "Exit the timemachine."
+ (interactive)
+ (let ((parent-buffer-name buffer-file-name))
+ (kill-buffer)
+ (let ((parent-buffer (find-buffer-visiting parent-buffer-name)))
+ (when (and parent-buffer git-timemachine-quit-to-invoking-buffer)
+ (switch-to-buffer parent-buffer nil t)))))
+
+(defun git-timemachine-blame ()
+ "Call ‘magit-blame’ on current revision."
+ (interactive)
+ (if (fboundp 'magit-blame)
+ (let ((magit-buffer-revision (car git-timemachine-revision)))
+ (magit-blame))
+ (message "You need to install magit for blame capabilities")))
+
+(defun git-timemachine-kill-revision ()
+ "Kill the current revisions abbreviated commit hash."
+ (interactive)
+ (let ((revision (car git-timemachine-revision)))
+ (message revision)
+ (kill-new revision)))
+
+(defun git-timemachine-kill-abbreviated-revision ()
+ "Kill the current revisions full commit hash."
+ (interactive)
+ (let ((revision (git-timemachine-abbreviate (car git-timemachine-revision))))
+ (message revision)
+ (kill-new revision)))
+
+(defun git-timemachine-show-commit ()
+ "Show commit for current revision."
+ (interactive)
+ (let ((rev (car git-timemachine-revision)))
+ (if (fboundp 'magit-show-commit)
+ (magit-show-commit rev)
+ (message "You need to install magit to show commit"))))
+
+(define-transient-command git-timemachine-help ()
+ "Show online help."
+ ["Navigate"
+ [("p" "show previous revision" git-timemachine-show-previous-revision)
+ ("n" "show next revision" git-timemachine-show-next-revision)
+ ("g" "show nth revision" git-timemachine-show-nth-revision)
+ ("t" "show fuzzy revision" git-timemachine-show-revision-fuzzy)]]
+ ["Kill current revision"
+ [("w" "kill abbreviated revision" git-timemachine-kill-abbreviated-revision)
+ ("W" "kill revision" git-timemachine-kill-revision)]]
+ ["Misc"
+ [("b" "blame current revision" git-timemachine-blame)
+ ("c" "show commit" git-timemachine-show-commit)
+ ("?" "show help" git-timemachine-help)
+ ("q" "quit" git-timemachine-quit)]])
+
+(define-minor-mode git-timemachine-mode
+ "Git Timemachine, feel the wings of history."
+ :init-value nil
+ :lighter " Timemachine"
+ :keymap
+ '(("p" . git-timemachine-show-previous-revision)
+ ("n" . git-timemachine-show-next-revision)
+ ("g" . git-timemachine-show-nth-revision)
+ ("t" . git-timemachine-show-revision-fuzzy)
+ ("q" . git-timemachine-quit)
+ ("w" . git-timemachine-kill-abbreviated-revision)
+ ("W" . git-timemachine-kill-revision)
+ ("b" . git-timemachine-blame)
+ ("c" . git-timemachine-show-commit)
+ ("?" . git-timemachine-help))
+ :group 'git-timemachine)
+
+(defun git-timemachine-validate (file)
+ "Validate that there is a FILE and that it belongs to a git repository.
+Call with the value of 'buffer-file-name."
+ (unless file
+ (error "This buffer is not visiting a file"))
+ (unless (vc-git-registered file)
+ (error "This file is not git tracked")))
+
+(defun git-timemachine--start (get-revision-fn)
+ "Setup a timemachine buffer and populate it from the result of GET-REVISION-FN."
+ (setq git-timemachine--revisions-cache nil)
+ (git-timemachine-validate (buffer-file-name))
+ (let ((git-directory (expand-file-name (vc-git-root (buffer-file-name))))
+ (file-name (buffer-file-name))
+ (timemachine-buffer (format "timemachine:%s" (buffer-name)))
+ (cur-line (line-number-at-pos))
+ (cursor-position (git-timemachine--get-cursor-position))
+ (new-line nil)
+ (mode major-mode)
+ (coding-system buffer-file-coding-system))
+ (with-current-buffer (get-buffer-create timemachine-buffer)
+ (switch-to-buffer timemachine-buffer)
+ (setq buffer-file-name file-name)
+ (setq buffer-file-coding-system coding-system)
+ (funcall mode)
+ (setq git-timemachine-directory git-directory
+ git-timemachine-file (file-relative-name file-name git-directory)
+ git-timemachine-revision nil)
+ (funcall get-revision-fn)
+ (setq new-line (git-timemachine--find-new-current-line git-timemachine-revision (list "HEAD" "" 0 "" "" "" "") cur-line)) ;; Allow to stay on the same line
+ (goto-char (point-min))
+ (forward-line (- new-line 1))
+ (git-timemachine--set-cursor-position cursor-position)
+ (git-timemachine-mode))))
+
+;;;###autoload
+(defun git-timemachine-toggle ()
+ "Toggle git timemachine mode."
+ (interactive)
+ (if (bound-and-true-p git-timemachine-mode)
+ (git-timemachine-quit)
+ (git-timemachine)))
+
+;;;###autoload
+(defun git-timemachine ()
+ "Enable git timemachine for file of current buffer."
+ (interactive)
+ (git-timemachine--start #'git-timemachine-show-current-revision))
+
+;;;###autoload
+(defun git-timemachine-switch-branch (git-branch)
+ "Enable git timemachine for current buffer, switching to GIT-BRANCH."
+ (interactive (list (git-timemachine-completing-read-fn "Branch to switch to: "(vc-git-branches))))
+ (git-timemachine--start (lambda () (git-timemachine-show-latest-revision-in-branch git-branch))))
+
+(provide 'git-timemachine)
+
+;;; git-timemachine.el ends here