-
Notifications
You must be signed in to change notification settings - Fork 43
/
gitsum.el
207 lines (183 loc) · 7.17 KB
/
gitsum.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
;;; gitsum.el --- basic darcsum feelalike for Git
;; Copyright (C) 2008 Christian Neukirchen <purl.org/net/chneukirchen>
;; Licensed under the same terms as Emacs.
;; Repository: http://github.com/chneukirchen/gitsum
;; git://github.com/chneukirchen/gitsum.git
;; Patches to: [email protected]
;; Version: 0.2
;; 04feb2008 +chris+
(eval-when-compile (require 'cl))
(defcustom gitsum-reuse-buffer t
"Whether `gitsum' should try to reuse an existing buffer
if there is already one that displays the same directory."
:group 'git
:type 'boolean)
(easy-mmode-defmap gitsum-diff-mode-shared-map
'(("A" . gitsum-amend)
("c" . gitsum-commit)
("g" . gitsum-refresh)
("k" . gitsum-kill-dwim)
("P" . gitsum-push)
("R" . gitsum-revert)
("s" . gitsum-switch-to-git-status)
("q" . gitsum-kill-buffer)
("u" . gitsum-undo))
"Basic keymap for `gitsum-diff-mode', bound to various prefix keys.")
(define-derived-mode gitsum-diff-mode diff-mode "gitsum"
"Git summary mode is for preparing patches to a Git repository.
This mode is meant to be activated by `M-x gitsum' or pressing `s' in git-status.
\\{gitsum-diff-mode-shared-map}
\\{gitsum-diff-mode-map}"
;; magic...
(lexical-let ((ro-bind (cons 'buffer-read-only gitsum-diff-mode-shared-map)))
(add-to-list 'minor-mode-overriding-map-alist ro-bind))
(setq buffer-read-only t))
(define-key gitsum-diff-mode-map (kbd "C-c C-c") 'gitsum-commit)
(define-key gitsum-diff-mode-map (kbd "C-/") 'gitsum-undo)
(define-key gitsum-diff-mode-map (kbd "C-_") 'gitsum-undo)
;; When git.el is loaded, hack into keymap.
(when (boundp 'git-status-mode-map)
(define-key git-status-mode-map "s" 'gitsum-switch-from-git-status))
;; Undo doesn't work in read-only buffers else.
(defun gitsum-undo ()
"Undo some previous changes.
Repeat this command to undo more changes.
A numeric argument serves as a repeat count."
(interactive)
(let ((inhibit-read-only t))
(undo)))
(defun gitsum-refresh (&optional arguments)
"Regenerate the patch based on the current state of the index."
(interactive)
(let ((inhibit-read-only t))
(erase-buffer)
(insert "# Directory: " default-directory "\n")
(insert "# Use n and p to navigate and k to kill a hunk. u is undo, g will refresh.\n")
(insert "# Edit the patch as you please and press 'c' to commit.\n\n")
(let ((diff (shell-command-to-string (concat "git diff " arguments))))
(if (zerop (length diff))
(insert "## No changes. ##")
(insert diff)
(goto-char (point-min))
(delete-matching-lines "^index \\|^diff --git ")))
(set-buffer-modified-p nil)
(goto-char (point-min))
(forward-line 4)))
(defun gitsum-kill-dwim ()
"Kill the current hunk or file depending on point."
(interactive)
(let ((inhibit-read-only t))
(if (looking-at "^---\\|^\\+\\+\\+")
(diff-file-kill)
(diff-hunk-kill)
(save-excursion
(when (or (looking-at "^--- ")
(eobp))
(let ((here (point)))
(forward-line -2)
(when (looking-at "^--- ")
(delete-region here (point)))))))))
(defun gitsum-commit ()
"Commit the patch as-is, asking for a commit message."
(interactive)
(shell-command-on-region (point-min) (point-max) "git apply --check --cached")
(let ((buffer (get-buffer-create "*gitsum-commit*"))
(dir default-directory))
(shell-command-on-region (point-min) (point-max) "(cat; git diff --cached) | git apply --stat" buffer)
(with-current-buffer buffer
(setq default-directory dir)
(goto-char (point-min))
(insert "\n")
(while (re-search-forward "^" nil t)
(replace-match "# " nil nil))
(forward-line 0)
(forward-char -1)
(delete-region (point) (point-max))
(goto-char (point-min)))
(log-edit 'gitsum-do-commit nil nil buffer)))
(defun gitsum-amend ()
"Amend the last commit."
(interactive)
(let ((last (substring (shell-command-to-string
"git log -1 --pretty=oneline --abbrev-commit")
0 -1)))
(when (y-or-n-p (concat "Are you sure you want to amend to " last "? "))
(shell-command-on-region (point-min) (point-max) "git apply --cached")
(shell-command "git commit --amend -C HEAD")
(gitsum-refresh))))
(defun gitsum-push ()
"Push the current repository."
(interactive)
(let ((args (read-string "Shell command: " "git push ")))
(let ((buffer (get-buffer-create " *gitsum-push*")))
(switch-to-buffer buffer)
(insert "Running " args "...\n\n")
(start-process-shell-command "gitsum-push" buffer args))))
(defun gitsum-revert ()
"Revert the active patches in the working directory."
(interactive)
(let ((count (count-matches "^@@" (point-min) (point-max))))
(if (not (yes-or-no-p
(format "Are you sure you want to revert these %d hunk(s)? "
count)))
(message "Revert canceled.")
(shell-command-on-region (point-min) (point-max) "git apply --reverse")
(gitsum-refresh))))
(defun gitsum-do-commit ()
"Perform the actual commit using the current buffer as log message."
(interactive)
(with-current-buffer log-edit-parent-buffer
(shell-command-on-region (point-min) (point-max)
"git apply --cached"))
(shell-command-on-region (point-min) (point-max)
"git commit -F- --cleanup=strip")
(with-current-buffer log-edit-parent-buffer
(gitsum-refresh)))
(defun gitsum-kill-buffer ()
"Kill the current buffer if it has no manual changes."
(interactive)
(if (buffer-modified-p)
(message "Patch was modified, use C-x k to kill.")
(kill-buffer nil)))
(defun gitsum-switch-to-git-status ()
"Switch to git-status."
(interactive)
(git-status default-directory))
(defun gitsum-switch-from-git-status ()
"Switch to gitsum, resticting diff to marked files if any."
(interactive)
(let ((marked (git-get-filenames
(ewoc-collect git-status
(lambda (info) (git-fileinfo->marked info))))))
(gitsum)
(when marked
(gitsum-refresh (mapconcat 'identity marked " ")))))
(defun gitsum-find-buffer (dir)
"Find the gitsum buffer handling a specified directory."
(let ((list (buffer-list))
(fulldir (expand-file-name dir))
found)
(while (and list (not found))
(let ((buffer (car list)))
(with-current-buffer buffer
(when (and list-buffers-directory
(string-equal fulldir
(expand-file-name list-buffers-directory))
(eq major-mode 'gitsum-diff-mode))
(setq found buffer))))
(setq list (cdr list)))
found))
(defun gitsum ()
"Entry point into gitsum-diff-mode."
(interactive)
(let* ((dir default-directory)
(buffer (or (and gitsum-reuse-buffer (gitsum-find-buffer dir))
(generate-new-buffer "*gitsum*"))))
(switch-to-buffer buffer)
(gitsum-diff-mode)
(set (make-local-variable 'list-buffers-directory) dir)
(gitsum-refresh)))
;; viper compatible
(eval-after-load "viper"
'(add-to-list 'viper-emacs-state-mode-list 'gitsum-diff-mode))
(provide 'gitsum)