diff options
| author | jason | 2018-01-10 17:06:36 -0700 |
|---|---|---|
| committer | jason | 2018-01-10 17:06:36 -0700 |
| commit | 641c67a117bdd9e73df6d093f80525837d2b26b0 (patch) | |
| tree | 6d261f6a6748a21fe1b6e7500648dff88b38a09f | |
| parent | 27edc86ad25d8252fa97840567a98bec8d5bad75 (diff) | |
| download | dotemacs-641c67a117bdd9e73df6d093f80525837d2b26b0.tar.gz dotemacs-641c67a117bdd9e73df6d093f80525837d2b26b0.zip | |
add git-undo to local-lib. it isn't in melpa
| -rw-r--r-- | local-lib/git-undo.el | 152 |
1 files changed, 152 insertions, 0 deletions
diff --git a/local-lib/git-undo.el b/local-lib/git-undo.el new file mode 100644 index 0000000..e6eeb34 --- /dev/null +++ b/local-lib/git-undo.el | |||
| @@ -0,0 +1,152 @@ | |||
| 1 | ;;; git-undo.el Foundation, Inc. | ||
| 2 | |||
| 3 | ;; Author: John Wiegley <johnw@newartisans.com> | ||
| 4 | ;; Created: 20 Nov 2017 | ||
| 5 | ;; Version: 0.1 | ||
| 6 | |||
| 7 | ;; Keywords: git diff history log undo | ||
| 8 | ;; X-URL: https://github.com/jwiegley/git-undo | ||
| 9 | |||
| 10 | ;; This program is free software; you can redistribute it and/or | ||
| 11 | ;; modify it under the terms of the GNU General Public License as | ||
| 12 | ;; published by the Free Software Foundation; either version 2, or (at | ||
| 13 | ;; your option) any later version. | ||
| 14 | |||
| 15 | ;; This program is distributed in the hope that it will be useful, but | ||
| 16 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 18 | ;; General Public License for more details. | ||
| 19 | |||
| 20 | ;; You should have received a copy of the GNU General Public License | ||
| 21 | ;; along with GNU Emacs; see the file COPYING. If not, write to the | ||
| 22 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
| 23 | ;; Boston, MA 02111-1307, USA. | ||
| 24 | |||
| 25 | ;;; Commentary: | ||
| 26 | |||
| 27 | ;; Select a region and then use M-x git-undo to revert changes in that region | ||
| 28 | ;; to the most recent Git historical version. Use C-x z to repeatdly walk back | ||
| 29 | ;; through the history. M-x git-undo-browse will let you see the history of | ||
| 30 | ;; changes in a separate buffer. | ||
| 31 | |||
| 32 | ;;; Code: | ||
| 33 | |||
| 34 | (require 'cl) | ||
| 35 | |||
| 36 | (defgroup git-undo nil | ||
| 37 | "Successively undo a buffer region using Git history" | ||
| 38 | :group 'emacs) | ||
| 39 | |||
| 40 | (defvar git-undo--region-start) | ||
| 41 | (defvar git-undo--region-end) | ||
| 42 | (defvar git-undo--history) | ||
| 43 | |||
| 44 | (defun git-undo--apply-diff (hunk) | ||
| 45 | (with-temp-buffer | ||
| 46 | (insert hunk) | ||
| 47 | (goto-char (point-min)) | ||
| 48 | (while (not (eobp)) | ||
| 49 | (pcase (char-after) | ||
| 50 | (?\ (delete-char 1) (forward-line)) | ||
| 51 | (?\+ (delete-char 1) (forward-line)) | ||
| 52 | (?\- (delete-region (point) (and (forward-line) (point)))) | ||
| 53 | (t (delete-region (point) (point-max))))) | ||
| 54 | (buffer-string))) | ||
| 55 | |||
| 56 | (defun git-undo--replace-region () | ||
| 57 | (goto-char git-undo--region-start) | ||
| 58 | (delete-region git-undo--region-start git-undo--region-end) | ||
| 59 | (if (null git-undo--history) | ||
| 60 | (error "There is no more Git history to undo") | ||
| 61 | (insert (git-undo--apply-diff (car git-undo--history))) | ||
| 62 | (setq git-undo--history (cdr git-undo--history))) | ||
| 63 | (goto-char git-undo--region-end)) | ||
| 64 | |||
| 65 | (defun git-undo--compute-offsets (start end) | ||
| 66 | "Taking uncommitted changes into account, find the location in | ||
| 67 | Git history for a given line." | ||
| 68 | (let ((file-name (buffer-file-name)) | ||
| 69 | (buffer-lines (line-number-at-pos (point-max)))) | ||
| 70 | (with-temp-buffer | ||
| 71 | (shell-command | ||
| 72 | (format "git --no-pager diff -U%d HEAD -- %s" | ||
| 73 | buffer-lines (file-name-nondirectory file-name)) | ||
| 74 | (current-buffer)) | ||
| 75 | (goto-char (point-min)) | ||
| 76 | (re-search-forward "^@@") | ||
| 77 | (forward-line) | ||
| 78 | (let ((adjustment 0) | ||
| 79 | (line 1) | ||
| 80 | adjusted-start | ||
| 81 | adjusted-end) | ||
| 82 | (while (not (eobp)) | ||
| 83 | (pcase (char-after) | ||
| 84 | (?\+ (setq adjustment (1- adjustment))) | ||
| 85 | (?\- (setq adjustment (1+ adjustment))) | ||
| 86 | (t (setq line (1+ line)))) | ||
| 87 | (when (= (- start adjustment) line) | ||
| 88 | (setq adjusted-start (+ start adjustment))) | ||
| 89 | (when (= (- end adjustment) line) | ||
| 90 | (setq adjusted-end (+ end adjustment)) | ||
| 91 | (goto-char (point-max))) | ||
| 92 | (forward-line)) | ||
| 93 | (cons (1+ adjusted-start) adjusted-end))))) | ||
| 94 | |||
| 95 | (defun git-undo--build-history (start end) | ||
| 96 | (let ((file-name (buffer-file-name))) | ||
| 97 | (destructuring-bind (start-line . end-line) | ||
| 98 | (git-undo--compute-offsets (line-number-at-pos start) | ||
| 99 | (1- (line-number-at-pos end))) | ||
| 100 | (with-temp-buffer | ||
| 101 | (message "Retrieving Git history for lines %d to %d..." | ||
| 102 | start-line end-line) | ||
| 103 | (shell-command | ||
| 104 | (format "git --no-pager log --no-expand-tabs -p -L%d,%d:%s" | ||
| 105 | start-line end-line | ||
| 106 | (file-name-nondirectory file-name)) | ||
| 107 | (current-buffer)) | ||
| 108 | (message "") | ||
| 109 | (goto-char (point-min)) | ||
| 110 | (let ((commit t) history) | ||
| 111 | (while (and commit | ||
| 112 | (re-search-forward "^@@" nil t) | ||
| 113 | (forward-line)) | ||
| 114 | (delete-region (point-min) (point)) | ||
| 115 | (setq commit (and (re-search-forward "^commit " nil t) | ||
| 116 | (match-beginning 0))) | ||
| 117 | (setq history (cons (buffer-substring-no-properties | ||
| 118 | (point-min) (or commit (point-max))) | ||
| 119 | history))) | ||
| 120 | (nreverse history)))))) | ||
| 121 | |||
| 122 | ;;;###autoload | ||
| 123 | (defun git-undo (&optional start end) | ||
| 124 | "Undo Git-historical changes in the region from START to END." | ||
| 125 | (interactive "r") | ||
| 126 | (if (eq last-command 'git-undo) | ||
| 127 | (git-undo--replace-region) | ||
| 128 | (set (make-local-variable 'git-undo--region-start) | ||
| 129 | (copy-marker start nil)) | ||
| 130 | (set (make-local-variable 'git-undo--region-end) | ||
| 131 | (copy-marker end t)) | ||
| 132 | (set (make-local-variable 'git-undo--history) | ||
| 133 | (git-undo--build-history start end)) | ||
| 134 | (git-undo--replace-region))) | ||
| 135 | |||
| 136 | ;;;###autoload | ||
| 137 | (defun git-undo-browse (&optional start end) | ||
| 138 | "Undo Git-historical changes in the region from START to END." | ||
| 139 | (interactive "r") | ||
| 140 | (let ((history (git-undo--build-history start end))) | ||
| 141 | (display-buffer | ||
| 142 | (with-current-buffer | ||
| 143 | (get-buffer-create "*Git Region History*") | ||
| 144 | (delete-region (point-min) (point-max)) | ||
| 145 | (dolist (entry history) | ||
| 146 | (insert (git-undo--apply-diff entry) | ||
| 147 | #("-----\n" 0 5 (face bold)))) | ||
| 148 | (delete-region (- (point) 6) (point)) | ||
| 149 | (goto-char (point-min)) | ||
| 150 | (current-buffer))))) | ||
| 151 | |||
| 152 | ;;; git-undo.el ends here | ||