chiark / gitweb /
stgit.el: Add an stgit customization group
[stgit] / contrib / stgit.el
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
12 (require 'git nil t)
13
14 (defun stgit (dir)
15   "Manage StGit patches for the tree in DIR."
16   (interactive "DDirectory: \n")
17   (switch-to-stgit-buffer (git-get-top-dir dir))
18   (stgit-reload))
19
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
34 directory 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))))))
42
43 (defun switch-to-stgit-buffer (dir)
44   "Switch to a (possibly new) buffer displaying StGit patches for DIR."
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.
58 Argument 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)
68   "Capture StGit output and show it in a window at the end."
69   `(let ((output-buf (get-buffer-create ,(or name "*StGit output*")))
70          (stgit-dir default-directory)
71          (inhibit-read-only t))
72      (with-current-buffer output-buf
73        (erase-buffer)
74        (setq default-directory stgit-dir)
75        (setq buffer-read-only t))
76      (let ((standard-output output-buf))
77        ,@body)
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)))))
83 (put 'stgit-capture-output 'lisp-indent-function 1)
84
85 (defun stgit-run-silent (&rest args)
86   (apply 'call-process "stg" nil standard-output nil args))
87
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
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
100 (defun stgit-reload ()
101   "Update the contents of the StGit buffer."
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: ")
108     (stgit-run-silent "branch")
109     (stgit-run-silent "series" "--description")
110     (stgit-rescan)
111     (if curpatch
112         (stgit-goto-patch curpatch)
113       (goto-line curline)))
114   (stgit-refresh-git-status))
115
116 (defgroup stgit nil
117   "A user interface for the StGit patch maintenance tool."
118   :group 'tools)
119
120 (defface stgit-description-face
121   '((((background dark)) (:foreground "tan"))
122     (((background light)) (:foreground "dark red")))
123   "The face used for StGit descriptions"
124   :group 'stgit)
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)))
130   "The face used for the top patch names"
131   :group 'stgit)
132
133 (defface stgit-applied-patch-face
134   '((((background dark)) (:foreground "light yellow"))
135     (((background light)) (:foreground "purple"))
136     (t ()))
137   "The face used for applied patch names"
138   :group 'stgit)
139
140 (defface stgit-unapplied-patch-face
141   '((((background dark)) (:foreground "gray80"))
142     (((background light)) (:foreground "orchid"))
143     (t ()))
144   "The face used for unapplied patch names"
145   :group 'stgit)
146
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
169 (defun stgit-rescan ()
170   "Rescan the status buffer."
171   (save-excursion
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))
178               ((looking-at "\\([>+-]\\)\\( \\)\\([^ ]+\\) *[|#] \\(.*\\)")
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)
190                    (setq marked (cons patchsym marked)))
191                  (when (memq patchsym stgit-expanded-patches)
192                    (stgit-expand-patch patchsym))
193                  ))
194               ((or (looking-at "stg series: Branch \".*\" not initialised")
195                    (looking-at "stg series: .*: branch not initialized"))
196                (forward-line 1)
197                (insert "Run M-x stgit-init to initialise")))
198         (forward-line 1))
199       (setq stgit-marked-patches (nreverse marked)))))
200
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
231 (defun stgit-quit ()
232   "Hide the stgit buffer."
233   (interactive)
234   (bury-buffer))
235
236 (defun stgit-git-status ()
237   "Show status using `git-status'."
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
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
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)
293   (mapc (lambda (arg) (define-key stgit-mode-map (car arg) (cdr arg)))
294         '((" " .        stgit-mark)
295           ("m" .        stgit-mark)
296           ("\d" .       stgit-unmark-up)
297           ("u" .        stgit-unmark-down)
298           ("?" .        stgit-help)
299           ("h" .        stgit-help)
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)
306           ("s" .        stgit-git-status)
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)
316           ("\r" .       stgit-select)
317           ("o" .        stgit-find-file-other-window)
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)
325           ("\C-_" .     stgit-undo)
326           ("q" . stgit-quit))))
327
328 (defun stgit-mode ()
329   "Major mode for interacting with StGit.
330 Commands:
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)
339   (set (make-local-variable 'stgit-marked-patches) nil)
340   (set (make-local-variable 'stgit-expanded-patches) nil)
341   (set-variable 'truncate-lines 't)
342   (run-hooks 'stgit-mode-hook))
343
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
352 (defun stgit-clear-marks ()
353   (setq stgit-marked-patches '()))
354
355 (defun stgit-marked-patches ()
356   "Return the names of the marked patches."
357   (mapcar 'symbol-name stgit-marked-patches))
358
359 (defun stgit-patch-at-point (&optional cause-error allow-file)
360   "Return the patch name on the current line.
361 If CAUSE-ERROR is not nil, signal an error if none found.
362 If ALLOW-FILE is not nil, also handle when point is on a file of
363 a 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)))))))
383
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
393 (defun stgit-goto-patch (patch)
394   "Move point to the line containing PATCH."
395   (let ((p (point)))
396     (goto-char (point-min))
397     (if (re-search-forward (concat "^[>+-][ *]" (regexp-quote patch) " ") nil t)
398         (progn (move-to-column goal-column)
399                t)
400       (goto-char p)
401       nil)))
402
403 (defun stgit-init ()
404   "Run stg init."
405   (interactive)
406   (stgit-capture-output nil
407     (stgit-run "init"))
408   (stgit-reload))
409
410 (defun stgit-mark ()
411   "Mark the patch under point."
412   (interactive)
413   (let ((patch (stgit-patch-at-point t)))
414     (stgit-add-mark patch)
415     (stgit-reload))
416   (stgit-next-patch))
417
418 (defun stgit-unmark-up ()
419   "Remove mark from the patch on the previous line."
420   (interactive)
421   (stgit-previous-patch)
422   (stgit-remove-mark (stgit-patch-at-point t))
423   (stgit-reload))
424
425 (defun stgit-unmark-down ()
426   "Remove mark from the patch on the current line."
427   (interactive)
428   (stgit-remove-mark (stgit-patch-at-point t))
429   (stgit-next-patch)
430   (stgit-reload))
431
432 (defun stgit-rename (name)
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)))
436     (stgit-capture-output nil
437       (stgit-run "rename" old-name name))
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)))))
446     (stgit-reload)
447     (stgit-goto-patch name)))
448
449 (defun stgit-repair ()
450   "Run stg repair."
451   (interactive)
452   (stgit-capture-output nil
453     (stgit-run "repair"))
454   (stgit-reload))
455
456 (defun stgit-commit ()
457   "Run stg commit."
458   (interactive)
459   (stgit-capture-output nil (stgit-run "commit"))
460   (stgit-reload))
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)))
466   (stgit-reload))
467
468 (defun stgit-push-next (npatches)
469   "Push the first unapplied patch.
470 With numeric prefix argument, push that many patches."
471   (interactive "p")
472   (stgit-capture-output nil (stgit-run "push" "-n"
473                                        (number-to-string npatches)))
474   (stgit-reload)
475   (stgit-refresh-git-status))
476
477 (defun stgit-pop-next (npatches)
478   "Pop the topmost applied patch.
479 With numeric prefix argument, pop that many patches."
480   (interactive "p")
481   (stgit-capture-output nil (stgit-run "pop" "-n" (number-to-string npatches)))
482   (stgit-reload)
483   (stgit-refresh-git-status))
484
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 ()
492   "Push or pop the patch on the current line."
493   (interactive)
494   (let ((patch (stgit-patch-at-point t))
495         (applied (stgit-applied-at-point)))
496     (stgit-capture-output nil
497       (stgit-run (if applied "pop" "push") patch))
498     (stgit-reload)))
499
500 (defun stgit-goto ()
501   "Go to the patch on the current line."
502   (interactive)
503   (let ((patch (stgit-patch-at-point t)))
504     (stgit-capture-output nil
505       (stgit-run "goto" patch))
506     (stgit-reload)))
507
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
516 (defun stgit-show ()
517   "Show the patch on the current line."
518   (interactive)
519   (stgit-capture-output "*StGit patch*"
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))))))
534
535 (defun stgit-edit ()
536   "Edit the patch on the current line."
537   (interactive)
538   (let ((patch (stgit-patch-at-point t))
539         (edit-buf (get-buffer-create "*StGit edit*"))
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))
545       (stgit-run-silent "edit" "--save-template=-" patch))))
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
554       (stgit-reload))))
555
556 (defun stgit-new ()
557   "Create a new patch."
558   (interactive)
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)))
563
564 (defun stgit-confirm-new ()
565   (interactive)
566   (let ((file (make-temp-file "stgit-edit-")))
567     (write-region (point-min) (point-max) file)
568     (stgit-capture-output nil
569       (stgit-run "new" "-f" file))
570     (with-current-buffer log-edit-parent-buffer
571       (stgit-reload))))
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))))
590
591 (defun stgit-delete (patch-names)
592   "Delete the named patches."
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))
600       (stgit-reload))))
601
602 (defun stgit-coalesce (patch-names)
603   "Run stg coalesce on the named patches."
604   (interactive (list (stgit-marked-patches)))
605   (let ((edit-buf (get-buffer-create "*StGit edit*"))
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))
611       (apply 'stgit-run-silent "coalesce" "--save-template=-" patch-names))))
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
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)))
626         (stgit-reload)
627         (goto-char pos)))))
628
629 (defun stgit-help ()
630   "Display help for the StGit mode."
631   (interactive)
632   (describe-function 'stgit-mode))
633
634 (defun stgit-undo (&optional arg)
635   "Run stg undo.
636 With 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")))
642   (stgit-reload))
643
644 (defun stgit-refresh (&optional arg)
645   "Run stg refresh.
646 With prefix argument, refresh the marked patch or the patch under point."
647   (interactive "P")
648   (let ((patchargs (if arg
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)))
657     (stgit-capture-output nil
658       (apply 'stgit-run "refresh" patchargs))
659     (stgit-refresh-git-status))
660   (stgit-reload))
661
662 (provide 'stgit)