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