chiark / gitweb /
stgit.el: Add support for showing which files are affected by a patch
[stgit] / contrib / stgit.el
CommitLineData
3a59f3db
KH
1;; stgit.el: An emacs mode for StGit
2;;
3;; Copyright (C) 2007 David Kågedal <davidk@lysator.liu.se>
4;;
5;; To install: put this file on the load-path and place the following
6;; in your .emacs file:
7;;
8;; (require 'stgit)
9;;
10;; To start: `M-x stgit'
11
0f076fe6
GH
12(require 'git nil t)
13
56d81fe5 14(defun stgit (dir)
a53347d9 15 "Manage StGit patches for the tree in DIR."
56d81fe5 16 (interactive "DDirectory: \n")
52144ce5 17 (switch-to-stgit-buffer (git-get-top-dir dir))
1f0bf00f 18 (stgit-reload))
56d81fe5 19
074a4fb0
GH
20(unless (fboundp 'git-get-top-dir)
21 (defun git-get-top-dir (dir)
22 "Retrieve the top-level directory of a git tree."
23 (let ((cdup (with-output-to-string
24 (with-current-buffer standard-output
25 (cd dir)
26 (unless (eq 0 (call-process "git" nil t nil
27 "rev-parse" "--show-cdup"))
28 (error "cannot find top-level git tree for %s." dir))))))
29 (expand-file-name (concat (file-name-as-directory dir)
30 (car (split-string cdup "\n")))))))
31
32(defun stgit-refresh-git-status (&optional dir)
33 "If it exists, refresh the `git-status' buffer belonging to
34directory DIR or `default-directory'"
35 (when (and (fboundp 'git-find-status-buffer)
36 (fboundp 'git-refresh-status))
37 (let* ((top-dir (git-get-top-dir (or dir default-directory)))
38 (git-status-buffer (and top-dir (git-find-status-buffer top-dir))))
39 (when git-status-buffer
40 (with-current-buffer git-status-buffer
41 (git-refresh-status))))))
52144ce5 42
56d81fe5 43(defun switch-to-stgit-buffer (dir)
a53347d9 44 "Switch to a (possibly new) buffer displaying StGit patches for DIR."
56d81fe5
DK
45 (setq dir (file-name-as-directory dir))
46 (let ((buffers (buffer-list)))
47 (while (and buffers
48 (not (with-current-buffer (car buffers)
49 (and (eq major-mode 'stgit-mode)
50 (string= default-directory dir)))))
51 (setq buffers (cdr buffers)))
52 (switch-to-buffer (if buffers
53 (car buffers)
54 (create-stgit-buffer dir)))))
55
56(defun create-stgit-buffer (dir)
57 "Create a buffer for showing StGit patches.
58Argument DIR is the repository path."
59 (let ((buf (create-file-buffer (concat dir "*stgit*")))
60 (inhibit-read-only t))
61 (with-current-buffer buf
62 (setq default-directory dir)
63 (stgit-mode)
64 (setq buffer-read-only t))
65 buf))
66
67(defmacro stgit-capture-output (name &rest body)
a53347d9 68 "Capture StGit output and show it in a window at the end."
34afb86c
DK
69 `(let ((output-buf (get-buffer-create ,(or name "*StGit output*")))
70 (stgit-dir default-directory)
71 (inhibit-read-only t))
56d81fe5 72 (with-current-buffer output-buf
34afb86c
DK
73 (erase-buffer)
74 (setq default-directory stgit-dir)
75 (setq buffer-read-only t))
56d81fe5
DK
76 (let ((standard-output output-buf))
77 ,@body)
34afb86c
DK
78 (with-current-buffer output-buf
79 (set-buffer-modified-p nil)
80 (setq buffer-read-only t)
81 (if (< (point-min) (point-max))
82 (display-buffer output-buf t)))))
56d81fe5
DK
83(put 'stgit-capture-output 'lisp-indent-function 1)
84
9aecd505 85(defun stgit-run-silent (&rest args)
56d81fe5
DK
86 (apply 'call-process "stg" nil standard-output nil args))
87
9aecd505
DK
88(defun stgit-run (&rest args)
89 (let ((msgcmd (mapconcat #'identity args " ")))
90 (message "Running stg %s..." msgcmd)
91 (apply 'call-process "stg" nil standard-output nil args)
92 (message "Running stg %s...done" msgcmd)))
93
378a003d
GH
94(defun stgit-run-git (&rest args)
95 (let ((msgcmd (mapconcat #'identity args " ")))
96 (message "Running git %s..." msgcmd)
97 (apply 'call-process "git" nil standard-output nil args)
98 (message "Running git %s...done" msgcmd)))
99
1f0bf00f 100(defun stgit-reload ()
a53347d9 101 "Update the contents of the StGit buffer."
56d81fe5
DK
102 (interactive)
103 (let ((inhibit-read-only t)
104 (curline (line-number-at-pos))
105 (curpatch (stgit-patch-at-point)))
106 (erase-buffer)
107 (insert "Branch: ")
9aecd505
DK
108 (stgit-run-silent "branch")
109 (stgit-run-silent "series" "--description")
6df83d42 110 (stgit-rescan)
56d81fe5
DK
111 (if curpatch
112 (stgit-goto-patch curpatch)
074a4fb0
GH
113 (goto-line curline)))
114 (stgit-refresh-git-status))
56d81fe5 115
07f464e0
DK
116(defface stgit-description-face
117 '((((background dark)) (:foreground "tan"))
118 (((background light)) (:foreground "dark red")))
119 "The face used for StGit desriptions")
120
121(defface stgit-top-patch-face
122 '((((background dark)) (:weight bold :foreground "yellow"))
123 (((background light)) (:weight bold :foreground "purple"))
124 (t (:weight bold)))
125 "The face used for the top patch names")
126
127(defface stgit-applied-patch-face
128 '((((background dark)) (:foreground "light yellow"))
129 (((background light)) (:foreground "purple"))
130 (t ()))
131 "The face used for applied patch names")
132
133(defface stgit-unapplied-patch-face
134 '((((background dark)) (:foreground "gray80"))
135 (((background light)) (:foreground "orchid"))
136 (t ()))
137 "The face used for unapplied patch names")
138
378a003d
GH
139(defun stgit-expand-patch (patchsym)
140 (save-excursion
141 (forward-line)
142 (let ((start (point)))
143 (stgit-run "files" (symbol-name patchsym))
144
145 ;; 'stg files' outputs a single newline for empty patches; it
146 ;; must be destroyed!
147 (when (and (= (1+ start) (point))
148 (= (char-before) ?\n))
149 (delete-backward-char 1))
150
151 (let ((end-marker (point-marker)))
152 (if (= start (point))
153 (insert-string " <no files>\n")
154 (unless (looking-at "^")
155 (insert ?\n))
156 (while (and (zerop (forward-line -1))
157 (>= (point) start))
158 (insert " ")))
159 (put-text-property start end-marker 'stgit-patchsym patchsym)))))
160
6df83d42
DK
161(defun stgit-rescan ()
162 "Rescan the status buffer."
07f464e0 163 (save-excursion
6df83d42
DK
164 (let ((marked ()))
165 (goto-char (point-min))
166 (while (not (eobp))
167 (cond ((looking-at "Branch: \\(.*\\)")
168 (put-text-property (match-beginning 1) (match-end 1)
169 'face 'bold))
8ee1e4b4 170 ((looking-at "\\([>+-]\\)\\( \\)\\([^ ]+\\) *[|#] \\(.*\\)")
6df83d42
DK
171 (let ((state (match-string 1))
172 (patchsym (intern (match-string 3))))
173 (put-text-property
174 (match-beginning 3) (match-end 3) 'face
175 (cond ((string= state ">") 'stgit-top-patch-face)
176 ((string= state "+") 'stgit-applied-patch-face)
177 ((string= state "-") 'stgit-unapplied-patch-face)))
178 (put-text-property (match-beginning 4) (match-end 4)
179 'face 'stgit-description-face)
180 (when (memq patchsym stgit-marked-patches)
181 (replace-match "*" nil nil nil 2)
378a003d
GH
182 (setq marked (cons patchsym marked)))
183 (when (memq patchsym stgit-expanded-patches)
184 (stgit-expand-patch patchsym))
185 ))
ad80ce22
DK
186 ((or (looking-at "stg series: Branch \".*\" not initialised")
187 (looking-at "stg series: .*: branch not initialized"))
1c2426dc
DK
188 (forward-line 1)
189 (insert "Run M-x stgit-init to initialise")))
6df83d42
DK
190 (forward-line 1))
191 (setq stgit-marked-patches (nreverse marked)))))
07f464e0 192
378a003d
GH
193(defun stgit-select ()
194 "Expand or collapse the current entry"
195 (interactive)
196 (let ((curpatch (stgit-patch-at-point)))
197 (if (not curpatch)
198 (let ((patched-file (stgit-patched-file-at-point)))
199 (unless patched-file
200 (error "No patch or file on the current line"))
201 (let ((filename (expand-file-name (cdr patched-file))))
202 (unless (file-exists-p filename)
203 (error "File does not exist"))
204 (find-file filename)))
205 (setq curpatch (intern curpatch))
206 (setq stgit-expanded-patches
207 (if (memq curpatch stgit-expanded-patches)
208 (delq curpatch stgit-expanded-patches)
209 (cons curpatch stgit-expanded-patches)))
210 (stgit-reload))))
211
212(defun stgit-find-file-other-window ()
213 "Open file at point in other window"
214 (interactive)
215 (let ((patched-file (stgit-patched-file-at-point)))
216 (unless patched-file
217 (error "No file on the current line"))
218 (let ((filename (expand-file-name (cdr patched-file))))
219 (unless (file-exists-p filename)
220 (error "File does not exist"))
221 (find-file-other-window filename))))
222
83327d53 223(defun stgit-quit ()
a53347d9 224 "Hide the stgit buffer."
83327d53
GH
225 (interactive)
226 (bury-buffer))
227
0f076fe6 228(defun stgit-git-status ()
a53347d9 229 "Show status using `git-status'."
0f076fe6
GH
230 (interactive)
231 (unless (fboundp 'git-status)
232 (error "stgit-git-status requires git-status"))
233 (let ((dir default-directory))
234 (save-selected-window
235 (pop-to-buffer nil)
236 (git-status dir))))
237
378a003d
GH
238(defun stgit-next-line (&optional arg try-vscroll)
239 "Move cursor vertically down ARG lines"
240 (interactive "p\np")
241 (next-line arg try-vscroll)
242 (when (looking-at " \\S-")
243 (forward-char 2)))
244
245(defun stgit-previous-line (&optional arg try-vscroll)
246 "Move cursor vertically up ARG lines"
247 (interactive "p\np")
248 (previous-line arg try-vscroll)
249 (when (looking-at " \\S-")
250 (forward-char 2)))
251
252(defun stgit-next-patch (&optional arg)
253 "Move cursor down ARG patches"
254 (interactive "p")
255 (unless arg
256 (setq arg 1))
257 (if (< arg 0)
258 (stgit-previous-patch (- arg))
259 (while (not (zerop arg))
260 (setq arg (1- arg))
261 (while (progn (stgit-next-line)
262 (not (stgit-patch-at-point)))))))
263
264(defun stgit-previous-patch (&optional arg)
265 "Move cursor up ARG patches"
266 (interactive "p")
267 (unless arg
268 (setq arg 1))
269 (if (< arg 0)
270 (stgit-next-patch (- arg))
271 (while (not (zerop arg))
272 (setq arg (1- arg))
273 (while (progn (stgit-previous-line)
274 (not (stgit-patch-at-point)))))))
275
56d81fe5
DK
276(defvar stgit-mode-hook nil
277 "Run after `stgit-mode' is setup.")
278
279(defvar stgit-mode-map nil
280 "Keymap for StGit major mode.")
281
282(unless stgit-mode-map
283 (setq stgit-mode-map (make-keymap))
284 (suppress-keymap stgit-mode-map)
022a3664
GH
285 (mapc (lambda (arg) (define-key stgit-mode-map (car arg) (cdr arg)))
286 '((" " . stgit-mark)
3dccdc9b 287 ("m" . stgit-mark)
9b151b27
GH
288 ("\d" . stgit-unmark-up)
289 ("u" . stgit-unmark-down)
022a3664
GH
290 ("?" . stgit-help)
291 ("h" . stgit-help)
378a003d
GH
292 ("p" . stgit-previous-line)
293 ("n" . stgit-next-line)
294 ("\C-p" . stgit-previous-patch)
295 ("\C-n" . stgit-next-patch)
296 ("\M-{" . stgit-previous-patch)
297 ("\M-}" . stgit-next-patch)
0f076fe6 298 ("s" . stgit-git-status)
022a3664
GH
299 ("g" . stgit-reload)
300 ("r" . stgit-refresh)
301 ("\C-c\C-r" . stgit-rename)
302 ("e" . stgit-edit)
303 ("c" . stgit-coalesce)
304 ("N" . stgit-new)
305 ("R" . stgit-repair)
306 ("C" . stgit-commit)
307 ("U" . stgit-uncommit)
378a003d
GH
308 ("\r" . stgit-select)
309 ("o" . stgit-find-file-other-window)
022a3664
GH
310 (">" . stgit-push-next)
311 ("<" . stgit-pop-next)
312 ("P" . stgit-push-or-pop)
313 ("G" . stgit-goto)
314 ("=" . stgit-show)
315 ("D" . stgit-delete)
316 ([(control ?/)] . stgit-undo)
83327d53
GH
317 ("\C-_" . stgit-undo)
318 ("q" . stgit-quit))))
56d81fe5
DK
319
320(defun stgit-mode ()
321 "Major mode for interacting with StGit.
322Commands:
323\\{stgit-mode-map}"
324 (kill-all-local-variables)
325 (buffer-disable-undo)
326 (setq mode-name "StGit"
327 major-mode 'stgit-mode
328 goal-column 2)
329 (use-local-map stgit-mode-map)
330 (set (make-local-variable 'list-buffers-directory) default-directory)
6df83d42 331 (set (make-local-variable 'stgit-marked-patches) nil)
378a003d 332 (set (make-local-variable 'stgit-expanded-patches) nil)
2870f8b8 333 (set-variable 'truncate-lines 't)
56d81fe5
DK
334 (run-hooks 'stgit-mode-hook))
335
6df83d42
DK
336(defun stgit-add-mark (patch)
337 (let ((patchsym (intern patch)))
338 (setq stgit-marked-patches (cons patchsym stgit-marked-patches))))
339
340(defun stgit-remove-mark (patch)
341 (let ((patchsym (intern patch)))
342 (setq stgit-marked-patches (delq patchsym stgit-marked-patches))))
343
e6b1fdae
DK
344(defun stgit-clear-marks ()
345 (setq stgit-marked-patches '()))
346
6df83d42
DK
347(defun stgit-marked-patches ()
348 "Return the names of the marked patches."
349 (mapcar 'symbol-name stgit-marked-patches))
350
378a003d
GH
351(defun stgit-patch-at-point (&optional cause-error allow-file)
352 "Return the patch name on the current line.
353If CAUSE-ERROR is not nil, signal an error if none found.
354If ALLOW-FILE is not nil, also handle when point is on a file of
355a patch."
356 (or (and allow-file
357 (let ((patchsym (get-text-property (point) 'stgit-patchsym)))
358 (and patchsym
359 (symbol-name patchsym))))
360 (save-excursion
361 (beginning-of-line)
362 (and (looking-at "[>+-][ *]\\([^ ]*\\)")
363 (match-string-no-properties 1)))
364 (and cause-error
365 (error "No patch on this line"))))
366
367(defun stgit-patched-file-at-point ()
368 "Returns a cons of the patchsym and file name at point"
369 (let ((patchsym (get-text-property (point) 'stgit-patchsym)))
370 (when patchsym
371 (save-excursion
372 (beginning-of-line)
373 (when (looking-at " [A-Z] \\(.*\\)")
374 (cons patchsym (match-string-no-properties 1)))))))
56d81fe5 375
7755d7f1
KH
376(defun stgit-patches-marked-or-at-point ()
377 "Return the names of the marked patches, or the patch on the current line."
378 (if stgit-marked-patches
379 (stgit-marked-patches)
380 (let ((patch (stgit-patch-at-point)))
381 (if patch
382 (list patch)
383 '()))))
384
56d81fe5 385(defun stgit-goto-patch (patch)
a53347d9 386 "Move point to the line containing PATCH."
56d81fe5
DK
387 (let ((p (point)))
388 (goto-char (point-min))
6df83d42 389 (if (re-search-forward (concat "^[>+-][ *]" (regexp-quote patch) " ") nil t)
56d81fe5
DK
390 (progn (move-to-column goal-column)
391 t)
392 (goto-char p)
393 nil)))
394
1c2426dc 395(defun stgit-init ()
a53347d9 396 "Run stg init."
1c2426dc
DK
397 (interactive)
398 (stgit-capture-output nil
b0424080 399 (stgit-run "init"))
1f0bf00f 400 (stgit-reload))
1c2426dc 401
6df83d42 402(defun stgit-mark ()
a53347d9 403 "Mark the patch under point."
6df83d42 404 (interactive)
018fa1ac 405 (let ((patch (stgit-patch-at-point t)))
6df83d42 406 (stgit-add-mark patch)
1f0bf00f 407 (stgit-reload))
378a003d 408 (stgit-next-patch))
6df83d42 409
9b151b27 410(defun stgit-unmark-up ()
a53347d9 411 "Remove mark from the patch on the previous line."
6df83d42 412 (interactive)
378a003d 413 (stgit-previous-patch)
018fa1ac 414 (stgit-remove-mark (stgit-patch-at-point t))
9b151b27
GH
415 (stgit-reload))
416
417(defun stgit-unmark-down ()
a53347d9 418 "Remove mark from the patch on the current line."
9b151b27 419 (interactive)
018fa1ac 420 (stgit-remove-mark (stgit-patch-at-point t))
378a003d 421 (stgit-next-patch)
9b151b27 422 (stgit-reload))
6df83d42 423
56d81fe5 424(defun stgit-rename (name)
018fa1ac
GH
425 "Rename the patch under point to NAME."
426 (interactive (list (read-string "Patch name: " (stgit-patch-at-point t))))
427 (let ((old-name (stgit-patch-at-point t)))
56d81fe5
DK
428 (stgit-capture-output nil
429 (stgit-run "rename" old-name name))
378a003d
GH
430 (let ((old-name-sym (intern old-name))
431 (name-sym (intern name)))
432 (when (memq old-name-sym stgit-expanded-patches)
433 (setq stgit-expanded-patches
434 (cons name-sym (delq old-name-sym stgit-expanded-patches))))
435 (when (memq old-name-sym stgit-marked-patches)
436 (setq stgit-marked-patches
437 (cons name-sym (delq old-name-sym stgit-marked-patches)))))
1f0bf00f 438 (stgit-reload)
56d81fe5
DK
439 (stgit-goto-patch name)))
440
26201d96 441(defun stgit-repair ()
a53347d9 442 "Run stg repair."
26201d96
DK
443 (interactive)
444 (stgit-capture-output nil
b0424080 445 (stgit-run "repair"))
1f0bf00f 446 (stgit-reload))
26201d96 447
c4aad9a7
DK
448(defun stgit-commit ()
449 "Run stg commit."
450 (interactive)
451 (stgit-capture-output nil (stgit-run "commit"))
1f0bf00f 452 (stgit-reload))
c4aad9a7
DK
453
454(defun stgit-uncommit (arg)
455 "Run stg uncommit. Numeric arg determines number of patches to uncommit."
456 (interactive "p")
457 (stgit-capture-output nil (stgit-run "uncommit" "-n" (number-to-string arg)))
1f0bf00f 458 (stgit-reload))
c4aad9a7 459
0b661144
DK
460(defun stgit-push-next (npatches)
461 "Push the first unapplied patch.
462With numeric prefix argument, push that many patches."
463 (interactive "p")
464 (stgit-capture-output nil (stgit-run "push" "-n"
465 (number-to-string npatches)))
074a4fb0
GH
466 (stgit-reload)
467 (stgit-refresh-git-status))
56d81fe5 468
0b661144
DK
469(defun stgit-pop-next (npatches)
470 "Pop the topmost applied patch.
471With numeric prefix argument, pop that many patches."
472 (interactive "p")
473 (stgit-capture-output nil (stgit-run "pop" "-n" (number-to-string npatches)))
074a4fb0
GH
474 (stgit-reload)
475 (stgit-refresh-git-status))
56d81fe5 476
f9182fca
KH
477(defun stgit-applied-at-point ()
478 "Is the patch on the current line applied?"
479 (save-excursion
480 (beginning-of-line)
481 (looking-at "[>+]")))
482
483(defun stgit-push-or-pop ()
a53347d9 484 "Push or pop the patch on the current line."
f9182fca 485 (interactive)
018fa1ac 486 (let ((patch (stgit-patch-at-point t))
f9182fca
KH
487 (applied (stgit-applied-at-point)))
488 (stgit-capture-output nil
b0424080 489 (stgit-run (if applied "pop" "push") patch))
1f0bf00f 490 (stgit-reload)))
f9182fca 491
c7adf5ef 492(defun stgit-goto ()
a53347d9 493 "Go to the patch on the current line."
c7adf5ef 494 (interactive)
018fa1ac 495 (let ((patch (stgit-patch-at-point t)))
c7adf5ef 496 (stgit-capture-output nil
b0424080 497 (stgit-run "goto" patch))
1f0bf00f 498 (stgit-reload)))
c7adf5ef 499
378a003d
GH
500(defun stgit-id (patch)
501 "Return the git commit id for PATCH"
502 (let ((result (with-output-to-string
503 (stgit-run-silent "id" patch))))
504 (unless (string-match "^\\([0-9A-Fa-f]\\{40\\}\\)$" result)
505 (error "Cannot find commit id for %s" patch))
506 (match-string 1 result)))
507
56d81fe5 508(defun stgit-show ()
a53347d9 509 "Show the patch on the current line."
56d81fe5
DK
510 (interactive)
511 (stgit-capture-output "*StGit patch*"
378a003d
GH
512 (let ((patch (stgit-patch-at-point)))
513 (if (not patch)
514 (let ((patched-file (stgit-patched-file-at-point)))
515 (unless patched-file
516 (error "No patch or file at point"))
517 (let ((id (stgit-id (symbol-name (car patched-file)))))
518 (with-output-to-temp-buffer "*StGit diff*"
519 (stgit-run-git "diff" (concat id "^") id (cdr patched-file))
520 (with-current-buffer standard-output
521 (diff-mode)))))
522 (stgit-run "show" (stgit-patch-at-point))
523 (with-current-buffer standard-output
524 (goto-char (point-min))
525 (diff-mode))))))
0663524d 526
0bca35c8 527(defun stgit-edit ()
a53347d9 528 "Edit the patch on the current line."
0bca35c8 529 (interactive)
018fa1ac 530 (let ((patch (stgit-patch-at-point t))
0780be79 531 (edit-buf (get-buffer-create "*StGit edit*"))
0bca35c8
DK
532 (dir default-directory))
533 (log-edit 'stgit-confirm-edit t nil edit-buf)
534 (set (make-local-variable 'stgit-edit-patch) patch)
535 (setq default-directory dir)
536 (let ((standard-output edit-buf))
9aecd505 537 (stgit-run-silent "edit" "--save-template=-" patch))))
0bca35c8
DK
538
539(defun stgit-confirm-edit ()
540 (interactive)
541 (let ((file (make-temp-file "stgit-edit-")))
542 (write-region (point-min) (point-max) file)
543 (stgit-capture-output nil
544 (stgit-run "edit" "-f" file stgit-edit-patch))
545 (with-current-buffer log-edit-parent-buffer
1f0bf00f 546 (stgit-reload))))
0bca35c8 547
64c097a0 548(defun stgit-new ()
a53347d9 549 "Create a new patch."
64c097a0 550 (interactive)
c5d45b92
GH
551 (let ((edit-buf (get-buffer-create "*StGit edit*"))
552 (dir default-directory))
553 (log-edit 'stgit-confirm-new t nil edit-buf)
554 (setq default-directory dir)))
64c097a0
DK
555
556(defun stgit-confirm-new ()
557 (interactive)
27b0f9e4 558 (let ((file (make-temp-file "stgit-edit-")))
64c097a0
DK
559 (write-region (point-min) (point-max) file)
560 (stgit-capture-output nil
27b0f9e4 561 (stgit-run "new" "-f" file))
64c097a0 562 (with-current-buffer log-edit-parent-buffer
1f0bf00f 563 (stgit-reload))))
64c097a0
DK
564
565(defun stgit-create-patch-name (description)
566 "Create a patch name from a long description"
567 (let ((patch ""))
568 (while (> (length description) 0)
569 (cond ((string-match "\\`[a-zA-Z_-]+" description)
570 (setq patch (downcase (concat patch (match-string 0 description))))
571 (setq description (substring description (match-end 0))))
572 ((string-match "\\` +" description)
573 (setq patch (concat patch "-"))
574 (setq description (substring description (match-end 0))))
575 ((string-match "\\`[^a-zA-Z_-]+" description)
576 (setq description (substring description (match-end 0))))))
577 (cond ((= (length patch) 0)
578 "patch")
579 ((> (length patch) 20)
580 (substring patch 0 20))
581 (t patch))))
0bca35c8 582
7755d7f1 583(defun stgit-delete (patch-names)
a53347d9 584 "Delete the named patches."
7755d7f1
KH
585 (interactive (list (stgit-patches-marked-or-at-point)))
586 (if (zerop (length patch-names))
587 (error "No patches to delete")
588 (when (yes-or-no-p (format "Really delete %d patches? "
589 (length patch-names)))
590 (stgit-capture-output nil
591 (apply 'stgit-run "delete" patch-names))
1f0bf00f 592 (stgit-reload))))
7755d7f1 593
ea0def18 594(defun stgit-coalesce (patch-names)
a53347d9 595 "Run stg coalesce on the named patches."
ea0def18 596 (interactive (list (stgit-marked-patches)))
0780be79 597 (let ((edit-buf (get-buffer-create "*StGit edit*"))
ea0def18
DK
598 (dir default-directory))
599 (log-edit 'stgit-confirm-coalesce t nil edit-buf)
600 (set (make-local-variable 'stgit-patches) patch-names)
601 (setq default-directory dir)
602 (let ((standard-output edit-buf))
9aecd505 603 (apply 'stgit-run-silent "coalesce" "--save-template=-" patch-names))))
ea0def18
DK
604
605(defun stgit-confirm-coalesce ()
606 (interactive)
607 (let ((file (make-temp-file "stgit-edit-")))
608 (write-region (point-min) (point-max) file)
609 (stgit-capture-output nil
610 (apply 'stgit-run "coalesce" "-f" file stgit-patches))
611 (with-current-buffer log-edit-parent-buffer
e6b1fdae
DK
612 (stgit-clear-marks)
613 ;; Go to first marked patch and stay there
614 (goto-char (point-min))
615 (re-search-forward (concat "^[>+-]\\*") nil t)
616 (move-to-column goal-column)
617 (let ((pos (point)))
1f0bf00f 618 (stgit-reload)
e6b1fdae 619 (goto-char pos)))))
ea0def18 620
0663524d
KH
621(defun stgit-help ()
622 "Display help for the StGit mode."
623 (interactive)
624 (describe-function 'stgit-mode))
3a59f3db 625
83e51dbf
DK
626(defun stgit-undo (&optional arg)
627 "Run stg undo.
628With prefix argument, run it with the --hard flag."
629 (interactive "P")
630 (stgit-capture-output nil
631 (if arg
632 (stgit-run "undo" "--hard")
633 (stgit-run "undo")))
1f0bf00f 634 (stgit-reload))
83e51dbf 635
4d73c4d8
DK
636(defun stgit-refresh (&optional arg)
637 "Run stg refresh.
a53347d9 638With prefix argument, refresh the marked patch or the patch under point."
4d73c4d8
DK
639 (interactive "P")
640 (let ((patchargs (if arg
b0424080
GH
641 (let ((patches (stgit-patches-marked-or-at-point)))
642 (cond ((null patches)
643 (error "no patch to update"))
644 ((> (length patches) 1)
645 (error "too many patches selected"))
646 (t
647 (cons "-p" patches))))
648 nil)))
4d73c4d8 649 (stgit-capture-output nil
074a4fb0
GH
650 (apply 'stgit-run "refresh" patchargs))
651 (stgit-refresh-git-status))
4d73c4d8
DK
652 (stgit-reload))
653
3a59f3db 654(provide 'stgit)