chiark / gitweb /
stgit.el: Add customizable face for "Index" and "Work tree" titles
[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 (require 'cl)
14 (require 'ewoc)
15
16 (defun stgit (dir)
17   "Manage StGit patches for the tree in DIR."
18   (interactive "DDirectory: \n")
19   (switch-to-stgit-buffer (git-get-top-dir dir))
20   (stgit-reload))
21
22 (unless (fboundp 'git-get-top-dir)
23   (defun git-get-top-dir (dir)
24     "Retrieve the top-level directory of a git tree."
25     (let ((cdup (with-output-to-string
26                   (with-current-buffer standard-output
27                     (cd dir)
28                     (unless (eq 0 (call-process "git" nil t nil
29                                                 "rev-parse" "--show-cdup"))
30                       (error "Cannot find top-level git tree for %s" dir))))))
31       (expand-file-name (concat (file-name-as-directory dir)
32                                 (car (split-string cdup "\n")))))))
33
34 (defun stgit-refresh-git-status (&optional dir)
35   "If it exists, refresh the `git-status' buffer belonging to
36 directory DIR or `default-directory'"
37   (when (and (fboundp 'git-find-status-buffer)
38              (fboundp 'git-refresh-status))
39     (let* ((top-dir (git-get-top-dir (or dir default-directory)))
40            (git-status-buffer (and top-dir (git-find-status-buffer top-dir))))
41       (when git-status-buffer
42         (with-current-buffer git-status-buffer
43           (git-refresh-status))))))
44
45 (defun stgit-find-buffer (dir)
46   "Return the buffer displaying StGit patches for DIR, or nil if none."
47   (setq dir (file-name-as-directory dir))
48   (let ((buffers (buffer-list)))
49     (while (and buffers
50                 (not (with-current-buffer (car buffers)
51                        (and (eq major-mode 'stgit-mode)
52                             (string= default-directory dir)))))
53       (setq buffers (cdr buffers)))
54     (and buffers (car buffers))))
55
56 (defun switch-to-stgit-buffer (dir)
57   "Switch to a (possibly new) buffer displaying StGit patches for DIR."
58   (setq dir (file-name-as-directory dir))
59   (let ((buffer (stgit-find-buffer dir)))
60     (switch-to-buffer (or buffer
61                           (create-stgit-buffer dir)))))
62
63 (defstruct (stgit-patch)
64   status name desc empty files-ewoc)
65
66 (defun stgit-patch-pp (patch)
67   (let ((status (stgit-patch-status patch))
68         (start (point))
69         (name (stgit-patch-name patch)))
70     (case name
71        (:index (insert (propertize "  Index"
72                                    'face 'stgit-index-work-tree-title-face)))
73        (:work  (insert (propertize "  Work tree"
74                                    'face 'stgit-index-work-tree-title-face)))
75        (t (insert (case status
76                     ('applied "+")
77                     ('top ">")
78                     ('unapplied "-"))
79                   (if (memq name stgit-marked-patches)
80                       "*" " ")
81                   (propertize (format "%-30s"
82                                       (symbol-name name))
83                               'face (case status
84                                       ('applied 'stgit-applied-patch-face)
85                                       ('top 'stgit-top-patch-face)
86                                       ('unapplied 'stgit-unapplied-patch-face)
87                                       ('index nil)
88                                       ('work nil)))
89                   "  "
90                   (if (stgit-patch-empty patch) "(empty) " "")
91                   (propertize (or (stgit-patch-desc patch) "")
92                               'face 'stgit-description-face))))
93     (put-text-property start (point) 'entry-type 'patch)
94     (when (memq name stgit-expanded-patches)
95       (stgit-insert-patch-files patch))
96     (put-text-property start (point) 'patch-data patch)))
97
98 (defun create-stgit-buffer (dir)
99   "Create a buffer for showing StGit patches.
100 Argument DIR is the repository path."
101   (let ((buf (create-file-buffer (concat dir "*stgit*")))
102         (inhibit-read-only t))
103     (with-current-buffer buf
104       (setq default-directory dir)
105       (stgit-mode)
106       (set (make-local-variable 'stgit-ewoc)
107            (ewoc-create #'stgit-patch-pp "Branch:\n" "--"))
108       (setq buffer-read-only t))
109     buf))
110
111 (defmacro stgit-capture-output (name &rest body)
112   "Capture StGit output and, if there was any output, show it in a window
113 at the end.
114 Returns nil if there was no output."
115   (declare (debug ([&or stringp null] body))
116            (indent 1))
117   `(let ((output-buf (get-buffer-create ,(or name "*StGit output*")))
118          (stgit-dir default-directory)
119          (inhibit-read-only t))
120      (with-current-buffer output-buf
121        (erase-buffer)
122        (setq default-directory stgit-dir)
123        (setq buffer-read-only t))
124      (let ((standard-output output-buf))
125        ,@body)
126      (with-current-buffer output-buf
127        (set-buffer-modified-p nil)
128        (setq buffer-read-only t)
129        (if (< (point-min) (point-max))
130            (display-buffer output-buf t)))))
131
132 (defun stgit-make-run-args (args)
133   "Return a copy of ARGS with its elements converted to strings."
134   (mapcar (lambda (x)
135             ;; don't use (format "%s" ...) to limit type errors
136             (cond ((stringp x) x)
137                   ((integerp x) (number-to-string x))
138                   ((symbolp x) (symbol-name x))
139                   (t
140                    (error "Bad element in stgit-make-run-args args: %S" x))))
141           args))
142
143 (defun stgit-run-silent (&rest args)
144   (setq args (stgit-make-run-args args))
145   (apply 'call-process "stg" nil standard-output nil args))
146
147 (defun stgit-run (&rest args)
148   (setq args (stgit-make-run-args args))
149   (let ((msgcmd (mapconcat #'identity args " ")))
150     (message "Running stg %s..." msgcmd)
151     (apply 'call-process "stg" nil standard-output nil args)
152     (message "Running stg %s...done" msgcmd)))
153
154 (defun stgit-run-git (&rest args)
155   (setq args (stgit-make-run-args args))
156   (let ((msgcmd (mapconcat #'identity args " ")))
157     (message "Running git %s..." msgcmd)
158     (apply 'call-process "git" nil standard-output nil args)
159     (message "Running git %s...done" msgcmd)))
160
161 (defun stgit-run-git-silent (&rest args)
162   (setq args (stgit-make-run-args args))
163   (apply 'call-process "git" nil standard-output nil args))
164
165 (defun stgit-index-empty-p ()
166   "Returns non-nil if the index contains no changes from HEAD."
167   (zerop (stgit-run-git-silent "diff-index" "--cached" "--quiet" "HEAD")))
168
169 (defvar stgit-index-node)
170 (defvar stgit-worktree-node)
171
172 (defun stgit-refresh-index ()
173   (when stgit-index-node
174     (ewoc-invalidate (car stgit-index-node) (cdr stgit-index-node))))
175
176 (defun stgit-refresh-worktree ()
177   (when stgit-worktree-node
178     (ewoc-invalidate (car stgit-worktree-node) (cdr stgit-worktree-node))))
179
180 (defun stgit-run-series (ewoc)
181   (let ((first-line t))
182     (with-temp-buffer
183       (let ((exit-status (stgit-run-silent "series" "--description" "--empty")))
184         (goto-char (point-min))
185         (if (not (zerop exit-status))
186             (cond ((looking-at "stg series: \\(.*\\)")
187                    (ewoc-set-hf ewoc (car (ewoc-get-hf ewoc))
188                                 "-- not initialized (run M-x stgit-init)"))
189                   ((looking-at ".*")
190                    (error "Error running stg: %s"
191                           (match-string 0))))
192           (while (not (eobp))
193             (unless (looking-at
194                      "\\([0 ]\\)\\([>+-]\\)\\( \\)\\([^ ]+\\) *[|#] \\(.*\\)")
195               (error "Syntax error in output from stg series"))
196             (let* ((state-str (match-string 2))
197                    (state (cond ((string= state-str ">") 'top)
198                                 ((string= state-str "+") 'applied)
199                                 ((string= state-str "-") 'unapplied))))
200               (ewoc-enter-last ewoc
201                                (make-stgit-patch
202                                 :status state
203                                 :name (intern (match-string 4))
204                                 :desc (match-string 5)
205                                 :empty (string= (match-string 1) "0"))))
206             (setq first-line nil)
207             (forward-line 1)))))
208     (if stgit-show-worktree
209         (setq stgit-index-node (cons ewoc (ewoc-enter-last ewoc
210                                                            (make-stgit-patch
211                                                             :status 'index
212                                                             :name :index
213                                                             :desc nil
214                                                             :empty nil)))
215               stgit-worktree-node (cons ewoc (ewoc-enter-last ewoc
216                                                               (make-stgit-patch
217                                                                :status 'work
218                                                                :name :work
219                                                                :desc nil
220                                                                :empty nil))))
221       (setq stgit-worktree-node nil))))
222
223
224 (defun stgit-reload ()
225   "Update the contents of the StGit buffer."
226   (interactive)
227   (let ((inhibit-read-only t)
228         (curline (line-number-at-pos))
229         (curpatch (stgit-patch-name-at-point)))
230     (ewoc-filter stgit-ewoc #'(lambda (x) nil))
231     (ewoc-set-hf stgit-ewoc
232                  (concat "Branch: "
233                          (propertize
234                           (with-temp-buffer
235                             (stgit-run-silent "branch")
236                             (buffer-substring (point-min) (1- (point-max))))
237                           'face 'stgit-branch-name-face)
238                          "\n")
239                  (if stgit-show-worktree
240                      "--"
241                    (propertize
242                     (substitute-command-keys "--\n\"\\[stgit-toggle-worktree]\"\
243  shows the working tree\n")
244                    'face 'stgit-description-face)))
245     (stgit-run-series stgit-ewoc)
246     (if curpatch
247         (stgit-goto-patch curpatch)
248       (goto-line curline)))
249   (stgit-refresh-git-status))
250
251 (defgroup stgit nil
252   "A user interface for the StGit patch maintenance tool."
253   :group 'tools)
254
255 (defface stgit-description-face
256   '((((background dark)) (:foreground "tan"))
257     (((background light)) (:foreground "dark red")))
258   "The face used for StGit descriptions"
259   :group 'stgit)
260
261 (defface stgit-branch-name-face
262   '((t :inherit bold))
263   "The face used for the StGit branch name"
264   :group 'stgit)
265
266 (defface stgit-top-patch-face
267   '((((background dark)) (:weight bold :foreground "yellow"))
268     (((background light)) (:weight bold :foreground "purple"))
269     (t (:weight bold)))
270   "The face used for the top patch names"
271   :group 'stgit)
272
273 (defface stgit-applied-patch-face
274   '((((background dark)) (:foreground "light yellow"))
275     (((background light)) (:foreground "purple"))
276     (t ()))
277   "The face used for applied patch names"
278   :group 'stgit)
279
280 (defface stgit-unapplied-patch-face
281   '((((background dark)) (:foreground "gray80"))
282     (((background light)) (:foreground "orchid"))
283     (t ()))
284   "The face used for unapplied patch names"
285   :group 'stgit)
286
287 (defface stgit-modified-file-face
288   '((((class color) (background light)) (:foreground "purple"))
289     (((class color) (background dark)) (:foreground "salmon")))
290   "StGit mode face used for modified file status"
291   :group 'stgit)
292
293 (defface stgit-unmerged-file-face
294   '((((class color) (background light)) (:foreground "red" :bold t))
295     (((class color) (background dark)) (:foreground "red" :bold t)))
296   "StGit mode face used for unmerged file status"
297   :group 'stgit)
298
299 (defface stgit-unknown-file-face
300   '((((class color) (background light)) (:foreground "goldenrod" :bold t))
301     (((class color) (background dark)) (:foreground "goldenrod" :bold t)))
302   "StGit mode face used for unknown file status"
303   :group 'stgit)
304
305 (defface stgit-file-permission-face
306   '((((class color) (background light)) (:foreground "green" :bold t))
307     (((class color) (background dark)) (:foreground "green" :bold t)))
308   "StGit mode face used for permission changes."
309   :group 'stgit)
310
311 (defface stgit-index-work-tree-title-face
312   '((((supports :slant italic)) :slant italic)
313     (t :inherit bold))
314   "StGit mode face used for the \"Index\" and \"Work tree\" titles"
315   :group 'stgit)
316
317
318 (defcustom stgit-expand-find-copies-harder
319   nil
320   "Try harder to find copied files when listing patches.
321
322 When not nil, runs git diff-tree with the --find-copies-harder
323 flag, which reduces performance."
324   :type 'boolean
325   :group 'stgit)
326
327 (defconst stgit-file-status-code-strings
328   (mapcar (lambda (arg)
329             (cons (car arg)
330                   (propertize (cadr arg) 'face (car (cddr arg)))))
331           '((add         "Added"       stgit-modified-file-face)
332             (copy        "Copied"      stgit-modified-file-face)
333             (delete      "Deleted"     stgit-modified-file-face)
334             (modify      "Modified"    stgit-modified-file-face)
335             (rename      "Renamed"     stgit-modified-file-face)
336             (mode-change "Mode change" stgit-modified-file-face)
337             (unmerged    "Unmerged"    stgit-unmerged-file-face)
338             (unknown     "Unknown"     stgit-unknown-file-face)))
339   "Alist of code symbols to description strings")
340
341 (defun stgit-file-status-code-as-string (file)
342   "Return stgit status code for FILE as a string"
343   (let* ((code (assq (stgit-file-status file)
344                      stgit-file-status-code-strings))
345          (score (stgit-file-cr-score file)))
346     (when code
347       (format "%-11s  "
348               (if (and score (/= score 100))
349                   (format "%s %s" (cdr code)
350                           (propertize (format "%d%%" score)
351                                       'face 'stgit-description-face))
352                 (cdr code))))))
353
354 (defun stgit-file-status-code (str &optional score)
355   "Return stgit status code from git status string"
356   (let ((code (assoc str '(("A" . add)
357                            ("C" . copy)
358                            ("D" . delete)
359                            ("M" . modify)
360                            ("R" . rename)
361                            ("T" . mode-change)
362                            ("U" . unmerged)
363                            ("X" . unknown)))))
364     (setq code (if code (cdr code) 'unknown))
365     (when (stringp score)
366       (if (> (length score) 0)
367           (setq score (string-to-number score))
368         (setq score nil)))
369     (if score (cons code score) code)))
370
371 (defconst stgit-file-type-strings
372   '((#o100 . "file")
373     (#o120 . "symlink")
374     (#o160 . "subproject"))
375   "Alist of names of file types")
376
377 (defun stgit-file-type-string (type)
378   "Return string describing file type TYPE (the high bits of file permission).
379 Cf. `stgit-file-type-strings' and `stgit-file-type-change-string'."
380   (let ((type-str (assoc type stgit-file-type-strings)))
381     (or (and type-str (cdr type-str))
382         (format "unknown type %o" type))))
383
384 (defun stgit-file-type-change-string (old-perm new-perm)
385   "Return string describing file type change from OLD-PERM to NEW-PERM.
386 Cf. `stgit-file-type-string'."
387   (let ((old-type (lsh old-perm -9))
388         (new-type (lsh new-perm -9)))
389     (cond ((= old-type new-type) "")
390           ((zerop new-type) "")
391           ((zerop old-type)
392            (if (= new-type #o100)
393                ""
394              (format "   (%s)" (stgit-file-type-string new-type))))
395           (t (format "   (%s -> %s)"
396                      (stgit-file-type-string old-type)
397                      (stgit-file-type-string new-type))))))
398
399 (defun stgit-file-mode-change-string (old-perm new-perm)
400   "Return string describing file mode change from OLD-PERM to NEW-PERM.
401 Cf. `stgit-file-type-change-string'."
402   (setq old-perm (logand old-perm #o777)
403         new-perm (logand new-perm #o777))
404   (if (or (= old-perm new-perm)
405           (zerop old-perm)
406           (zerop new-perm))
407       ""
408     (let* ((modified       (logxor old-perm new-perm))
409            (not-x-modified (logand (logxor old-perm new-perm) #o666)))
410       (cond ((zerop modified) "")
411             ((and (zerop not-x-modified)
412                   (or (and (eq #o111 (logand old-perm #o111))
413                            (propertize "-x" 'face 'stgit-file-permission-face))
414                       (and (eq #o111 (logand new-perm #o111))
415                            (propertize "+x" 'face
416                                        'stgit-file-permission-face)))))
417             (t (concat (propertize (format "%o" old-perm)
418                                    'face 'stgit-file-permission-face)
419                        (propertize " -> "
420                                    'face 'stgit-description-face)
421                        (propertize (format "%o" new-perm)
422                                    'face 'stgit-file-permission-face)))))))
423
424 (defstruct (stgit-file)
425   old-perm new-perm copy-or-rename cr-score cr-from cr-to status file)
426
427 (defun stgit-file-pp (file)
428   (let ((status (stgit-file-status file))
429         (name (if (stgit-file-copy-or-rename file)
430                   (concat (stgit-file-cr-from file)
431                           (propertize " -> "
432                                       'face 'stgit-description-face)
433                           (stgit-file-cr-to file))
434                 (stgit-file-file file)))
435         (mode-change (stgit-file-mode-change-string
436                       (stgit-file-old-perm file)
437                       (stgit-file-new-perm file)))
438         (start (point)))
439     (insert (format "    %-12s%1s%s%s\n"
440                     (stgit-file-status-code-as-string file)
441                     mode-change
442                     name
443                     (propertize (stgit-file-type-change-string
444                                  (stgit-file-old-perm file)
445                                  (stgit-file-new-perm file))
446                                 'face 'stgit-description-face)))
447     (add-text-properties start (point)
448                          (list 'entry-type 'file
449                                'file-data file))))
450
451 (defun stgit-find-copies-harder-diff-arg ()
452   "Return the flag to use with `git-diff' depending on the
453 `stgit-expand-find-copies-harder' flag."
454   (if stgit-expand-find-copies-harder
455       "--find-copies-harder"
456     "-C"))
457
458 (defun stgit-insert-patch-files (patch)
459   "Expand (show modification of) the patch PATCH after the line
460 at point."
461   (let* ((patchsym (stgit-patch-name patch))
462          (end (progn (insert "#") (prog1 (point-marker) (forward-char -1))))
463          (args (list "-z" (stgit-find-copies-harder-diff-arg)))
464          (ewoc (ewoc-create #'stgit-file-pp nil nil t)))
465     (setf (stgit-patch-files-ewoc patch) ewoc)
466     (with-temp-buffer
467       (apply 'stgit-run-git
468              (cond ((eq patchsym :work)
469                     `("diff-files" ,@args))
470                    ((eq patchsym :index)
471                     `("diff-index" ,@args "--cached" "HEAD"))
472                    (t
473                     `("diff-tree" ,@args "-r" ,(stgit-id patchsym)))))
474       (goto-char (point-min))
475       (unless (or (eobp) (memq patchsym '(:work :index)))
476         (forward-char 41))
477       (while (looking-at ":\\([0-7]+\\) \\([0-7]+\\) [0-9A-Fa-f]\\{40\\} [0-9A-Fa-f]\\{40\\} ")
478         (let ((old-perm (string-to-number (match-string 1) 8))
479               (new-perm (string-to-number (match-string 2) 8)))
480           (goto-char (match-end 0))
481           (let ((file
482                  (cond ((looking-at
483                          "\\([CR]\\)\\([0-9]*\\)\0\\([^\0]*\\)\0\\([^\0]*\\)\0")
484                         (let* ((patch-status (stgit-patch-status patch))
485                                (file-subexp  (if (eq patch-status 'unapplied)
486                                                  3
487                                                4))
488                                (file         (match-string file-subexp)))
489                           (make-stgit-file
490                            :old-perm       old-perm
491                            :new-perm       new-perm
492                            :copy-or-rename t
493                            :cr-score       (string-to-number (match-string 2))
494                            :cr-from        (match-string 3)
495                            :cr-to          (match-string 4)
496                            :status         (stgit-file-status-code
497                                             (match-string 1))
498                            :file           file)))
499                        ((looking-at "\\([ABD-QS-Z]\\)\0\\([^\0]*\\)\0")
500                         (make-stgit-file
501                          :old-perm       old-perm
502                          :new-perm       new-perm
503                          :copy-or-rename nil
504                          :cr-score       nil
505                          :cr-from        nil
506                          :cr-to          nil
507                          :status         (stgit-file-status-code
508                                           (match-string 1))
509                          :file           (match-string 2))))))
510             (ewoc-enter-last ewoc file))
511           (goto-char (match-end 0))))
512       (unless (ewoc-nth ewoc 0)
513         (ewoc-set-hf ewoc "" (propertize "    <no files>\n"
514                                          'face 'stgit-description-face))))
515     (goto-char end)
516     (delete-char -2)))
517
518 (defun stgit-select-file ()
519   (let ((filename (expand-file-name
520                    (stgit-file-file (stgit-patched-file-at-point)))))
521     (unless (file-exists-p filename)
522       (error "File does not exist"))
523     (find-file filename)))
524
525 (defun stgit-select-patch ()
526   (let ((patchname (stgit-patch-name-at-point)))
527     (if (memq patchname stgit-expanded-patches)
528         (setq stgit-expanded-patches (delq patchname stgit-expanded-patches))
529       (setq stgit-expanded-patches (cons patchname stgit-expanded-patches)))
530     (ewoc-invalidate stgit-ewoc (ewoc-locate stgit-ewoc)))
531   (move-to-column (stgit-goal-column)))
532
533 (defun stgit-select ()
534   "Expand or collapse the current entry"
535   (interactive)
536   (case (get-text-property (point) 'entry-type)
537     ('patch
538      (stgit-select-patch))
539     ('file
540      (stgit-select-file))
541     (t
542      (error "No patch or file on line"))))
543
544 (defun stgit-find-file-other-window ()
545   "Open file at point in other window"
546   (interactive)
547   (let ((patched-file (stgit-patched-file-at-point)))
548     (unless patched-file
549       (error "No file on the current line"))
550     (let ((filename (expand-file-name (stgit-file-file patched-file))))
551       (unless (file-exists-p filename)
552         (error "File does not exist"))
553       (find-file-other-window filename))))
554
555 (defun stgit-quit ()
556   "Hide the stgit buffer."
557   (interactive)
558   (bury-buffer))
559
560 (defun stgit-git-status ()
561   "Show status using `git-status'."
562   (interactive)
563   (unless (fboundp 'git-status)
564     (error "The stgit-git-status command requires git-status"))
565   (let ((dir default-directory))
566     (save-selected-window
567       (pop-to-buffer nil)
568       (git-status dir))))
569
570 (defun stgit-goal-column ()
571   "Return goal column for the current line"
572   (case (get-text-property (point) 'entry-type)
573     ('patch 2)
574     ('file 4)
575     (t 0)))
576
577 (defun stgit-next-line (&optional arg)
578   "Move cursor vertically down ARG lines"
579   (interactive "p")
580   (next-line arg)
581   (move-to-column (stgit-goal-column)))
582
583 (defun stgit-previous-line (&optional arg)
584   "Move cursor vertically up ARG lines"
585   (interactive "p")
586   (previous-line arg)
587   (move-to-column (stgit-goal-column)))
588
589 (defun stgit-next-patch (&optional arg)
590   "Move cursor down ARG patches."
591   (interactive "p")
592   (ewoc-goto-next stgit-ewoc (or arg 1))
593   (move-to-column goal-column))
594
595 (defun stgit-previous-patch (&optional arg)
596   "Move cursor up ARG patches."
597   (interactive "p")
598   (ewoc-goto-prev stgit-ewoc (or arg 1))
599   (move-to-column goal-column))
600
601 (defvar stgit-mode-hook nil
602   "Run after `stgit-mode' is setup.")
603
604 (defvar stgit-mode-map nil
605   "Keymap for StGit major mode.")
606
607 (unless stgit-mode-map
608   (let ((toggle-map (make-keymap)))
609     (suppress-keymap toggle-map)
610     (mapc (lambda (arg) (define-key toggle-map (car arg) (cdr arg)))
611           '(("t" .        stgit-toggle-worktree)))
612     (setq stgit-mode-map (make-keymap))
613     (suppress-keymap stgit-mode-map)
614     (mapc (lambda (arg) (define-key stgit-mode-map (car arg) (cdr arg)))
615           `((" " .        stgit-mark)
616             ("m" .        stgit-mark)
617             ("\d" .       stgit-unmark-up)
618             ("u" .        stgit-unmark-down)
619             ("?" .        stgit-help)
620             ("h" .        stgit-help)
621             ("\C-p" .     stgit-previous-line)
622             ("\C-n" .     stgit-next-line)
623             ([up] .       stgit-previous-line)
624             ([down] .     stgit-next-line)
625             ("p" .        stgit-previous-patch)
626             ("n" .        stgit-next-patch)
627             ("\M-{" .     stgit-previous-patch)
628             ("\M-}" .     stgit-next-patch)
629             ("s" .        stgit-git-status)
630             ("g" .        stgit-reload-or-repair)
631             ("r" .        stgit-refresh)
632             ("\C-c\C-r" . stgit-rename)
633             ("e" .        stgit-edit)
634             ("M" .        stgit-move-patches)
635             ("S" .        stgit-squash)
636             ("N" .        stgit-new)
637             ("\C-c\C-c" . stgit-commit)
638             ("\C-c\C-u" . stgit-uncommit)
639             ("U" .        stgit-revert-file)
640             ("R" .        stgit-resolve-file)
641             ("\r" .       stgit-select)
642             ("o" .        stgit-find-file-other-window)
643             ("i" .        stgit-file-toggle-index)
644             (">" .        stgit-push-next)
645             ("<" .        stgit-pop-next)
646             ("P" .        stgit-push-or-pop)
647             ("G" .        stgit-goto)
648             ("=" .        stgit-show)
649             ("D" .        stgit-delete)
650             ([(control ?/)] . stgit-undo)
651             ("\C-_" .     stgit-undo)
652             ("B" .        stgit-branch)
653             ("t" .        ,toggle-map)
654             ("q" .        stgit-quit)))))
655
656 (defun stgit-mode ()
657   "Major mode for interacting with StGit.
658 Commands:
659 \\{stgit-mode-map}"
660   (kill-all-local-variables)
661   (buffer-disable-undo)
662   (setq mode-name "StGit"
663         major-mode 'stgit-mode
664         goal-column 2)
665   (use-local-map stgit-mode-map)
666   (set (make-local-variable 'list-buffers-directory) default-directory)
667   (set (make-local-variable 'stgit-marked-patches) nil)
668   (set (make-local-variable 'stgit-expanded-patches) nil)
669   (set (make-local-variable 'stgit-show-worktree) stgit-default-show-worktree)
670   (set (make-local-variable 'stgit-index-node) nil)
671   (set (make-local-variable 'stgit-worktree-node) nil)
672   (set-variable 'truncate-lines 't)
673   (add-hook 'after-save-hook 'stgit-update-saved-file)
674   (run-hooks 'stgit-mode-hook))
675
676 (defun stgit-update-saved-file ()
677   (let* ((file (expand-file-name buffer-file-name))
678          (dir (file-name-directory file))
679          (gitdir (condition-case nil (git-get-top-dir dir)
680                    (error nil)))
681          (buffer (and gitdir (stgit-find-buffer gitdir))))
682     (when buffer
683       (with-current-buffer buffer
684         (stgit-refresh-worktree)))))
685
686 (defun stgit-add-mark (patchsym)
687   "Mark the patch PATCHSYM."
688   (setq stgit-marked-patches (cons patchsym stgit-marked-patches)))
689
690 (defun stgit-remove-mark (patchsym)
691   "Unmark the patch PATCHSYM."
692   (setq stgit-marked-patches (delq patchsym stgit-marked-patches)))
693
694 (defun stgit-clear-marks ()
695   "Unmark all patches."
696   (setq stgit-marked-patches '()))
697
698 (defun stgit-patch-at-point (&optional cause-error)
699   (get-text-property (point) 'patch-data))
700
701 (defun stgit-patch-name-at-point (&optional cause-error only-patches)
702   "Return the patch name on the current line as a symbol.
703 If CAUSE-ERROR is not nil, signal an error if none found.
704 If ONLY-PATCHES is not nil, only allow real patches, and not
705 index or work tree."
706   (let ((patch (stgit-patch-at-point)))
707     (and patch
708          only-patches
709          (memq (stgit-patch-status patch) '(work index))
710          (setq patch nil))
711     (cond (patch
712            (stgit-patch-name patch))
713           (cause-error
714            (error "No patch on this line")))))
715
716 (defun stgit-patched-file-at-point ()
717   (get-text-property (point) 'file-data))
718
719 (defun stgit-patches-marked-or-at-point ()
720   "Return the symbols of the marked patches, or the patch on the current line."
721   (if stgit-marked-patches
722       stgit-marked-patches
723     (let ((patch (stgit-patch-name-at-point)))
724       (if patch
725           (list patch)
726         '()))))
727
728 (defun stgit-goto-patch (patchsym)
729   "Move point to the line containing patch PATCHSYM.
730 If that patch cannot be found, do nothing."
731   (let ((node (ewoc-nth stgit-ewoc 0)))
732     (while (and node (not (eq (stgit-patch-name (ewoc-data node))
733                               patchsym)))
734       (setq node (ewoc-next stgit-ewoc node)))
735     (when node
736       (ewoc-goto-node stgit-ewoc node)
737       (move-to-column goal-column))))
738
739 (defun stgit-init ()
740   "Run stg init."
741   (interactive)
742   (stgit-capture-output nil
743     (stgit-run "init"))
744   (stgit-reload))
745
746 (defun stgit-mark ()
747   "Mark the patch under point."
748   (interactive)
749   (let* ((node (ewoc-locate stgit-ewoc))
750          (patch (ewoc-data node))
751          (name (stgit-patch-name patch)))
752     (when (eq name :work)
753       (error "Cannot mark the work tree"))
754     (when (eq name :index)
755       (error "Cannot mark the index"))
756     (stgit-add-mark (stgit-patch-name patch))
757     (ewoc-invalidate stgit-ewoc node))
758   (stgit-next-patch))
759
760 (defun stgit-unmark-up ()
761   "Remove mark from the patch on the previous line."
762   (interactive)
763   (stgit-previous-patch)
764   (let* ((node (ewoc-locate stgit-ewoc))
765          (patch (ewoc-data node)))
766     (stgit-remove-mark (stgit-patch-name patch))
767     (ewoc-invalidate stgit-ewoc node))
768   (move-to-column (stgit-goal-column)))
769
770 (defun stgit-unmark-down ()
771   "Remove mark from the patch on the current line."
772   (interactive)
773   (let* ((node (ewoc-locate stgit-ewoc))
774          (patch (ewoc-data node)))
775     (stgit-remove-mark (stgit-patch-name patch))
776     (ewoc-invalidate stgit-ewoc node))
777   (stgit-next-patch))
778
779 (defun stgit-rename (name)
780   "Rename the patch under point to NAME."
781   (interactive (list
782                 (read-string "Patch name: "
783                              (symbol-name (stgit-patch-name-at-point t t)))))
784   (let ((old-patchsym (stgit-patch-name-at-point t t)))
785     (stgit-capture-output nil
786       (stgit-run "rename" old-patchsym name))
787     (let ((name-sym (intern name)))
788       (when (memq old-patchsym stgit-expanded-patches)
789         (setq stgit-expanded-patches
790             (cons name-sym (delq old-patchsym stgit-expanded-patches))))
791       (when (memq old-patchsym stgit-marked-patches)
792         (setq stgit-marked-patches
793             (cons name-sym (delq old-patchsym stgit-marked-patches))))
794       (stgit-reload)
795       (stgit-goto-patch name-sym))))
796
797 (defun stgit-reload-or-repair (repair)
798   "Update the contents of the StGit buffer (`stgit-reload').
799
800 With a prefix argument, repair the StGit metadata if the branch
801 was modified with git commands (`stgit-repair')."
802   (interactive "P")
803   (if repair
804       (stgit-repair)
805     (stgit-reload)))
806
807 (defun stgit-repair ()
808   "Run stg repair."
809   (interactive)
810   (stgit-capture-output nil
811     (stgit-run "repair"))
812   (stgit-reload))
813
814 (defun stgit-available-branches ()
815   "Returns a list of the available stg branches"
816   (let ((output (with-output-to-string
817                   (stgit-run "branch" "--list")))
818         (start 0)
819         result)
820     (while (string-match "^>?\\s-+s\\s-+\\(\\S-+\\)" output start)
821       (setq result (cons (match-string 1 output) result))
822       (setq start (match-end 0)))
823     result))
824
825 (defun stgit-branch (branch)
826   "Switch to branch BRANCH."
827   (interactive (list (completing-read "Switch to branch: "
828                                       (stgit-available-branches))))
829   (stgit-capture-output nil (stgit-run "branch" "--" branch))
830   (stgit-reload))
831
832 (defun stgit-commit (count)
833   "Run stg commit on COUNT commits.
834 Interactively, the prefix argument is used as COUNT."
835   (interactive "p")
836   (stgit-capture-output nil (stgit-run "commit" "-n" count))
837   (stgit-reload))
838
839 (defun stgit-revert-file ()
840   "Revert the file at point, which must be in the index or the
841 working tree."
842   (interactive)
843   (let* ((patched-file (or (stgit-patched-file-at-point)
844                            (error "No file on the current line")))
845          (patch-name   (stgit-patch-name-at-point))
846          (file-status  (stgit-file-status patched-file))
847          (rm-file      (cond ((stgit-file-copy-or-rename patched-file)
848                               (stgit-file-cr-to patched-file))
849                              ((eq file-status 'add)
850                               (stgit-file-file patched-file))))
851          (co-file      (cond ((eq file-status 'rename)
852                               (stgit-file-cr-from patched-file))
853                              ((not (memq file-status '(copy add)))
854                               (stgit-file-file patched-file)))))
855
856     (unless (memq patch-name '(:work :index))
857       (error "No index or working tree file on this line"))
858
859     (let ((nfiles (+ (if rm-file 1 0) (if co-file 1 0))))
860       (when (yes-or-no-p (format "Revert %d file%s? "
861                                  nfiles
862                                  (if (= nfiles 1) "" "s")))
863         (stgit-capture-output nil
864           (when rm-file
865             (stgit-run-git "rm" "-f" "-q" "--" rm-file))
866           (when co-file
867             (stgit-run-git "checkout" "HEAD" co-file)))
868         (stgit-reload)))))
869
870 (defun stgit-resolve-file ()
871   "Resolve conflict in the file at point."
872   (interactive)
873   (let* ((patched-file (stgit-patched-file-at-point))
874          (patch        (stgit-patch-at-point))
875          (patch-name   (and patch (stgit-patch-name patch)))
876          (status       (and patched-file (stgit-file-status patched-file))))
877
878     (unless (memq patch-name '(:work :index))
879       (error "No index or working tree file on this line"))
880
881     (unless (eq status 'unmerged)
882       (error "No conflict to resolve at the current line"))
883
884     (stgit-capture-output nil
885       (stgit-move-change-to-index (stgit-file-file patched-file)))
886
887     (stgit-reload)))
888
889 (defun stgit-uncommit (count)
890   "Run stg uncommit on COUNT commits.
891 Interactively, the prefix argument is used as COUNT."
892   (interactive "p")
893   (stgit-capture-output nil (stgit-run "uncommit" "-n" count))
894   (stgit-reload))
895
896 (defun stgit-push-next (npatches)
897   "Push the first unapplied patch.
898 With numeric prefix argument, push that many patches."
899   (interactive "p")
900   (stgit-capture-output nil (stgit-run "push" "-n" npatches))
901   (stgit-reload)
902   (stgit-refresh-git-status))
903
904 (defun stgit-pop-next (npatches)
905   "Pop the topmost applied patch.
906 With numeric prefix argument, pop that many patches."
907   (interactive "p")
908   (stgit-capture-output nil (stgit-run "pop" "-n" npatches))
909   (stgit-reload)
910   (stgit-refresh-git-status))
911
912 (defun stgit-applied-at-point ()
913   "Is the patch on the current line applied?"
914   (save-excursion
915     (beginning-of-line)
916     (looking-at "[>+]")))
917
918 (defun stgit-push-or-pop ()
919   "Push or pop the patch on the current line."
920   (interactive)
921   (let ((patchsym (stgit-patch-name-at-point t))
922         (applied (stgit-applied-at-point)))
923     (stgit-capture-output nil
924       (stgit-run (if applied "pop" "push") patchsym))
925     (stgit-reload)))
926
927 (defun stgit-goto ()
928   "Go to the patch on the current line."
929   (interactive)
930   (let ((patchsym (stgit-patch-name-at-point t)))
931     (stgit-capture-output nil
932       (stgit-run "goto" patchsym))
933     (stgit-reload)))
934
935 (defun stgit-id (patchsym)
936   "Return the git commit id for PATCHSYM.
937 If PATCHSYM is a keyword, returns PATCHSYM unmodified."
938   (if (keywordp patchsym)
939       patchsym
940     (let ((result (with-output-to-string
941                     (stgit-run-silent "id" patchsym))))
942       (unless (string-match "^\\([0-9A-Fa-f]\\{40\\}\\)$" result)
943         (error "Cannot find commit id for %s" patchsym))
944       (match-string 1 result))))
945
946 (defun stgit-show ()
947   "Show the patch on the current line."
948   (interactive)
949   (stgit-capture-output "*StGit patch*"
950     (case (get-text-property (point) 'entry-type)
951       ('file
952        (let* ((patched-file (stgit-patched-file-at-point))
953               (patch-name (stgit-patch-name-at-point))
954               (patch-id (stgit-id patch-name))
955               (args (append (and (stgit-file-cr-from patched-file)
956                                  (list (stgit-find-copies-harder-diff-arg)))
957                             (cond ((eq patch-id :index)
958                                    '("--cached"))
959                                   ((eq patch-id :work)
960                                    nil)
961                                   (t
962                                    (list (concat patch-id "^") patch-id)))
963                             '("--")
964                               (if (stgit-file-copy-or-rename patched-file)
965                                   (list (stgit-file-cr-from patched-file)
966                                         (stgit-file-cr-to patched-file))
967                                 (list (stgit-file-file patched-file))))))
968          (apply 'stgit-run-git "diff" args)))
969       ('patch
970        (let* ((patch-name (stgit-patch-name-at-point))
971               (patch-id (stgit-id patch-name)))
972          (if (or (eq patch-id :index) (eq patch-id :work))
973              (apply 'stgit-run-git "diff"
974                     (stgit-find-copies-harder-diff-arg)
975                     (and (eq patch-id :index)
976                          '("--cached")))
977            (stgit-run "show" "-O" "--patch-with-stat" "-O" "-M"
978                       (stgit-patch-name-at-point)))))
979       (t
980        (error "No patch or file at point")))
981     (with-current-buffer standard-output
982       (goto-char (point-min))
983       (diff-mode))))
984
985 (defun stgit-move-change-to-index (file)
986   "Copies the workspace state of FILE to index, using git add or git rm"
987   (let ((op (if (or (file-exists-p file) (file-symlink-p file))
988                 '("add") '("rm" "-q"))))
989     (stgit-capture-output "*git output*"
990       (apply 'stgit-run-git (append op '("--") (list file))))))
991
992 (defun stgit-remove-change-from-index (file)
993   "Unstages the change in FILE from the index"
994   (stgit-capture-output "*git output*"
995     (stgit-run-git "reset" "-q" "--" file)))
996
997 (defun stgit-file-toggle-index ()
998   "Move modified file in or out of the index."
999   (interactive)
1000   (let ((patched-file (stgit-patched-file-at-point)))
1001     (unless patched-file
1002       (error "No file on the current line"))
1003     (when (eq (stgit-file-status patched-file) 'unmerged)
1004       (error (substitute-command-keys "Use \\[stgit-resolve-file] to move an unmerged file to the index")))
1005     (let ((patch-name (stgit-patch-name-at-point)))
1006       (cond ((eq patch-name :work)
1007              (stgit-move-change-to-index (stgit-file-file patched-file)))
1008             ((eq patch-name :index)
1009              (stgit-remove-change-from-index (stgit-file-file patched-file)))
1010             (t
1011              (error "Can only move files in the working tree to index")))))
1012   (stgit-refresh-worktree)
1013   (stgit-refresh-index))
1014
1015 (defun stgit-edit ()
1016   "Edit the patch on the current line."
1017   (interactive)
1018   (let ((patchsym (stgit-patch-name-at-point t t))
1019         (edit-buf (get-buffer-create "*StGit edit*"))
1020         (dir default-directory))
1021     (log-edit 'stgit-confirm-edit t nil edit-buf)
1022     (set (make-local-variable 'stgit-edit-patchsym) patchsym)
1023     (setq default-directory dir)
1024     (let ((standard-output edit-buf))
1025       (stgit-run-silent "edit" "--save-template=-" patchsym))))
1026
1027 (defun stgit-confirm-edit ()
1028   (interactive)
1029   (let ((file (make-temp-file "stgit-edit-")))
1030     (write-region (point-min) (point-max) file)
1031     (stgit-capture-output nil
1032       (stgit-run "edit" "-f" file stgit-edit-patchsym))
1033     (with-current-buffer log-edit-parent-buffer
1034       (stgit-reload))))
1035
1036 (defun stgit-new (add-sign)
1037   "Create a new patch.
1038 With a prefix argument, include a \"Signed-off-by:\" line at the
1039 end of the patch."
1040   (interactive "P")
1041   (let ((edit-buf (get-buffer-create "*StGit edit*"))
1042         (dir default-directory))
1043     (log-edit 'stgit-confirm-new t nil edit-buf)
1044     (setq default-directory dir)
1045     (when add-sign
1046       (save-excursion
1047         (let ((standard-output (current-buffer)))
1048           (stgit-run-silent "new" "--sign" "--save-template=-"))))))
1049
1050 (defun stgit-confirm-new ()
1051   (interactive)
1052   (let ((file (make-temp-file "stgit-edit-")))
1053     (write-region (point-min) (point-max) file)
1054     (stgit-capture-output nil
1055       (stgit-run "new" "-f" file))
1056     (with-current-buffer log-edit-parent-buffer
1057       (stgit-reload))))
1058
1059 (defun stgit-create-patch-name (description)
1060   "Create a patch name from a long description"
1061   (let ((patch ""))
1062     (while (> (length description) 0)
1063       (cond ((string-match "\\`[a-zA-Z_-]+" description)
1064              (setq patch (downcase (concat patch
1065                                            (match-string 0 description))))
1066              (setq description (substring description (match-end 0))))
1067             ((string-match "\\` +" description)
1068              (setq patch (concat patch "-"))
1069              (setq description (substring description (match-end 0))))
1070             ((string-match "\\`[^a-zA-Z_-]+" description)
1071              (setq description (substring description (match-end 0))))))
1072     (cond ((= (length patch) 0)
1073            "patch")
1074           ((> (length patch) 20)
1075            (substring patch 0 20))
1076           (t patch))))
1077
1078 (defun stgit-delete (patchsyms &optional spill-p)
1079   "Delete the patches in PATCHSYMS.
1080 Interactively, delete the marked patches, or the patch at point.
1081
1082 With a prefix argument, or SPILL-P, spill the patch contents to
1083 the work tree and index."
1084   (interactive (list (stgit-patches-marked-or-at-point)
1085                      current-prefix-arg))
1086   (unless patchsyms
1087     (error "No patches to delete"))
1088   (when (memq :index patchsyms)
1089     (error "Cannot delete the index"))
1090   (when (memq :work  patchsyms)
1091     (error "Cannot delete the work tree"))
1092
1093   (let ((npatches (length patchsyms)))
1094     (when (yes-or-no-p (format "Really delete %d patch%s%s? "
1095                                npatches
1096                                (if (= 1 npatches) "" "es")
1097                                (if spill-p
1098                                    " (spilling contents to index)"
1099                                  "")))
1100       (let ((args (if spill-p 
1101                       (cons "--spill" patchsyms)
1102                     patchsyms)))
1103         (stgit-capture-output nil
1104           (apply 'stgit-run "delete" args))
1105         (stgit-reload)))))
1106
1107 (defun stgit-move-patches-target ()
1108   "Return the patchsym indicating a target patch for
1109 `stgit-move-patches'.
1110
1111 This is either the patch at point, or one of :top and :bottom, if
1112 the point is after or before the applied patches."
1113
1114   (let ((patchsym (stgit-patch-name-at-point)))
1115     (cond (patchsym patchsym)
1116           ((save-excursion (re-search-backward "^>" nil t)) :top)
1117           (t :bottom))))
1118
1119 (defun stgit-sort-patches (patchsyms)
1120   "Returns the list of patches in PATCHSYMS sorted according to
1121 their position in the patch series, bottommost first.
1122
1123 PATCHSYMS may not contain duplicate entries."
1124   (let (sorted-patchsyms
1125         (series (with-output-to-string
1126                   (with-current-buffer standard-output
1127                     (stgit-run-silent "series" "--noprefix"))))
1128         start)
1129     (while (string-match "^\\(.+\\)" series start)
1130       (let ((patchsym (intern (match-string 1 series))))
1131         (when (memq patchsym patchsyms)
1132           (setq sorted-patchsyms (cons patchsym sorted-patchsyms))))
1133       (setq start (match-end 0)))
1134     (setq sorted-patchsyms (nreverse sorted-patchsyms))
1135
1136     (unless (= (length patchsyms) (length sorted-patchsyms))
1137       (error "Internal error"))
1138
1139     sorted-patchsyms))
1140
1141 (defun stgit-move-patches (patchsyms target-patch)
1142   "Move the patches in PATCHSYMS to below TARGET-PATCH.
1143 If TARGET-PATCH is :bottom or :top, move the patches to the
1144 bottom or top of the stack, respectively.
1145
1146 Interactively, move the marked patches to where the point is."
1147   (interactive (list stgit-marked-patches
1148                      (stgit-move-patches-target)))
1149   (unless patchsyms
1150     (error "Need at least one patch to move"))
1151
1152   (unless target-patch
1153     (error "Point not at a patch"))
1154
1155   (if (eq target-patch :top)
1156       (stgit-capture-output nil
1157         (apply 'stgit-run "float" patchsyms))
1158
1159     ;; need to have patchsyms sorted by position in the stack
1160     (let ((sorted-patchsyms (stgit-sort-patches patchsyms)))
1161       (while sorted-patchsyms
1162         (setq sorted-patchsyms
1163               (and (stgit-capture-output nil
1164                      (if (eq target-patch :bottom)
1165                          (stgit-run "sink" "--" (car sorted-patchsyms))
1166                        (stgit-run "sink" "--to" target-patch "--"
1167                                   (car sorted-patchsyms))))
1168                    (cdr sorted-patchsyms))))))
1169   (stgit-reload))
1170
1171 (defun stgit-squash (patchsyms)
1172   "Squash the patches in PATCHSYMS.
1173 Interactively, squash the marked patches.
1174
1175 Unless there are any conflicts, the patches will be merged into
1176 one patch, which will occupy the same spot in the series as the
1177 deepest patch had before the squash."
1178   (interactive (list stgit-marked-patches))
1179   (when (< (length patchsyms) 2)
1180     (error "Need at least two patches to squash"))
1181   (let ((stgit-buffer (current-buffer))
1182         (edit-buf (get-buffer-create "*StGit edit*"))
1183         (dir default-directory)
1184         (sorted-patchsyms (stgit-sort-patches patchsyms)))
1185     (log-edit 'stgit-confirm-squash t nil edit-buf)
1186     (set (make-local-variable 'stgit-patchsyms) sorted-patchsyms)
1187     (setq default-directory dir)
1188     (let ((result (let ((standard-output edit-buf))
1189                     (apply 'stgit-run-silent "squash"
1190                            "--save-template=-" sorted-patchsyms))))
1191
1192       ;; stg squash may have reordered the patches or caused conflicts
1193       (with-current-buffer stgit-buffer
1194         (stgit-reload))
1195
1196       (unless (eq 0 result)
1197         (fundamental-mode)
1198         (rename-buffer "*StGit error*")
1199         (resize-temp-buffer-window)
1200         (switch-to-buffer-other-window stgit-buffer)
1201         (error "stg squash failed")))))
1202
1203 (defun stgit-confirm-squash ()
1204   (interactive)
1205   (let ((file (make-temp-file "stgit-edit-")))
1206     (write-region (point-min) (point-max) file)
1207     (stgit-capture-output nil
1208       (apply 'stgit-run "squash" "-f" file stgit-patchsyms))
1209     (with-current-buffer log-edit-parent-buffer
1210       (stgit-clear-marks)
1211       ;; Go to first marked patch and stay there
1212       (goto-char (point-min))
1213       (re-search-forward (concat "^[>+-]\\*") nil t)
1214       (move-to-column goal-column)
1215       (let ((pos (point)))
1216         (stgit-reload)
1217         (goto-char pos)))))
1218
1219 (defun stgit-help ()
1220   "Display help for the StGit mode."
1221   (interactive)
1222   (describe-function 'stgit-mode))
1223
1224 (defun stgit-undo (&optional arg)
1225   "Run stg undo.
1226 With prefix argument, run it with the --hard flag."
1227   (interactive "P")
1228   (stgit-capture-output nil
1229     (if arg
1230         (stgit-run "undo" "--hard")
1231       (stgit-run "undo")))
1232   (stgit-reload))
1233
1234 (defun stgit-refresh (&optional arg)
1235   "Run stg refresh.
1236 If the index contains any changes, only refresh from index.
1237
1238 With prefix argument, refresh the marked patch or the patch under point."
1239   (interactive "P")
1240   (let ((patchargs (if arg
1241                        (let ((patches (stgit-patches-marked-or-at-point)))
1242                          (cond ((null patches)
1243                                 (error "No patch to update"))
1244                                ((> (length patches) 1)
1245                                 (error "Too many patches selected"))
1246                                (t
1247                                 (cons "-p" patches))))
1248                      nil)))
1249     (unless (stgit-index-empty-p)
1250       (setq patchargs (cons "--index" patchargs)))
1251     (stgit-capture-output nil
1252       (apply 'stgit-run "refresh" patchargs))
1253     (stgit-refresh-git-status))
1254   (stgit-reload))
1255
1256 (defcustom stgit-default-show-worktree
1257   nil
1258   "Set to non-nil to by default show the working tree in a new stgit buffer.
1259
1260 This value is used as the default value for `stgit-show-worktree'."
1261   :type 'boolean
1262   :group 'stgit)
1263
1264 (defvar stgit-show-worktree nil
1265   "Show work tree and index in the stgit buffer.
1266
1267 See `stgit-default-show-worktree' for its default value.")
1268
1269 (defun stgit-toggle-worktree (&optional arg)
1270   "Toggle the visibility of the work tree.
1271 With arg, show the work tree if arg is positive.
1272
1273 Its initial setting is controlled by `stgit-default-show-worktree'."
1274   (interactive)
1275   (setq stgit-show-worktree
1276         (if (numberp arg)
1277             (> arg 0)
1278           (not stgit-show-worktree)))
1279   (stgit-reload))
1280
1281 (provide 'stgit)