##// END OF EJS Templates
mq.el: add mq-signoff, to sign off on a patch
Bryan O'Sullivan -
r4429:3b0f73ed default
parent child Browse files
Show More
@@ -1,375 +1,410 b''
1 1 ;;; mq.el --- Emacs support for Mercurial Queues
2 2
3 3 ;; Copyright (C) 2006 Bryan O'Sullivan
4 4
5 5 ;; Author: Bryan O'Sullivan <bos@serpentine.com>
6 6
7 7 ;; mq.el is free software; you can redistribute it and/or modify it
8 8 ;; under the terms of version 2 of the GNU General Public License as
9 9 ;; published by the Free Software Foundation.
10 10
11 11 ;; mq.el is distributed in the hope that it will be useful, but
12 12 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 14 ;; General Public License for more details.
15 15
16 16 ;; You should have received a copy of the GNU General Public License
17 17 ;; along with mq.el, GNU Emacs, or XEmacs; see the file COPYING (`C-h
18 18 ;; C-l'). If not, write to the Free Software Foundation, Inc., 59
19 19 ;; Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 20
21 21 (require 'mercurial)
22 22
23 23
24 24 (defcustom mq-mode-hook nil
25 25 "Hook run when a buffer enters mq-mode."
26 26 :type 'sexp
27 27 :group 'mercurial)
28 28
29 29 (defcustom mq-global-prefix "\C-cq"
30 30 "The global prefix for Mercurial Queues keymap bindings."
31 31 :type 'sexp
32 32 :group 'mercurial)
33 33
34 34 (defcustom mq-edit-mode-hook nil
35 35 "Hook run after a buffer is populated to edit a patch description."
36 36 :type 'sexp
37 37 :group 'mercurial)
38 38
39 39 (defcustom mq-edit-finish-hook nil
40 40 "Hook run before a patch description is finished up with."
41 41 :type 'sexp
42 42 :group 'mercurial)
43 43
44 (defcustom mq-signoff-address nil
45 "Address with which to sign off on a patch."
46 :type 'string
47 :group 'mercurial)
48
44 49
45 50 ;;; Internal variables.
46 51
47 52 (defvar mq-mode nil
48 53 "Is this file managed by MQ?")
49 54 (make-variable-buffer-local 'mq-mode)
50 55 (put 'mq-mode 'permanent-local t)
51 56
52 57 (defvar mq-patch-history nil)
53 58
54 59 (defvar mq-top-patch '(nil))
55 60
56 61 (defvar mq-prev-buffer nil)
57 62 (make-variable-buffer-local 'mq-prev-buffer)
58 63 (put 'mq-prev-buffer 'permanent-local t)
59 64
60 65
61 66 ;;; Global keymap.
62 67
63 68 (defvar mq-global-map (make-sparse-keymap))
64 69 (fset 'mq-global-map mq-global-map)
65 70 (global-set-key mq-global-prefix 'mq-global-map)
66 71 (define-key mq-global-map "." 'mq-push)
67 72 (define-key mq-global-map ">" 'mq-push-all)
68 73 (define-key mq-global-map "," 'mq-pop)
69 74 (define-key mq-global-map "<" 'mq-pop-all)
70 75 (define-key mq-global-map "=" 'mq-diff)
71 76 (define-key mq-global-map "r" 'mq-refresh)
72 77 (define-key mq-global-map "e" 'mq-refresh-edit)
73 78 (define-key mq-global-map "i" 'mq-new)
74 79 (define-key mq-global-map "n" 'mq-next)
80 (define-key mq-global-map "o" 'mq-signoff)
75 81 (define-key mq-global-map "p" 'mq-previous)
76 82 (define-key mq-global-map "s" 'mq-edit-series)
77 83 (define-key mq-global-map "t" 'mq-top)
78 84
79 85 (add-minor-mode 'mq-mode 'mq-mode)
80 86
81 87
82 88 ;;; Refresh edit mode keymap.
83 89
84 90 (defvar mq-edit-mode-map (make-sparse-keymap))
85 91 (define-key mq-edit-mode-map "\C-c\C-c" 'mq-edit-finish)
86 92 (define-key mq-edit-mode-map "\C-c\C-k" 'mq-edit-kill)
93 (define-key mq-edit-mode-map "\C-c\C-s" 'mq-signoff)
87 94
88 95
89 96 ;;; Helper functions.
90 97
91 98 (defun mq-read-patch-name (&optional source prompt force)
92 99 "Read a patch name to use with a command.
93 100 May return nil, meaning \"use the default\"."
94 101 (let ((patches (split-string
95 102 (hg-chomp (hg-run0 (or source "qseries"))) "\n")))
96 103 (when force
97 104 (completing-read (format "Patch%s: " (or prompt ""))
98 105 (map 'list 'cons patches patches)
99 106 nil
100 107 nil
101 108 nil
102 109 'mq-patch-history))))
103 110
104 111 (defun mq-refresh-buffers (root)
105 112 (save-excursion
106 113 (dolist (buf (hg-buffers-visiting-repo root))
107 114 (when (not (verify-visited-file-modtime buf))
108 115 (set-buffer buf)
109 116 (let ((ctx (hg-buffer-context)))
110 117 (message "Refreshing %s..." (buffer-name))
111 118 (revert-buffer t t t)
112 119 (hg-restore-context ctx)
113 120 (message "Refreshing %s...done" (buffer-name))))))
114 121 (hg-update-mode-lines root)
115 122 (mq-update-mode-lines root))
116 123
117 124 (defun mq-last-line ()
118 125 (goto-char (point-max))
119 126 (beginning-of-line)
120 127 (when (looking-at "^$")
121 128 (forward-line -1))
122 129 (let ((bol (point)))
123 130 (end-of-line)
124 131 (let ((line (buffer-substring bol (point))))
125 132 (when (> (length line) 0)
126 133 line))))
127 134
128 135 (defun mq-push (&optional patch)
129 136 "Push patches until PATCH is reached.
130 137 If PATCH is nil, push at most one patch."
131 138 (interactive (list (mq-read-patch-name "qunapplied" " to push"
132 139 current-prefix-arg)))
133 140 (let ((root (hg-root))
134 141 (prev-buf (current-buffer))
135 142 last-line ok)
136 143 (unless root
137 144 (error "Cannot push outside a repository!"))
138 145 (hg-sync-buffers root)
139 146 (let ((buf-name (format "MQ: Push %s" (or patch "next patch"))))
140 147 (kill-buffer (get-buffer-create buf-name))
141 148 (split-window-vertically)
142 149 (other-window 1)
143 150 (switch-to-buffer (get-buffer-create buf-name))
144 151 (cd root)
145 152 (message "Pushing...")
146 153 (setq ok (= 0 (apply 'call-process (hg-binary) nil t t "qpush"
147 154 (if patch (list patch))))
148 155 last-line (mq-last-line))
149 156 (let ((lines (count-lines (point-min) (point-max))))
150 157 (if (or (<= lines 1)
151 158 (and (equal lines 2) (string-match "Now at:" last-line)))
152 159 (progn
153 160 (kill-buffer (current-buffer))
154 161 (delete-window))
155 162 (hg-view-mode prev-buf))))
156 163 (mq-refresh-buffers root)
157 164 (sit-for 0)
158 165 (when last-line
159 166 (if ok
160 167 (message "Pushing... %s" last-line)
161 168 (error "Pushing... %s" last-line)))))
162 169
163 170 (defun mq-push-all ()
164 171 "Push patches until all are applied."
165 172 (interactive)
166 173 (mq-push "-a"))
167 174
168 175 (defun mq-pop (&optional patch)
169 176 "Pop patches until PATCH is reached.
170 177 If PATCH is nil, pop at most one patch."
171 178 (interactive (list (mq-read-patch-name "qapplied" " to pop to"
172 179 current-prefix-arg)))
173 180 (let ((root (hg-root))
174 181 last-line ok)
175 182 (unless root
176 183 (error "Cannot pop outside a repository!"))
177 184 (hg-sync-buffers root)
178 185 (set-buffer (generate-new-buffer "qpop"))
179 186 (cd root)
180 187 (message "Popping...")
181 188 (setq ok (= 0 (apply 'call-process (hg-binary) nil t t "qpop"
182 189 (if patch (list patch))))
183 190 last-line (mq-last-line))
184 191 (kill-buffer (current-buffer))
185 192 (mq-refresh-buffers root)
186 193 (sit-for 0)
187 194 (when last-line
188 195 (if ok
189 196 (message "Popping... %s" last-line)
190 197 (error "Popping... %s" last-line)))))
191 198
192 199 (defun mq-pop-all ()
193 200 "Push patches until none are applied."
194 201 (interactive)
195 202 (mq-pop "-a"))
196 203
197 204 (defun mq-refresh-internal (root &rest args)
198 205 (hg-sync-buffers root)
199 206 (let ((patch (mq-patch-info "qtop")))
200 207 (message "Refreshing %s..." patch)
201 208 (let ((ret (apply 'hg-run "qrefresh" args)))
202 209 (if (equal (car ret) 0)
203 210 (message "Refreshing %s... done." patch)
204 211 (error "Refreshing %s... %s" patch (hg-chomp (cdr ret)))))))
205 212
206 213 (defun mq-refresh (&optional git)
207 214 "Refresh the topmost applied patch.
208 215 With a prefix argument, generate a git-compatible patch."
209 216 (interactive "P")
210 217 (let ((root (hg-root)))
211 218 (unless root
212 219 (error "Cannot refresh outside of a repository!"))
213 220 (apply 'mq-refresh-internal root (if git '("--git")))))
214 221
215 222 (defun mq-patch-info (cmd &optional msg)
216 223 (let* ((ret (hg-run cmd))
217 224 (info (hg-chomp (cdr ret))))
218 225 (if (equal (car ret) 0)
219 226 (if msg
220 227 (message "%s patch: %s" msg info)
221 228 info)
222 229 (error "%s" info))))
223 230
224 231 (defun mq-top ()
225 232 "Print the name of the topmost applied patch."
226 233 (interactive)
227 234 (mq-patch-info "qtop" "Top"))
228 235
229 236 (defun mq-next ()
230 237 "Print the name of the next patch to be pushed."
231 238 (interactive)
232 239 (mq-patch-info "qnext" "Next"))
233 240
234 241 (defun mq-previous ()
235 242 "Print the name of the first patch below the topmost applied patch.
236 243 This would become the active patch if popped to."
237 244 (interactive)
238 245 (mq-patch-info "qprev" "Previous"))
239 246
240 247 (defun mq-edit-finish ()
241 248 "Finish editing the description of this patch, and refresh the patch."
242 249 (interactive)
243 250 (unless (equal (mq-patch-info "qtop") mq-top)
244 251 (error "Topmost patch has changed!"))
245 252 (hg-sync-buffers hg-root)
246 253 (run-hooks 'mq-edit-finish-hook)
247 254 (mq-refresh-internal hg-root "-m" (buffer-substring (point-min) (point-max)))
248 255 (let ((buf mq-prev-buffer))
249 256 (kill-buffer nil)
250 257 (switch-to-buffer buf)))
251 258
252 259 (defun mq-edit-kill ()
253 260 "Kill the edit currently being prepared."
254 261 (interactive)
255 262 (when (or (not (buffer-modified-p)) (y-or-n-p "Really kill this edit? "))
256 263 (let ((buf mq-prev-buffer))
257 264 (kill-buffer nil)
258 265 (switch-to-buffer buf))))
259 266
260 267 (defun mq-get-top (root)
261 268 (let ((entry (assoc root mq-top-patch)))
262 269 (if entry
263 270 (cdr entry))))
264 271
265 272 (defun mq-set-top (root patch)
266 273 (let ((entry (assoc root mq-top-patch)))
267 274 (if entry
268 275 (if patch
269 276 (setcdr entry patch)
270 277 (setq mq-top-patch (delq entry mq-top-patch)))
271 278 (setq mq-top-patch (cons (cons root patch) mq-top-patch)))))
272 279
273 280 (defun mq-update-mode-lines (root)
274 281 (let ((cwd default-directory))
275 282 (cd root)
276 283 (condition-case nil
277 284 (mq-set-top root (mq-patch-info "qtop"))
278 285 (error (mq-set-top root nil)))
279 286 (cd cwd))
280 287 (let ((patch (mq-get-top root)))
281 288 (save-excursion
282 289 (dolist (buf (hg-buffers-visiting-repo root))
283 290 (set-buffer buf)
284 291 (if mq-mode
285 292 (setq mq-mode (or (and patch (concat " MQ:" patch)) " MQ")))))))
286 293
287 294 (defun mq-mode (&optional arg)
288 295 "Minor mode for Mercurial repositories with an MQ patch queue"
289 296 (interactive "i")
290 297 (cond ((hg-root)
291 298 (setq mq-mode (if (null arg) (not mq-mode)
292 299 arg))
293 300 (mq-update-mode-lines (hg-root))))
294 301 (run-hooks 'mq-mode-hook))
295 302
296 303 (defun mq-edit-mode ()
297 304 "Mode for editing the description of a patch.
298 305
299 306 Key bindings
300 307 ------------
301 308 \\[mq-edit-finish] use this description
302 309 \\[mq-edit-kill] abandon this description"
303 310 (interactive)
304 311 (use-local-map mq-edit-mode-map)
305 312 (set-syntax-table text-mode-syntax-table)
306 313 (setq local-abbrev-table text-mode-abbrev-table
307 314 major-mode 'mq-edit-mode
308 315 mode-name "MQ-Edit")
309 316 (set-buffer-modified-p nil)
310 317 (setq buffer-undo-list nil)
311 318 (run-hooks 'text-mode-hook 'mq-edit-mode-hook))
312 319
313 320 (defun mq-refresh-edit ()
314 321 "Refresh the topmost applied patch, editing the patch description."
315 322 (interactive)
316 323 (while mq-prev-buffer
317 324 (set-buffer mq-prev-buffer))
318 325 (let ((root (hg-root))
319 326 (prev-buffer (current-buffer))
320 327 (patch (mq-patch-info "qtop")))
321 328 (hg-sync-buffers root)
322 329 (let ((buf-name (format "*MQ: Edit description of %s*" patch)))
323 330 (switch-to-buffer (get-buffer-create buf-name))
324 331 (when (= (point-min) (point-max))
325 332 (set (make-local-variable 'hg-root) root)
326 333 (set (make-local-variable 'mq-top) patch)
327 334 (setq mq-prev-buffer prev-buffer)
328 335 (insert (hg-run0 "qheader"))
329 336 (goto-char (point-min)))
330 337 (mq-edit-mode)
331 338 (cd root)))
332 339 (message "Type `C-c C-c' to finish editing and refresh the patch."))
333 340
334 341 (defun mq-new (name)
335 342 "Create a new empty patch named NAME.
336 343 The patch is applied on top of the current topmost patch.
337 344 With a prefix argument, forcibly create the patch even if the working
338 345 directory is modified."
339 346 (interactive (list (mq-read-patch-name "qseries" " to create" t)))
340 347 (message "Creating patch...")
341 348 (let ((ret (if current-prefix-arg
342 349 (hg-run "qnew" "-f" name)
343 350 (hg-run "qnew" name))))
344 351 (if (equal (car ret) 0)
345 352 (progn
346 353 (hg-update-mode-lines (buffer-file-name))
347 354 (message "Creating patch... done."))
348 355 (error "Creating patch... %s" (hg-chomp (cdr ret))))))
349 356
350 357 (defun mq-edit-series ()
351 358 "Edit the MQ series file directly."
352 359 (interactive)
353 360 (let ((root (hg-root)))
354 361 (unless root
355 362 (error "Not in an MQ repository!"))
356 363 (find-file (concat root ".hg/patches/series"))))
357 364
358 365 (defun mq-diff (&optional git)
359 366 "Display a diff of the topmost applied patch.
360 367 With a prefix argument, display a git-compatible diff."
361 368 (interactive "P")
362 369 (hg-view-output ((format "MQ: Diff of %s" (mq-patch-info "qtop")))
363 370 (if git
364 371 (call-process (hg-binary) nil t nil "qdiff" "--git")
365 372 (call-process (hg-binary) nil t nil "qdiff"))
366 373 (diff-mode)
367 374 (font-lock-fontify-buffer)))
368 375
376 (defun mq-signoff ()
377 "Sign off on the current patch, in the style used by the Linux kernel.
378 If the variable mq-signoff-address is non-nil, it will be used, otherwise
379 the value of the ui.username item from your hgrc will be used."
380 (interactive)
381 (let ((was-editing (eq major-mode 'mq-edit-mode))
382 signed)
383 (unless was-editing
384 (mq-refresh-edit))
385 (save-excursion
386 (let* ((user (or mq-signoff-address
387 (hg-run0 "debugconfig" "ui.username")))
388 (signoff (concat "Signed-off-by: " user)))
389 (if (search-forward signoff nil t)
390 (message "You have already signed off on this patch.")
391 (goto-char (point-max))
392 (let ((case-fold-search t))
393 (if (re-search-backward "^Signed-off-by: " nil t)
394 (forward-line 1)
395 (insert "\n")))
396 (insert signoff)
397 (message "%s" signoff)
398 (setq signed t))))
399 (unless was-editing
400 (if signed
401 (mq-edit-finish)
402 (mq-edit-kill)))))
403
369 404
370 405 (provide 'mq)
371 406
372 407
373 408 ;;; Local Variables:
374 409 ;;; prompt-to-byte-compile: nil
375 410 ;;; end:
General Comments 0
You need to be logged in to leave comments. Login now