stgit.el: Do not recurse into unknown directories after "t u"
[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
4f7efe0c
GH
12(when (< emacs-major-version 22)
13 (error "Emacs older than 22 is not supported by stgit.el"))
14
0f076fe6 15(require 'git nil t)
50d88c67 16(require 'cl)
98230edd 17(require 'ewoc)
5038381d 18(require 'easymenu)
0b9ea6b8 19(require 'format-spec)
0f076fe6 20
4ba91e80
DK
21(defun stgit-set-default (symbol value)
22 "Set default value of SYMBOL to VALUE using `set-default' and
23reload all StGit buffers."
24 (set-default symbol value)
25 (dolist (buf (buffer-list))
26 (with-current-buffer buf
27 (when (eq major-mode 'stgit-mode)
28 (stgit-reload)))))
29
30(defgroup stgit nil
31 "A user interface for the StGit patch maintenance tool."
32 :group 'tools
33 :link '(function-link stgit)
34 :link '(url-link "http://www.procode.org/stgit/"))
35
36(defcustom stgit-abbreviate-copies-and-renames t
37 "If non-nil, abbreviate copies and renames as \"dir/{old -> new}/file\"
38instead of \"dir/old/file -> dir/new/file\"."
39 :type 'boolean
40 :group 'stgit
41 :set 'stgit-set-default)
42
43(defcustom stgit-default-show-worktree t
44 "Set to non-nil to by default show the working tree in a new stgit buffer.
45
46Use \\<stgit-mode-map>\\[stgit-toggle-worktree] to toggle the this setting in an already-started StGit buffer."
47 :type 'boolean
48 :group 'stgit
49 :link '(variable-link stgit-show-worktree))
50
51(defcustom stgit-find-copies-harder nil
52 "Try harder to find copied files when listing patches.
53
54When not nil, runs git diff-tree with the --find-copies-harder
55flag, which reduces performance."
56 :type 'boolean
57 :group 'stgit
58 :set 'stgit-set-default)
59
60(defcustom stgit-show-worktree-mode 'center
61 "This variable controls where the \"Index\" and \"Work tree\"
62will be shown on in the buffer.
63
64It can be set to 'top (above all patches), 'center (show between
65applied and unapplied patches), and 'bottom (below all patches)."
66 :type '(radio (const :tag "above all patches (top)" top)
67 (const :tag "between applied and unapplied patches (center)"
68 center)
69 (const :tag "below all patches (bottom)" bottom))
70 :group 'stgit
71 :link '(variable-link stgit-show-worktree)
72 :set 'stgit-set-default)
73
0b9ea6b8
DK
74(defcustom stgit-patch-line-format "%s%m%-30n %e%d"
75 "The format string used to format patch lines.
76The format string is passed to `format-spec' and the following
77format characters are recognized:
78
79 %s - A '+', '-', '>' or space, depending on whether the patch is
80 applied, unapplied, top, or something else.
81
82 %m - An asterisk if the patch is marked, and a space otherwise.
83
84 %n - The patch name.
85
86 %e - The string \"(empty) \" if the patch is empty.
87
a0045b87
DK
88 %d - The short patch description.
89
90 %D - The short patch description, or the patch name.
91
92When `stgit-show-patch-names' is non-nil, the `stgit-noname-patch-line-format'
93variable is used instead."
94 :type 'string
95 :group 'stgit
96 :set 'stgit-set-default)
97
98(defcustom stgit-noname-patch-line-format "%s%m%e%D"
99 "The alternate format string used to format patch lines.
100It has the same semantics as `stgit-patch-line-format', and the
101display can be toggled between the two formats using
102\\<stgit-mode-map>>\\[stgit-toggle-patch-names].
103
104The alternate form is used when the patch name is hidden."
0b9ea6b8
DK
105 :type 'string
106 :group 'stgit
107 :set 'stgit-set-default)
108
a0045b87
DK
109(defcustom stgit-default-show-patch-names t
110 "If non-nil, default to showing patch names in a new stgit buffer.
111
112Use \\<stgit-mode-map>\\[stgit-toggle-patch-names] to toggle the
113this setting in an already-started StGit buffer."
114 :type 'boolean
115 :group 'stgit
116 :link '(variable-link stgit-show-patch-names))
117
43ee50b6
DK
118(defcustom stgit-file-line-format " %-11s %-2m %n %c"
119 "The format string used to format file lines.
120The format string is passed to `format-spec' and the following
121format characters are recognized:
122
123 %s - A string describing the status of the file.
124
125 %m - Mode change information
126
127 %n - The file name.
128
129 %c - A description of file changes."
130 :type 'string
131 :group 'stgit
132 :set 'stgit-set-default)
133
4ba91e80
DK
134(defface stgit-branch-name-face
135 '((t :inherit bold))
136 "The face used for the StGit branch name"
137 :group 'stgit)
138
139(defface stgit-top-patch-face
140 '((((background dark)) (:weight bold :foreground "yellow"))
141 (((background light)) (:weight bold :foreground "purple"))
142 (t (:weight bold)))
143 "The face used for the top patch names"
144 :group 'stgit)
145
146(defface stgit-applied-patch-face
147 '((((background dark)) (:foreground "light yellow"))
148 (((background light)) (:foreground "purple"))
149 (t ()))
150 "The face used for applied patch names"
151 :group 'stgit)
152
153(defface stgit-unapplied-patch-face
154 '((((background dark)) (:foreground "gray80"))
155 (((background light)) (:foreground "orchid"))
156 (t ()))
157 "The face used for unapplied patch names"
158 :group 'stgit)
159
160(defface stgit-description-face
161 '((((background dark)) (:foreground "tan"))
162 (((background light)) (:foreground "dark red")))
163 "The face used for StGit descriptions"
164 :group 'stgit)
165
166(defface stgit-index-work-tree-title-face
167 '((((supports :slant italic)) :slant italic)
168 (t :inherit bold))
169 "StGit mode face used for the \"Index\" and \"Work tree\" titles"
170 :group 'stgit)
171
172(defface stgit-unmerged-file-face
173 '((((class color) (background light)) (:foreground "red" :bold t))
174 (((class color) (background dark)) (:foreground "red" :bold t)))
175 "StGit mode face used for unmerged file status"
176 :group 'stgit)
177
178(defface stgit-unknown-file-face
179 '((((class color) (background light)) (:foreground "goldenrod" :bold t))
180 (((class color) (background dark)) (:foreground "goldenrod" :bold t)))
181 "StGit mode face used for unknown file status"
182 :group 'stgit)
183
184(defface stgit-ignored-file-face
185 '((((class color) (background light)) (:foreground "grey60"))
186 (((class color) (background dark)) (:foreground "grey40")))
187 "StGit mode face used for ignored files")
188
189(defface stgit-file-permission-face
190 '((((class color) (background light)) (:foreground "green" :bold t))
191 (((class color) (background dark)) (:foreground "green" :bold t)))
192 "StGit mode face used for permission changes."
193 :group 'stgit)
194
195(defface stgit-modified-file-face
196 '((((class color) (background light)) (:foreground "purple"))
197 (((class color) (background dark)) (:foreground "salmon")))
198 "StGit mode face used for modified file status"
199 :group 'stgit)
200
56d81fe5 201(defun stgit (dir)
fdf5e327
GH
202 "Manage StGit patches for the tree in DIR.
203
204See `stgit-mode' for commands available."
56d81fe5 205 (interactive "DDirectory: \n")
52144ce5 206 (switch-to-stgit-buffer (git-get-top-dir dir))
1f0bf00f 207 (stgit-reload))
56d81fe5 208
9d04c657
GH
209(defun stgit-assert-mode ()
210 "Signal an error if not in an StGit buffer."
211 (assert (derived-mode-p 'stgit-mode) nil "Not an StGit buffer"))
212
074a4fb0
GH
213(unless (fboundp 'git-get-top-dir)
214 (defun git-get-top-dir (dir)
215 "Retrieve the top-level directory of a git tree."
216 (let ((cdup (with-output-to-string
217 (with-current-buffer standard-output
218 (cd dir)
219 (unless (eq 0 (call-process "git" nil t nil
220 "rev-parse" "--show-cdup"))
df283a8b 221 (error "Cannot find top-level git tree for %s" dir))))))
074a4fb0
GH
222 (expand-file-name (concat (file-name-as-directory dir)
223 (car (split-string cdup "\n")))))))
224
225(defun stgit-refresh-git-status (&optional dir)
226 "If it exists, refresh the `git-status' buffer belonging to
227directory DIR or `default-directory'"
228 (when (and (fboundp 'git-find-status-buffer)
229 (fboundp 'git-refresh-status))
230 (let* ((top-dir (git-get-top-dir (or dir default-directory)))
231 (git-status-buffer (and top-dir (git-find-status-buffer top-dir))))
232 (when git-status-buffer
233 (with-current-buffer git-status-buffer
234 (git-refresh-status))))))
52144ce5 235
b894e680
DK
236(defun stgit-find-buffer (dir)
237 "Return the buffer displaying StGit patches for DIR, or nil if none."
56d81fe5
DK
238 (setq dir (file-name-as-directory dir))
239 (let ((buffers (buffer-list)))
240 (while (and buffers
241 (not (with-current-buffer (car buffers)
242 (and (eq major-mode 'stgit-mode)
243 (string= default-directory dir)))))
244 (setq buffers (cdr buffers)))
b894e680
DK
245 (and buffers (car buffers))))
246
247(defun switch-to-stgit-buffer (dir)
248 "Switch to a (possibly new) buffer displaying StGit patches for DIR."
249 (setq dir (file-name-as-directory dir))
250 (let ((buffer (stgit-find-buffer dir)))
251 (switch-to-buffer (or buffer
252 (create-stgit-buffer dir)))))
253
413f9909
GH
254(defstruct (stgit-patch
255 (:conc-name stgit-patch->))
3164eec6 256 status name desc empty files-ewoc)
56d81fe5 257
0b9ea6b8 258(defun stgit-patch-display-name (patch)
413f9909 259 (let ((name (stgit-patch->name patch)))
0b9ea6b8
DK
260 (case name
261 (:index "Index")
262 (:work "Work Tree")
263 (t (symbol-name name)))))
264
98230edd 265(defun stgit-patch-pp (patch)
413f9909 266 (let* ((status (stgit-patch->status patch))
9153ce3a 267 (start (point))
413f9909 268 (name (stgit-patch->name patch))
0b9ea6b8 269 (face (cdr (assq status stgit-patch-status-face-alist)))
a0045b87
DK
270 (fmt (if stgit-show-patch-names
271 stgit-patch-line-format
272 stgit-noname-patch-line-format))
0b9ea6b8
DK
273 (spec (format-spec-make
274 ?s (case status
275 ('applied "+")
276 ('top ">")
277 ('unapplied "-")
278 (t " "))
279 ?m (if (memq name stgit-marked-patches)
280 "*" " ")
281 ?n (propertize (stgit-patch-display-name patch)
282 'face face
283 'syntax-table (string-to-syntax "w"))
413f9909
GH
284 ?e (if (stgit-patch->empty patch) "(empty) " "")
285 ?d (propertize (or (stgit-patch->desc patch) "")
a0045b87 286 'face 'stgit-description-face)
413f9909 287 ?D (propertize (or (stgit-patch->desc patch)
a0045b87
DK
288 (stgit-patch-display-name patch))
289 'face face))))
0b9ea6b8 290
a0045b87 291 (insert (format-spec fmt spec) "\n")
f9b82d36 292 (put-text-property start (point) 'entry-type 'patch)
98230edd 293 (when (memq name stgit-expanded-patches)
0de6881a 294 (stgit-insert-patch-files patch))
98230edd
DK
295 (put-text-property start (point) 'patch-data patch)))
296
56d81fe5
DK
297(defun create-stgit-buffer (dir)
298 "Create a buffer for showing StGit patches.
299Argument DIR is the repository path."
300 (let ((buf (create-file-buffer (concat dir "*stgit*")))
301 (inhibit-read-only t))
302 (with-current-buffer buf
303 (setq default-directory dir)
304 (stgit-mode)
98230edd 305 (set (make-local-variable 'stgit-ewoc)
4f7ff561 306 (ewoc-create #'stgit-patch-pp "Branch:\n\n" "--\n" t))
56d81fe5
DK
307 (setq buffer-read-only t))
308 buf))
309
310(defmacro stgit-capture-output (name &rest body)
e558a4ab
GH
311 "Capture StGit output and, if there was any output, show it in a window
312at the end.
313Returns nil if there was no output."
94baef5a
DK
314 (declare (debug ([&or stringp null] body))
315 (indent 1))
34afb86c
DK
316 `(let ((output-buf (get-buffer-create ,(or name "*StGit output*")))
317 (stgit-dir default-directory)
318 (inhibit-read-only t))
56d81fe5 319 (with-current-buffer output-buf
277b52af 320 (buffer-disable-undo)
34afb86c
DK
321 (erase-buffer)
322 (setq default-directory stgit-dir)
323 (setq buffer-read-only t))
56d81fe5
DK
324 (let ((standard-output output-buf))
325 ,@body)
34afb86c
DK
326 (with-current-buffer output-buf
327 (set-buffer-modified-p nil)
328 (setq buffer-read-only t)
329 (if (< (point-min) (point-max))
330 (display-buffer output-buf t)))))
56d81fe5 331
d51722b7
GH
332(defun stgit-make-run-args (args)
333 "Return a copy of ARGS with its elements converted to strings."
334 (mapcar (lambda (x)
335 ;; don't use (format "%s" ...) to limit type errors
336 (cond ((stringp x) x)
337 ((integerp x) (number-to-string x))
338 ((symbolp x) (symbol-name x))
339 (t
340 (error "Bad element in stgit-make-run-args args: %S" x))))
341 args))
342
9aecd505 343(defun stgit-run-silent (&rest args)
d51722b7 344 (setq args (stgit-make-run-args args))
56d81fe5
DK
345 (apply 'call-process "stg" nil standard-output nil args))
346
9aecd505 347(defun stgit-run (&rest args)
d51722b7 348 (setq args (stgit-make-run-args args))
9aecd505
DK
349 (let ((msgcmd (mapconcat #'identity args " ")))
350 (message "Running stg %s..." msgcmd)
351 (apply 'call-process "stg" nil standard-output nil args)
352 (message "Running stg %s...done" msgcmd)))
353
378a003d 354(defun stgit-run-git (&rest args)
d51722b7 355 (setq args (stgit-make-run-args args))
378a003d
GH
356 (let ((msgcmd (mapconcat #'identity args " ")))
357 (message "Running git %s..." msgcmd)
358 (apply 'call-process "git" nil standard-output nil args)
359 (message "Running git %s...done" msgcmd)))
360
1f60181a 361(defun stgit-run-git-silent (&rest args)
d51722b7 362 (setq args (stgit-make-run-args args))
1f60181a
GH
363 (apply 'call-process "git" nil standard-output nil args))
364
b894e680
DK
365(defun stgit-index-empty-p ()
366 "Returns non-nil if the index contains no changes from HEAD."
367 (zerop (stgit-run-git-silent "diff-index" "--cached" "--quiet" "HEAD")))
368
1629f59f
GH
369(defun stgit-work-tree-empty-p ()
370 "Returns non-nil if the work tree contains no changes from index."
371 (zerop (stgit-run-git-silent "diff-files" "--quiet")))
372
2ecb05c8
GH
373(defvar stgit-index-node)
374(defvar stgit-worktree-node)
210a2a52
DK
375
376(defun stgit-refresh-index ()
377 (when stgit-index-node
378 (ewoc-invalidate (car stgit-index-node) (cdr stgit-index-node))))
379
380(defun stgit-refresh-worktree ()
381 (when stgit-worktree-node
382 (ewoc-invalidate (car stgit-worktree-node) (cdr stgit-worktree-node))))
383
8f702de4
GH
384(defun stgit-run-series-insert-index (ewoc)
385 (setq index-node (cons ewoc (ewoc-enter-last ewoc
386 (make-stgit-patch
387 :status 'index
388 :name :index
389 :desc nil
390 :empty nil)))
391 worktree-node (cons ewoc (ewoc-enter-last ewoc
392 (make-stgit-patch
393 :status 'work
394 :name :work
395 :desc nil
396 :empty nil)))))
397
98230edd 398(defun stgit-run-series (ewoc)
8f702de4
GH
399 (setq stgit-index-node nil
400 stgit-worktree-node nil)
401 (let ((inserted-index (not stgit-show-worktree))
402 index-node
03fc3b26
GH
403 worktree-node
404 all-patchsyms)
98230edd 405 (with-temp-buffer
ea305902
GH
406 (let* ((standard-output (current-buffer))
407 (exit-status (stgit-run-silent "series"
408 "--description" "--empty")))
98230edd
DK
409 (goto-char (point-min))
410 (if (not (zerop exit-status))
411 (cond ((looking-at "stg series: \\(.*\\)")
8f702de4 412 (setq inserted-index t)
98230edd 413 (ewoc-set-hf ewoc (car (ewoc-get-hf ewoc))
8f702de4
GH
414 (substitute-command-keys
415 "-- not initialized; run \\[stgit-init]")))
98230edd
DK
416 ((looking-at ".*")
417 (error "Error running stg: %s"
418 (match-string 0))))
419 (while (not (eobp))
420 (unless (looking-at
421 "\\([0 ]\\)\\([>+-]\\)\\( \\)\\([^ ]+\\) *[|#] \\(.*\\)")
422 (error "Syntax error in output from stg series"))
423 (let* ((state-str (match-string 2))
424 (state (cond ((string= state-str ">") 'top)
425 ((string= state-str "+") 'applied)
8f702de4
GH
426 ((string= state-str "-") 'unapplied)))
427 (name (intern (match-string 4)))
428 (desc (match-string 5))
429 (empty (string= (match-string 1) "0")))
430 (unless inserted-index
431 (when (or (eq stgit-show-worktree-mode 'top)
432 (and (eq stgit-show-worktree-mode 'center)
433 (eq state 'unapplied)))
434 (setq inserted-index t)
435 (stgit-run-series-insert-index ewoc)))
03fc3b26 436 (setq all-patchsyms (cons name all-patchsyms))
98230edd
DK
437 (ewoc-enter-last ewoc
438 (make-stgit-patch
439 :status state
8f702de4
GH
440 :name name
441 :desc desc
442 :empty empty)))
443 (forward-line 1))))
444 (unless inserted-index
445 (stgit-run-series-insert-index ewoc)))
446 (setq stgit-index-node index-node
03fc3b26
GH
447 stgit-worktree-node worktree-node
448 stgit-marked-patches (intersection stgit-marked-patches
449 all-patchsyms))))
98230edd 450
1f0bf00f 451(defun stgit-reload ()
a53347d9 452 "Update the contents of the StGit buffer."
56d81fe5 453 (interactive)
9d04c657 454 (stgit-assert-mode)
56d81fe5
DK
455 (let ((inhibit-read-only t)
456 (curline (line-number-at-pos))
a9089e68
GH
457 (curpatch (stgit-patch-name-at-point))
458 (curfile (stgit-patched-file-at-point)))
98230edd
DK
459 (ewoc-filter stgit-ewoc #'(lambda (x) nil))
460 (ewoc-set-hf stgit-ewoc
461 (concat "Branch: "
462 (propertize
ea305902
GH
463 (substring (with-output-to-string
464 (stgit-run-silent "branch"))
465 0 -1)
4f292066 466 'face 'stgit-branch-name-face)
4f7ff561 467 "\n\n")
ce3b6130
DK
468 (if stgit-show-worktree
469 "--"
470 (propertize
471 (substitute-command-keys "--\n\"\\[stgit-toggle-worktree]\"\
472 shows the working tree\n")
6a73154a 473 'face 'stgit-description-face)))
98230edd 474 (stgit-run-series stgit-ewoc)
56d81fe5 475 (if curpatch
413f9909 476 (stgit-goto-patch curpatch (and curfile (stgit-file->file curfile)))
074a4fb0
GH
477 (goto-line curline)))
478 (stgit-refresh-git-status))
56d81fe5 479
1f60181a
GH
480(defconst stgit-file-status-code-strings
481 (mapcar (lambda (arg)
482 (cons (car arg)
a6d9a852
GH
483 (propertize (cadr arg) 'face (car (cddr arg)))))
484 '((add "Added" stgit-modified-file-face)
485 (copy "Copied" stgit-modified-file-face)
486 (delete "Deleted" stgit-modified-file-face)
487 (modify "Modified" stgit-modified-file-face)
488 (rename "Renamed" stgit-modified-file-face)
489 (mode-change "Mode change" stgit-modified-file-face)
490 (unmerged "Unmerged" stgit-unmerged-file-face)
d9473917
GH
491 (unknown "Unknown" stgit-unknown-file-face)
492 (ignore "Ignored" stgit-ignored-file-face)))
1f60181a
GH
493 "Alist of code symbols to description strings")
494
000f337c
GH
495(defconst stgit-patch-status-face-alist
496 '((applied . stgit-applied-patch-face)
497 (top . stgit-top-patch-face)
498 (unapplied . stgit-unapplied-patch-face)
9153ce3a
GH
499 (index . stgit-index-work-tree-title-face)
500 (work . stgit-index-work-tree-title-face))
000f337c
GH
501 "Alist of face to use for a given patch status")
502
3164eec6
DK
503(defun stgit-file-status-code-as-string (file)
504 "Return stgit status code for FILE as a string"
413f9909 505 (let* ((code (assq (stgit-file->status file)
3164eec6 506 stgit-file-status-code-strings))
413f9909 507 (score (stgit-file->cr-score file)))
3164eec6 508 (when code
43ee50b6
DK
509 (if (and score (/= score 100))
510 (format "%s %s" (cdr code)
511 (propertize (format "%d%%" score)
512 'face 'stgit-description-face))
513 (cdr code)))))
1f60181a 514
a6d9a852 515(defun stgit-file-status-code (str &optional score)
1f60181a
GH
516 "Return stgit status code from git status string"
517 (let ((code (assoc str '(("A" . add)
518 ("C" . copy)
519 ("D" . delete)
d9473917 520 ("I" . ignore)
1f60181a
GH
521 ("M" . modify)
522 ("R" . rename)
523 ("T" . mode-change)
524 ("U" . unmerged)
525 ("X" . unknown)))))
a6d9a852
GH
526 (setq code (if code (cdr code) 'unknown))
527 (when (stringp score)
528 (if (> (length score) 0)
529 (setq score (string-to-number score))
530 (setq score nil)))
531 (if score (cons code score) code)))
532
533(defconst stgit-file-type-strings
534 '((#o100 . "file")
535 (#o120 . "symlink")
536 (#o160 . "subproject"))
537 "Alist of names of file types")
538
539(defun stgit-file-type-string (type)
47271f41
GH
540 "Return string describing file type TYPE (the high bits of file permission).
541Cf. `stgit-file-type-strings' and `stgit-file-type-change-string'."
a6d9a852
GH
542 (let ((type-str (assoc type stgit-file-type-strings)))
543 (or (and type-str (cdr type-str))
544 (format "unknown type %o" type))))
545
546(defun stgit-file-type-change-string (old-perm new-perm)
47271f41
GH
547 "Return string describing file type change from OLD-PERM to NEW-PERM.
548Cf. `stgit-file-type-string'."
a6d9a852
GH
549 (let ((old-type (lsh old-perm -9))
550 (new-type (lsh new-perm -9)))
551 (cond ((= old-type new-type) "")
552 ((zerop new-type) "")
553 ((zerop old-type)
554 (if (= new-type #o100)
555 ""
43ee50b6
DK
556 (format "(%s)" (stgit-file-type-string new-type))))
557 (t (format "(%s -> %s)"
a6d9a852
GH
558 (stgit-file-type-string old-type)
559 (stgit-file-type-string new-type))))))
560
561(defun stgit-file-mode-change-string (old-perm new-perm)
47271f41
GH
562 "Return string describing file mode change from OLD-PERM to NEW-PERM.
563Cf. `stgit-file-type-change-string'."
a6d9a852
GH
564 (setq old-perm (logand old-perm #o777)
565 new-perm (logand new-perm #o777))
566 (if (or (= old-perm new-perm)
567 (zerop old-perm)
568 (zerop new-perm))
569 ""
570 (let* ((modified (logxor old-perm new-perm))
571 (not-x-modified (logand (logxor old-perm new-perm) #o666)))
572 (cond ((zerop modified) "")
573 ((and (zerop not-x-modified)
574 (or (and (eq #o111 (logand old-perm #o111))
575 (propertize "-x" 'face 'stgit-file-permission-face))
576 (and (eq #o111 (logand new-perm #o111))
577 (propertize "+x" 'face
578 'stgit-file-permission-face)))))
579 (t (concat (propertize (format "%o" old-perm)
580 'face 'stgit-file-permission-face)
581 (propertize " -> "
582 'face 'stgit-description-face)
583 (propertize (format "%o" new-perm)
584 'face 'stgit-file-permission-face)))))))
1f60181a 585
413f9909
GH
586(defstruct (stgit-file
587 (:conc-name stgit-file->))
0de6881a
DK
588 old-perm new-perm copy-or-rename cr-score cr-from cr-to status file)
589
ca027a87 590(defun stgit-describe-copy-or-rename (file)
6a73154a
GH
591 (let ((arrow (concat " " (propertize "->" 'face 'stgit-description-face) " "))
592 from to common-head common-tail)
ca027a87
GH
593
594 (when stgit-abbreviate-copies-and-renames
413f9909
GH
595 (setq from (split-string (stgit-file->cr-from file) "/")
596 to (split-string (stgit-file->cr-to file) "/"))
ca027a87
GH
597
598 (while (and from to (cdr from) (cdr to)
599 (string-equal (car from) (car to)))
600 (setq common-head (cons (car from) common-head)
601 from (cdr from)
602 to (cdr to)))
603 (setq common-head (nreverse common-head)
604 from (nreverse from)
605 to (nreverse to))
606 (while (and from to (cdr from) (cdr to)
607 (string-equal (car from) (car to)))
608 (setq common-tail (cons (car from) common-tail)
609 from (cdr from)
610 to (cdr to)))
611 (setq from (nreverse from)
612 to (nreverse to)))
613
614 (if (or common-head common-tail)
615 (concat (if common-head
616 (mapconcat #'identity common-head "/")
617 "")
618 (if common-head "/" "")
619 (propertize "{" 'face 'stgit-description-face)
620 (mapconcat #'identity from "/")
621 arrow
622 (mapconcat #'identity to "/")
623 (propertize "}" 'face 'stgit-description-face)
624 (if common-tail "/" "")
625 (if common-tail
626 (mapconcat #'identity common-tail "/")
627 ""))
413f9909 628 (concat (stgit-file->cr-from file) arrow (stgit-file->cr-to file)))))
ca027a87 629
3164eec6 630(defun stgit-file-pp (file)
43ee50b6
DK
631 (let ((start (point))
632 (spec (format-spec-make
633 ?s (stgit-file-status-code-as-string file)
634 ?m (stgit-file-mode-change-string
413f9909
GH
635 (stgit-file->old-perm file)
636 (stgit-file->new-perm file))
637 ?n (if (stgit-file->copy-or-rename file)
43ee50b6 638 (stgit-describe-copy-or-rename file)
413f9909 639 (stgit-file->file file))
43ee50b6 640 ?c (propertize (stgit-file-type-change-string
413f9909
GH
641 (stgit-file->old-perm file)
642 (stgit-file->new-perm file))
43ee50b6
DK
643 'face 'stgit-description-face))))
644 (insert (format-spec stgit-file-line-format spec) "\n")
0de6881a 645 (add-text-properties start (point)
3164eec6
DK
646 (list 'entry-type 'file
647 'file-data file))))
0de6881a 648
7567401c
GH
649(defun stgit-find-copies-harder-diff-arg ()
650 "Return the flag to use with `git-diff' depending on the
b6df231c
GH
651`stgit-find-copies-harder' flag."
652 (if stgit-find-copies-harder "--find-copies-harder" "-C"))
7567401c 653
d9473917
GH
654(defun stgit-insert-ls-files (args file-flag)
655 (let ((start (point)))
656 (apply 'stgit-run-git
657 (append '("ls-files" "--exclude-standard" "-z") args))
658 (goto-char start)
659 (while (looking-at "\\([^\0]*\\)\0")
660 (let ((name-len (- (match-end 0) (match-beginning 0))))
661 (insert ":0 0 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 " file-flag "\0")
662 (forward-char name-len)))))
663
7f972e9b
GH
664(defun stgit-process-files (callback)
665 (goto-char (point-min))
666 (when (looking-at "[0-9A-Fa-f]\\{40\\}\0")
667 (goto-char (match-end 0)))
668 (while (looking-at ":\\([0-7]+\\) \\([0-7]+\\) [0-9A-Fa-f]\\{40\\} [0-9A-Fa-f]\\{40\\} ")
669 (let ((old-perm (string-to-number (match-string 1) 8))
670 (new-perm (string-to-number (match-string 2) 8)))
671 (goto-char (match-end 0))
672 (let ((file
673 (cond ((looking-at
674 "\\([CR]\\)\\([0-9]*\\)\0\\([^\0]*\\)\0\\([^\0]*\\)\0")
675 (let* ((patch-status (stgit-patch->status patch))
676 (file-subexp (if (eq patch-status 'unapplied)
677 3
678 4))
679 (file (match-string file-subexp)))
680 (make-stgit-file
681 :old-perm old-perm
682 :new-perm new-perm
683 :copy-or-rename t
684 :cr-score (string-to-number (match-string 2))
685 :cr-from (match-string 3)
686 :cr-to (match-string 4)
687 :status (stgit-file-status-code
688 (match-string 1))
689 :file file)))
690 ((looking-at "\\([ABD-QS-Z]\\)\0\\([^\0]*\\)\0")
691 (make-stgit-file
692 :old-perm old-perm
693 :new-perm new-perm
694 :copy-or-rename nil
695 :cr-score nil
696 :cr-from nil
697 :cr-to nil
698 :status (stgit-file-status-code
699 (match-string 1))
700 :file (match-string 2))))))
701 (goto-char (match-end 0))
702 (funcall callback file)))))
703
704
0de6881a 705(defun stgit-insert-patch-files (patch)
88134ff7
GH
706 "Expand (show modification of) the patch PATCH after the line
707at point."
413f9909 708 (let* ((patchsym (stgit-patch->name patch))
0434bec1
GH
709 (end (point-marker))
710 (args (list "-z" (stgit-find-copies-harder-diff-arg)))
711 (ewoc (ewoc-create #'stgit-file-pp nil nil t)))
712 (set-marker-insertion-type end t)
413f9909 713 (setf (stgit-patch->files-ewoc patch) ewoc)
0de6881a 714 (with-temp-buffer
ea305902
GH
715 (let ((standard-output (current-buffer)))
716 (apply 'stgit-run-git
717 (cond ((eq patchsym :work)
718 `("diff-files" "-0" ,@args))
719 ((eq patchsym :index)
720 `("diff-index" ,@args "--cached" "HEAD"))
721 (t
722 `("diff-tree" ,@args "-r" ,(stgit-id patchsym)))))
723
724 (when (and (eq patchsym :work))
725 (when stgit-show-ignored
726 (stgit-insert-ls-files '("--ignored" "--others") "I"))
727 (when stgit-show-unknown
7f972e9b
GH
728 (stgit-insert-ls-files '("--directory" "--no-empty-directory"
729 "--others")
730 "X"))
ea305902
GH
731 (sort-regexp-fields nil ":[^\0]*\0\\([^\0]*\\)\0" "\\1"
732 (point-min) (point-max)))
733
7f972e9b 734 (stgit-process-files (lambda (file) (ewoc-enter-last ewoc file)))
ea305902
GH
735
736 (unless (ewoc-nth ewoc 0)
737 (ewoc-set-hf ewoc ""
738 (concat " "
739 (propertize "<no files>"
740 'face 'stgit-description-face)
741 "\n")))))
0434bec1 742 (goto-char end)))
07f464e0 743
030f0535
GH
744(defun stgit-find-file (&optional other-window)
745 (let* ((file (or (stgit-patched-file-at-point)
746 (error "No file at point")))
413f9909 747 (filename (expand-file-name (stgit-file->file file))))
0de6881a
DK
748 (unless (file-exists-p filename)
749 (error "File does not exist"))
030f0535
GH
750 (funcall (if other-window 'find-file-other-window 'find-file)
751 filename)
413f9909 752 (when (eq (stgit-file->status file) 'unmerged)
030f0535 753 (smerge-mode 1))))
acc5652f 754
afbf766b 755(defun stgit-expand (&optional patches collapse)
fd64ee57 756 "Show the contents of marked patches, or the patch at point.
afbf766b
GH
757
758See also `stgit-collapse'.
759
760Non-interactively, operate on PATCHES, and collapse instead of
761expand if COLLAPSE is not nil."
beac0f14 762 (interactive (list (stgit-patches-marked-or-at-point t)))
9d04c657 763 (stgit-assert-mode)
afbf766b
GH
764 (let ((patches-diff (funcall (if collapse #'intersection #'set-difference)
765 patches stgit-expanded-patches)))
766 (setq stgit-expanded-patches
767 (if collapse
768 (set-difference stgit-expanded-patches patches-diff)
769 (append stgit-expanded-patches patches-diff)))
770 (ewoc-map #'(lambda (patch)
413f9909 771 (memq (stgit-patch->name patch) patches-diff))
afbf766b
GH
772 stgit-ewoc))
773 (move-to-column (stgit-goal-column)))
774
775(defun stgit-collapse (&optional patches)
fd64ee57 776 "Hide the contents of marked patches, or the patch at point.
afbf766b
GH
777
778See also `stgit-expand'."
beac0f14 779 (interactive (list (stgit-patches-marked-or-at-point t)))
9d04c657 780 (stgit-assert-mode)
afbf766b
GH
781 (stgit-expand patches t))
782
50d88c67 783(defun stgit-select-patch ()
98230edd 784 (let ((patchname (stgit-patch-name-at-point)))
afbf766b
GH
785 (stgit-expand (list patchname)
786 (memq patchname stgit-expanded-patches))))
acc5652f 787
7f972e9b
GH
788(defun stgit-expand-directory (file)
789 (let* ((patch (stgit-patch-at-point))
790 (ewoc (stgit-patch->files-ewoc patch))
791 (node (ewoc-locate ewoc))
792 (filename (stgit-file->file file))
793 (start (make-marker))
794 (end (make-marker)))
795
796 (save-excursion
797 (forward-line 1)
798 (set-marker start (point))
799 (set-marker end (point))
800 (set-marker-insertion-type end t))
801
802 (assert (string-match "/$" filename))
803 ;; remove trailing "/"
804 (setf (stgit-file->file file) (substring filename 0 -1))
805 (ewoc-invalidate ewoc node)
806
807 (with-temp-buffer
808 (let ((standard-output (current-buffer)))
809 (stgit-insert-ls-files (list "--directory" "--others"
810 "--no-empty-directory" "--"
811 filename)
812 "X")
813 (stgit-process-files (lambda (f)
814 (setq node (ewoc-enter-after ewoc node f))))))
815
816 (let ((inhibit-read-only t))
817 (put-text-property start end 'patch-data patch))))
818
819(defun stgit-select-file ()
820 (let* ((file (or (stgit-patched-file-at-point)
821 (error "No file at point")))
822 (filename (stgit-file->file file)))
823 (if (string-match "/$" filename)
824 (stgit-expand-directory file)
825 (stgit-find-file))))
826
378a003d 827(defun stgit-select ()
da01a29b
GH
828 "With point on a patch, toggle showing files in the patch.
829
830With point on a file, open the associated file. Opens the target
831file for (applied) copies and renames."
378a003d 832 (interactive)
9d04c657 833 (stgit-assert-mode)
50d88c67
DK
834 (case (get-text-property (point) 'entry-type)
835 ('patch
836 (stgit-select-patch))
837 ('file
7f972e9b 838 (stgit-select-file))
50d88c67
DK
839 (t
840 (error "No patch or file on line"))))
378a003d
GH
841
842(defun stgit-find-file-other-window ()
843 "Open file at point in other window"
844 (interactive)
9d04c657 845 (stgit-assert-mode)
030f0535 846 (stgit-find-file t))
378a003d 847
d9b954c7
GH
848(defun stgit-find-file-merge ()
849 "Open file at point and merge it using `smerge-ediff'."
850 (interactive)
9d04c657 851 (stgit-assert-mode)
d9b954c7
GH
852 (stgit-find-file t)
853 (smerge-ediff))
854
83327d53 855(defun stgit-quit ()
a53347d9 856 "Hide the stgit buffer."
83327d53 857 (interactive)
9d04c657 858 (stgit-assert-mode)
83327d53
GH
859 (bury-buffer))
860
0f076fe6 861(defun stgit-git-status ()
a53347d9 862 "Show status using `git-status'."
0f076fe6 863 (interactive)
9d04c657 864 (stgit-assert-mode)
0f076fe6 865 (unless (fboundp 'git-status)
df283a8b 866 (error "The stgit-git-status command requires git-status"))
0f076fe6
GH
867 (let ((dir default-directory))
868 (save-selected-window
869 (pop-to-buffer nil)
870 (git-status dir))))
871
58f72f16
GH
872(defun stgit-goal-column ()
873 "Return goal column for the current line"
50d88c67
DK
874 (case (get-text-property (point) 'entry-type)
875 ('patch 2)
876 ('file 4)
877 (t 0)))
58f72f16
GH
878
879(defun stgit-next-line (&optional arg)
378a003d 880 "Move cursor vertically down ARG lines"
58f72f16 881 (interactive "p")
9d04c657 882 (stgit-assert-mode)
58f72f16
GH
883 (next-line arg)
884 (move-to-column (stgit-goal-column)))
378a003d 885
58f72f16 886(defun stgit-previous-line (&optional arg)
378a003d 887 "Move cursor vertically up ARG lines"
58f72f16 888 (interactive "p")
9d04c657 889 (stgit-assert-mode)
58f72f16
GH
890 (previous-line arg)
891 (move-to-column (stgit-goal-column)))
378a003d
GH
892
893(defun stgit-next-patch (&optional arg)
98230edd 894 "Move cursor down ARG patches."
378a003d 895 (interactive "p")
9d04c657 896 (stgit-assert-mode)
98230edd
DK
897 (ewoc-goto-next stgit-ewoc (or arg 1))
898 (move-to-column goal-column))
378a003d
GH
899
900(defun stgit-previous-patch (&optional arg)
98230edd 901 "Move cursor up ARG patches."
378a003d 902 (interactive "p")
9d04c657 903 (stgit-assert-mode)
98230edd
DK
904 (ewoc-goto-prev stgit-ewoc (or arg 1))
905 (move-to-column goal-column))
378a003d 906
56d81fe5
DK
907(defvar stgit-mode-hook nil
908 "Run after `stgit-mode' is setup.")
909
910(defvar stgit-mode-map nil
911 "Keymap for StGit major mode.")
912
913(unless stgit-mode-map
5038381d
GH
914 (let ((diff-map (make-sparse-keymap))
915 (toggle-map (make-sparse-keymap)))
d9b954c7
GH
916 (suppress-keymap diff-map)
917 (mapc (lambda (arg) (define-key diff-map (car arg) (cdr arg)))
918 '(("b" . stgit-diff-base)
919 ("c" . stgit-diff-combined)
920 ("m" . stgit-find-file-merge)
921 ("o" . stgit-diff-ours)
922 ("t" . stgit-diff-theirs)))
ce3b6130
DK
923 (suppress-keymap toggle-map)
924 (mapc (lambda (arg) (define-key toggle-map (car arg) (cdr arg)))
a0045b87
DK
925 '(("n" . stgit-toggle-patch-names)
926 ("t" . stgit-toggle-worktree)
d9473917
GH
927 ("i" . stgit-toggle-ignored)
928 ("u" . stgit-toggle-unknown)))
ce3b6130
DK
929 (setq stgit-mode-map (make-keymap))
930 (suppress-keymap stgit-mode-map)
931 (mapc (lambda (arg) (define-key stgit-mode-map (car arg) (cdr arg)))
d11e0621
GH
932 `((" " . stgit-mark-down)
933 ("m" . stgit-mark-down)
ce3b6130
DK
934 ("\d" . stgit-unmark-up)
935 ("u" . stgit-unmark-down)
936 ("?" . stgit-help)
937 ("h" . stgit-help)
938 ("\C-p" . stgit-previous-line)
939 ("\C-n" . stgit-next-line)
940 ([up] . stgit-previous-line)
941 ([down] . stgit-next-line)
942 ("p" . stgit-previous-patch)
943 ("n" . stgit-next-patch)
944 ("\M-{" . stgit-previous-patch)
945 ("\M-}" . stgit-next-patch)
946 ("s" . stgit-git-status)
408fa7cb 947 ("g" . stgit-reload-or-repair)
ce3b6130
DK
948 ("r" . stgit-refresh)
949 ("\C-c\C-r" . stgit-rename)
950 ("e" . stgit-edit)
951 ("M" . stgit-move-patches)
952 ("S" . stgit-squash)
953 ("N" . stgit-new)
2acb7116 954 ("c" . stgit-new-and-refresh)
e9fdd4ea
GH
955 ("\C-c\C-c" . stgit-commit)
956 ("\C-c\C-u" . stgit-uncommit)
1629f59f 957 ("U" . stgit-revert)
51783171 958 ("R" . stgit-resolve-file)
ce3b6130 959 ("\r" . stgit-select)
afbf766b
GH
960 ("+" . stgit-expand)
961 ("-" . stgit-collapse)
ce3b6130 962 ("o" . stgit-find-file-other-window)
dde3ab4d 963 ("i" . stgit-toggle-index)
ce3b6130
DK
964 (">" . stgit-push-next)
965 ("<" . stgit-pop-next)
966 ("P" . stgit-push-or-pop)
967 ("G" . stgit-goto)
d9b954c7 968 ("=" . stgit-diff)
ce3b6130 969 ("D" . stgit-delete)
b8463f1d 970 ([?\C-/] . stgit-undo)
ce3b6130 971 ("\C-_" . stgit-undo)
b8463f1d
GH
972 ([?\C-c ?\C-/] . stgit-redo)
973 ("\C-c\C-_" . stgit-redo)
ce3b6130 974 ("B" . stgit-branch)
380a021f 975 ("\C-c\C-b" . stgit-rebase)
ce3b6130 976 ("t" . ,toggle-map)
d9b954c7 977 ("d" . ,diff-map)
5038381d
GH
978 ("q" . stgit-quit))))
979
980 (let ((at-unmerged-file '(let ((file (stgit-patched-file-at-point)))
413f9909 981 (and file (eq (stgit-file->status file)
5038381d
GH
982 'unmerged))))
983 (patch-collapsed-p '(lambda (p) (not (memq p stgit-expanded-patches)))))
984 (easy-menu-define stgit-menu stgit-mode-map
985 "StGit Menu"
986 `("StGit"
987 ["Reload" stgit-reload-or-repair
988 :help "Reload StGit status from disk"]
989 ["Repair" stgit-repair
990 :keys "\\[universal-argument] \\[stgit-reload-or-repair]"
991 :help "Repair StGit metadata"]
992 "-"
993 ["Undo" stgit-undo t]
994 ["Redo" stgit-redo t]
995 "-"
996 ["Git status" stgit-git-status :active (fboundp 'git-status)]
997 "-"
998 ["New patch" stgit-new-and-refresh
999 :help "Create a new patch from changes in index or work tree"
1000 :active (not (and (stgit-index-empty-p) (stgit-work-tree-empty-p)))]
1001 ["New empty patch" stgit-new
1002 :help "Create a new, empty patch"]
1003 ["(Un)mark patch" stgit-toggle-mark
1004 :label (if (memq (stgit-patch-name-at-point nil t)
1005 stgit-marked-patches)
1006 "Unmark patch" "Mark patch")
1007 :active (stgit-patch-name-at-point nil t)]
1008 ["Expand/collapse patch"
1009 (let ((patches (stgit-patches-marked-or-at-point)))
1010 (if (member-if ,patch-collapsed-p patches)
1011 (stgit-expand patches)
1012 (stgit-collapse patches)))
1013 :label (if (member-if ,patch-collapsed-p
1014 (stgit-patches-marked-or-at-point))
1015 "Expand patches"
1016 "Collapse patches")
1017 :active (stgit-patches-marked-or-at-point)]
1018 ["Edit patch" stgit-edit
1019 :help "Edit patch comment"
1020 :active (stgit-patch-name-at-point nil t)]
1021 ["Rename patch" stgit-rename :active (stgit-patch-name-at-point nil t)]
1022 ["Push/pop patch" stgit-push-or-pop
7c11b754
GH
1023 :label (if (subsetp (stgit-patches-marked-or-at-point nil t)
1024 (stgit-applied-patchsyms t))
1025 "Pop patches" "Push patches")]
beac0f14
GH
1026 ["Delete patches" stgit-delete
1027 :active (stgit-patches-marked-or-at-point nil t)]
5038381d
GH
1028 "-"
1029 ["Move patches" stgit-move-patches
1030 :active stgit-marked-patches
fd64ee57 1031 :help "Move marked patch(es) to point"]
5038381d
GH
1032 ["Squash patches" stgit-squash
1033 :active (> (length stgit-marked-patches) 1)
fd64ee57 1034 :help "Merge marked patches into one"]
5038381d
GH
1035 "-"
1036 ["Refresh top patch" stgit-refresh
1037 :active (not (and (stgit-index-empty-p) (stgit-work-tree-empty-p)))
1038 :help "Refresh the top patch with changes in index or work tree"]
1039 ["Refresh this patch" (stgit-refresh t)
1040 :keys "\\[universal-argument] \\[stgit-refresh]"
fd64ee57 1041 :help "Refresh marked patch with changes in index or work tree"
5038381d
GH
1042 :active (and (not (and (stgit-index-empty-p)
1043 (stgit-work-tree-empty-p)))
1044 (stgit-patch-name-at-point nil t))]
1045 "-"
1046 ["Find file" stgit-select
1047 :active (eq (get-text-property (point) 'entry-type) 'file)]
1048 ["Open file" stgit-find-file-other-window
1049 :active (eq (get-text-property (point) 'entry-type) 'file)]
1050 ["Toggle file index" stgit-toggle-index
1051 :active (and (eq (get-text-property (point) 'entry-type) 'file)
1052 (memq (stgit-patch-name-at-point) '(:work :index)))
1053 :label (if (eq (stgit-patch-name-at-point) :work)
1054 "Move change to index"
1055 "Move change to work tree")]
1056 "-"
1057 ["Show diff" stgit-diff
1058 :active (get-text-property (point) 'entry-type)]
1059 ("Merge"
1060 :active (stgit-git-index-unmerged-p)
1061 ["Combined diff" stgit-diff-combined
1062 :active (memq (stgit-patch-name-at-point nil nil) '(:work :index))]
1063 ["Diff against base" stgit-diff-base
1064 :help "Show diff against the common base"
1065 :active (memq (stgit-patch-name-at-point nil nil) '(:work :index))]
1066 ["Diff against ours" stgit-diff-ours
1067 :help "Show diff against our branch"
1068 :active (memq (stgit-patch-name-at-point nil nil) '(:work :index))]
1069 ["Diff against theirs" stgit-diff-theirs
1070 :help "Show diff against their branch"
1071 :active (memq (stgit-patch-name-at-point nil nil) '(:work :index))]
1072 "-"
1073 ["Interactive merge" stgit-find-file-merge
1074 :help "Interactively merge the file"
1075 :active ,at-unmerged-file]
1076 ["Resolve file" stgit-resolve-file
1077 :help "Mark file conflict as resolved"
1078 :active ,at-unmerged-file]
1079 )
1080 "-"
1081 ["Show index & work tree" stgit-toggle-worktree :style toggle
1082 :selected stgit-show-worktree]
1083 ["Show unknown files" stgit-toggle-unknown :style toggle
1084 :selected stgit-show-unknown :active stgit-show-worktree]
1085 ["Show ignored files" stgit-toggle-ignored :style toggle
1086 :selected stgit-show-ignored :active stgit-show-worktree]
a0045b87
DK
1087 ["Show patch names" stgit-toggle-patch-names :style toggle
1088 :selected stgit-show-patch-names]
5038381d
GH
1089 "-"
1090 ["Switch branches" stgit-branch t
1091 :help "Switch to another branch"]
1092 ["Rebase branch" stgit-rebase t
1093 :help "Rebase the current branch"]
1094 ))))
1095
1096;; disable tool bar editing buttons
1097(put 'stgit-mode 'mode-class 'special)
56d81fe5
DK
1098
1099(defun stgit-mode ()
1100 "Major mode for interacting with StGit.
fdf5e327
GH
1101
1102Start StGit using \\[stgit].
1103
1104Basic commands:
1105\\<stgit-mode-map>\
1106\\[stgit-help] Show this help text
1107\\[stgit-quit] Hide the StGit buffer
1108\\[describe-bindings] Show all key bindings
1109
1110\\[stgit-reload-or-repair] Reload the StGit buffer
1111\\[universal-argument] \\[stgit-reload-or-repair] Repair StGit metadata
1112
1113\\[stgit-undo] Undo most recent StGit operation
1114\\[stgit-redo] Undo recent undo
1115
1116\\[stgit-git-status] Run `git-status' (if available)
1117
1118Movement commands:
1119\\[stgit-previous-line] Move to previous line
1120\\[stgit-next-line] Move to next line
1121\\[stgit-previous-patch] Move to previous patch
1122\\[stgit-next-patch] Move to next patch
1123
d11e0621 1124\\[stgit-mark-down] Mark patch and move down
fdf5e327
GH
1125\\[stgit-unmark-up] Unmark patch and move up
1126\\[stgit-unmark-down] Unmark patch and move down
1127
1128Commands for patches:
1129\\[stgit-select] Toggle showing changed files in patch
1130\\[stgit-refresh] Refresh patch with changes in index or work tree
1131\\[stgit-diff] Show the patch log and diff
1132
fd64ee57
GH
1133\\[stgit-expand] Show changes in marked patches
1134\\[stgit-collapse] Hide changes in marked patches
afbf766b 1135
2acb7116 1136\\[stgit-new-and-refresh] Create a new patch from index or work tree
c20b20a5
GH
1137\\[stgit-new] Create a new, empty patch
1138
fdf5e327
GH
1139\\[stgit-rename] Rename patch
1140\\[stgit-edit] Edit patch description
1141\\[stgit-delete] Delete patch(es)
1142
1629f59f 1143\\[stgit-revert] Revert all changes in index or work tree
dde3ab4d 1144\\[stgit-toggle-index] Toggle all changes between index and work tree
1629f59f 1145
fdf5e327
GH
1146\\[stgit-push-next] Push next patch onto stack
1147\\[stgit-pop-next] Pop current patch from stack
c20b20a5
GH
1148\\[stgit-push-or-pop] Push or pop marked patches
1149\\[stgit-goto] Make patch at point current by popping or pushing
fdf5e327
GH
1150
1151\\[stgit-squash] Squash (meld together) patches
c20b20a5 1152\\[stgit-move-patches] Move marked patches to point
fdf5e327
GH
1153
1154\\[stgit-commit] Commit patch(es)
1155\\[stgit-uncommit] Uncommit patch(es)
1156
1157Commands for files:
1158\\[stgit-select] Open the file in this window
1159\\[stgit-find-file-other-window] Open the file in another window
1160\\[stgit-diff] Show the file's diff
1161
dde3ab4d 1162\\[stgit-toggle-index] Toggle change between index and work tree
1629f59f 1163\\[stgit-revert] Revert changes to file
fdf5e327
GH
1164
1165Display commands:
a0045b87 1166\\[stgit-toggle-patch-names] Toggle showing patch names
fdf5e327
GH
1167\\[stgit-toggle-worktree] Toggle showing index and work tree
1168\\[stgit-toggle-unknown] Toggle showing unknown files
1169\\[stgit-toggle-ignored] Toggle showing ignored files
1170
1171Commands for diffs:
1172\\[stgit-diff] Show diff of patch or file
1173\\[stgit-diff-base] Show diff against the merge base
1174\\[stgit-diff-ours] Show diff against our branch
1175\\[stgit-diff-theirs] Show diff against their branch
1176
1177 With one prefix argument (e.g., \\[universal-argument] \\[stgit-diff]), \
1178ignore space changes.
1179 With two prefix arguments (e.g., \\[universal-argument] \
1180\\[universal-argument] \\[stgit-diff]), ignore all space changes.
1181
1182Commands for merge conflicts:
1183\\[stgit-find-file-merge] Resolve conflicts using `smerge-ediff'
1184\\[stgit-resolve-file] Mark unmerged file as resolved
1185
1186Commands for branches:
1187\\[stgit-branch] Switch to another branch
380a021f 1188\\[stgit-rebase] Rebase the current branch
fdf5e327
GH
1189
1190Customization variables:
1191`stgit-abbreviate-copies-and-renames'
a0045b87 1192`stgit-default-show-patch-names'
fdf5e327
GH
1193`stgit-default-show-worktree'
1194`stgit-find-copies-harder'
1195`stgit-show-worktree-mode'
1196
1197See also \\[customize-group] for the \"stgit\" group."
56d81fe5
DK
1198 (kill-all-local-variables)
1199 (buffer-disable-undo)
1200 (setq mode-name "StGit"
1201 major-mode 'stgit-mode
1202 goal-column 2)
1203 (use-local-map stgit-mode-map)
1204 (set (make-local-variable 'list-buffers-directory) default-directory)
6df83d42 1205 (set (make-local-variable 'stgit-marked-patches) nil)
6467d976 1206 (set (make-local-variable 'stgit-expanded-patches) (list :work :index))
a0045b87
DK
1207 (set (make-local-variable 'stgit-show-patch-names)
1208 stgit-default-show-patch-names)
ce3b6130 1209 (set (make-local-variable 'stgit-show-worktree) stgit-default-show-worktree)
2ecb05c8
GH
1210 (set (make-local-variable 'stgit-index-node) nil)
1211 (set (make-local-variable 'stgit-worktree-node) nil)
224ef1ec 1212 (set (make-local-variable 'parse-sexp-lookup-properties) t)
2870f8b8 1213 (set-variable 'truncate-lines 't)
b894e680 1214 (add-hook 'after-save-hook 'stgit-update-saved-file)
56d81fe5
DK
1215 (run-hooks 'stgit-mode-hook))
1216
b894e680
DK
1217(defun stgit-update-saved-file ()
1218 (let* ((file (expand-file-name buffer-file-name))
1219 (dir (file-name-directory file))
1220 (gitdir (condition-case nil (git-get-top-dir dir)
1221 (error nil)))
1222 (buffer (and gitdir (stgit-find-buffer gitdir))))
1223 (when buffer
1224 (with-current-buffer buffer
210a2a52 1225 (stgit-refresh-worktree)))))
b894e680 1226
d51722b7
GH
1227(defun stgit-add-mark (patchsym)
1228 "Mark the patch PATCHSYM."
8036afdd 1229 (setq stgit-marked-patches (cons patchsym stgit-marked-patches)))
6df83d42 1230
d51722b7
GH
1231(defun stgit-remove-mark (patchsym)
1232 "Unmark the patch PATCHSYM."
8036afdd 1233 (setq stgit-marked-patches (delq patchsym stgit-marked-patches)))
6df83d42 1234
e6b1fdae 1235(defun stgit-clear-marks ()
47271f41 1236 "Unmark all patches."
e6b1fdae
DK
1237 (setq stgit-marked-patches '()))
1238
735cb7ec 1239(defun stgit-patch-at-point (&optional cause-error)
2c862b07
DK
1240 (get-text-property (point) 'patch-data))
1241
64ada6f5 1242(defun stgit-patch-name-at-point (&optional cause-error only-patches)
d51722b7 1243 "Return the patch name on the current line as a symbol.
64ada6f5
GH
1244If CAUSE-ERROR is not nil, signal an error if none found.
1245If ONLY-PATCHES is not nil, only allow real patches, and not
1246index or work tree."
2c862b07 1247 (let ((patch (stgit-patch-at-point)))
64ada6f5
GH
1248 (and patch
1249 only-patches
413f9909 1250 (memq (stgit-patch->status patch) '(work index))
64ada6f5 1251 (setq patch nil))
2c862b07 1252 (cond (patch
413f9909 1253 (stgit-patch->name patch))
2c862b07
DK
1254 (cause-error
1255 (error "No patch on this line")))))
378a003d 1256
3164eec6
DK
1257(defun stgit-patched-file-at-point ()
1258 (get-text-property (point) 'file-data))
56d81fe5 1259
beac0f14
GH
1260(defun stgit-patches-marked-or-at-point (&optional cause-error only-patches)
1261 "Return the symbols of the marked patches, or the patch on the current line.
1262If CAUSE-ERRROR is not nil, signal an error if none found.
1263If ONLY-PATCHES is not nil, do not include index or work tree."
7755d7f1 1264 (if stgit-marked-patches
d51722b7 1265 stgit-marked-patches
beac0f14
GH
1266 (let ((patch (stgit-patch-name-at-point nil only-patches)))
1267 (cond (patch (list patch))
1268 (cause-error (error "No patches marked or at this line"))
1269 (t nil)))))
7755d7f1 1270
a9089e68 1271(defun stgit-goto-patch (patchsym &optional file)
d51722b7 1272 "Move point to the line containing patch PATCHSYM.
a9089e68
GH
1273If that patch cannot be found, do nothing.
1274
1275If the patch was found and FILE is not nil, instead move to that
1276file's line. If FILE cannot be found, stay on the line of
1277PATCHSYM."
f9b82d36 1278 (let ((node (ewoc-nth stgit-ewoc 0)))
413f9909 1279 (while (and node (not (eq (stgit-patch->name (ewoc-data node))
f9b82d36
DK
1280 patchsym)))
1281 (setq node (ewoc-next stgit-ewoc node)))
a9089e68 1282 (when (and node file)
413f9909 1283 (let* ((file-ewoc (stgit-patch->files-ewoc (ewoc-data node)))
a9089e68 1284 (file-node (ewoc-nth file-ewoc 0)))
413f9909 1285 (while (and file-node (not (equal (stgit-file->file (ewoc-data file-node)) file)))
a9089e68
GH
1286 (setq file-node (ewoc-next file-ewoc file-node)))
1287 (when file-node
1288 (ewoc-goto-node file-ewoc file-node)
1289 (move-to-column (stgit-goal-column))
1290 (setq node nil))))
f9b82d36
DK
1291 (when node
1292 (ewoc-goto-node stgit-ewoc node)
d51722b7 1293 (move-to-column goal-column))))
56d81fe5 1294
1c2426dc 1295(defun stgit-init ()
a53347d9 1296 "Run stg init."
1c2426dc 1297 (interactive)
9d04c657 1298 (stgit-assert-mode)
1c2426dc 1299 (stgit-capture-output nil
b0424080 1300 (stgit-run "init"))
1f0bf00f 1301 (stgit-reload))
1c2426dc 1302
d11e0621
GH
1303(defun stgit-toggle-mark ()
1304 "Toggle mark on the patch under point."
1305 (interactive)
1306 (stgit-assert-mode)
1307 (if (memq (stgit-patch-name-at-point t t) stgit-marked-patches)
1308 (stgit-unmark)
1309 (stgit-mark)))
1310
6df83d42 1311(defun stgit-mark ()
a53347d9 1312 "Mark the patch under point."
6df83d42 1313 (interactive)
9d04c657 1314 (stgit-assert-mode)
8036afdd 1315 (let* ((node (ewoc-locate stgit-ewoc))
64ada6f5 1316 (patch (ewoc-data node))
413f9909 1317 (name (stgit-patch->name patch)))
64ada6f5
GH
1318 (when (eq name :work)
1319 (error "Cannot mark the work tree"))
1320 (when (eq name :index)
1321 (error "Cannot mark the index"))
413f9909 1322 (stgit-add-mark (stgit-patch->name patch))
d11e0621
GH
1323 (let ((column (current-column)))
1324 (ewoc-invalidate stgit-ewoc node)
1325 (move-to-column column))))
1326
1327(defun stgit-mark-down ()
1328 "Mark the patch under point and move to the next patch."
1329 (interactive)
1330 (stgit-mark)
378a003d 1331 (stgit-next-patch))
6df83d42 1332
d11e0621
GH
1333(defun stgit-unmark ()
1334 "Remove mark from the patch on the current line."
6df83d42 1335 (interactive)
9d04c657 1336 (stgit-assert-mode)
8036afdd
DK
1337 (let* ((node (ewoc-locate stgit-ewoc))
1338 (patch (ewoc-data node)))
413f9909 1339 (stgit-remove-mark (stgit-patch->name patch))
d11e0621
GH
1340 (let ((column (current-column)))
1341 (ewoc-invalidate stgit-ewoc node)
1342 (move-to-column column))))
1343
1344(defun stgit-unmark-up ()
1345 "Remove mark from the patch on the previous line."
1346 (interactive)
1347 (stgit-assert-mode)
1348 (stgit-previous-patch)
1349 (stgit-unmark))
9b151b27
GH
1350
1351(defun stgit-unmark-down ()
a53347d9 1352 "Remove mark from the patch on the current line."
9b151b27 1353 (interactive)
9d04c657 1354 (stgit-assert-mode)
d11e0621 1355 (stgit-unmark)
1288eda2 1356 (stgit-next-patch))
6df83d42 1357
56d81fe5 1358(defun stgit-rename (name)
018fa1ac 1359 "Rename the patch under point to NAME."
64ada6f5
GH
1360 (interactive (list
1361 (read-string "Patch name: "
1362 (symbol-name (stgit-patch-name-at-point t t)))))
9d04c657 1363 (stgit-assert-mode)
64ada6f5 1364 (let ((old-patchsym (stgit-patch-name-at-point t t)))
56d81fe5 1365 (stgit-capture-output nil
d51722b7
GH
1366 (stgit-run "rename" old-patchsym name))
1367 (let ((name-sym (intern name)))
1368 (when (memq old-patchsym stgit-expanded-patches)
378a003d 1369 (setq stgit-expanded-patches
6a73154a 1370 (cons name-sym (delq old-patchsym stgit-expanded-patches))))
d51722b7 1371 (when (memq old-patchsym stgit-marked-patches)
378a003d 1372 (setq stgit-marked-patches
6a73154a 1373 (cons name-sym (delq old-patchsym stgit-marked-patches))))
d51722b7
GH
1374 (stgit-reload)
1375 (stgit-goto-patch name-sym))))
56d81fe5 1376
408fa7cb
GH
1377(defun stgit-reload-or-repair (repair)
1378 "Update the contents of the StGit buffer (`stgit-reload').
1379
1380With a prefix argument, repair the StGit metadata if the branch
1381was modified with git commands (`stgit-repair')."
1382 (interactive "P")
9d04c657 1383 (stgit-assert-mode)
408fa7cb
GH
1384 (if repair
1385 (stgit-repair)
1386 (stgit-reload)))
1387
26201d96 1388(defun stgit-repair ()
a53347d9 1389 "Run stg repair."
26201d96 1390 (interactive)
9d04c657 1391 (stgit-assert-mode)
26201d96 1392 (stgit-capture-output nil
b0424080 1393 (stgit-run "repair"))
1f0bf00f 1394 (stgit-reload))
26201d96 1395
adeef6bc
GH
1396(defun stgit-available-branches ()
1397 "Returns a list of the available stg branches"
1398 (let ((output (with-output-to-string
1399 (stgit-run "branch" "--list")))
1400 (start 0)
1401 result)
1402 (while (string-match "^>?\\s-+s\\s-+\\(\\S-+\\)" output start)
1403 (setq result (cons (match-string 1 output) result))
1404 (setq start (match-end 0)))
1405 result))
1406
1407(defun stgit-branch (branch)
1408 "Switch to branch BRANCH."
1409 (interactive (list (completing-read "Switch to branch: "
1410 (stgit-available-branches))))
9d04c657 1411 (stgit-assert-mode)
adeef6bc
GH
1412 (stgit-capture-output nil (stgit-run "branch" "--" branch))
1413 (stgit-reload))
1414
380a021f
GH
1415(defun stgit-available-refs (&optional omit-stgit)
1416 "Returns a list of the available git refs.
1417If OMIT-STGIT is not nil, filter out \"resf/heads/*.stgit\"."
1418 (let* ((output (with-output-to-string
1419 (stgit-run-git-silent "for-each-ref" "--format=%(refname)"
1420 "refs/tags" "refs/heads"
1421 "refs/remotes")))
1422 (result (split-string output "\n" t)))
1423 (mapcar (lambda (s)
1424 (if (string-match "^refs/\\(heads\\|tags\\|remotes\\)/" s)
1425 (substring s (match-end 0))
1426 s))
1427 (if omit-stgit
1428 (delete-if (lambda (s)
1429 (string-match "^refs/heads/.*\\.stgit$" s))
1430 result)
1431 result))))
1432
1433(defun stgit-rebase (new-base)
1434 "Rebase to NEW-BASE."
1435 (interactive (list (completing-read "Rebase to: "
1436 (stgit-available-refs t))))
9d04c657 1437 (stgit-assert-mode)
380a021f
GH
1438 (stgit-capture-output nil (stgit-run "rebase" new-base))
1439 (stgit-reload))
1440
41c1c59c
GH
1441(defun stgit-commit (count)
1442 "Run stg commit on COUNT commits.
e552cb5f
GH
1443Interactively, the prefix argument is used as COUNT.
1444A negative COUNT will uncommit instead."
41c1c59c 1445 (interactive "p")
9d04c657 1446 (stgit-assert-mode)
e552cb5f
GH
1447 (if (< count 0)
1448 (stgit-uncommit (- count))
1449 (stgit-capture-output nil (stgit-run "commit" "-n" count))
1450 (stgit-reload)))
1451
1452(defun stgit-uncommit (count)
1453 "Run stg uncommit on COUNT commits.
1454Interactively, the prefix argument is used as COUNT.
1455A negative COUNT will commit instead."
1456 (interactive "p")
9d04c657 1457 (stgit-assert-mode)
e552cb5f
GH
1458 (if (< count 0)
1459 (stgit-commit (- count))
1460 (stgit-capture-output nil (stgit-run "uncommit" "-n" count))
1461 (stgit-reload)))
c4aad9a7 1462
556345d3
GH
1463(defun stgit-neighbour-file ()
1464 "Return the file name of the next file after point, or the
1465previous file if point is at the last file within a patch."
1466 (let ((old-point (point))
1467 neighbour-file)
1468 (and (zerop (forward-line 1))
1469 (let ((f (stgit-patched-file-at-point)))
413f9909 1470 (and f (setq neighbour-file (stgit-file->file f)))))
556345d3
GH
1471 (goto-char old-point)
1472 (unless neighbour-file
1473 (and (zerop (forward-line -1))
1474 (let ((f (stgit-patched-file-at-point)))
413f9909 1475 (and f (setq neighbour-file (stgit-file->file f)))))
556345d3
GH
1476 (goto-char old-point))
1477 neighbour-file))
1478
3959a095
GH
1479(defun stgit-revert-file ()
1480 "Revert the file at point, which must be in the index or the
1481working tree."
1482 (interactive)
9d04c657 1483 (stgit-assert-mode)
3959a095
GH
1484 (let* ((patched-file (or (stgit-patched-file-at-point)
1485 (error "No file on the current line")))
1486 (patch-name (stgit-patch-name-at-point))
413f9909
GH
1487 (file-status (stgit-file->status patched-file))
1488 (rm-file (cond ((stgit-file->copy-or-rename patched-file)
1489 (stgit-file->cr-to patched-file))
3959a095 1490 ((eq file-status 'add)
413f9909 1491 (stgit-file->file patched-file))))
3959a095 1492 (co-file (cond ((eq file-status 'rename)
413f9909 1493 (stgit-file->cr-from patched-file))
3959a095 1494 ((not (memq file-status '(copy add)))
413f9909 1495 (stgit-file->file patched-file))))
556345d3 1496 (next-file (stgit-neighbour-file)))
3959a095
GH
1497
1498 (unless (memq patch-name '(:work :index))
1499 (error "No index or working tree file on this line"))
1500
d9473917
GH
1501 (when (eq file-status 'ignore)
1502 (error "Cannot revert ignored files"))
1503
1504 (when (eq file-status 'unknown)
1505 (error "Cannot revert unknown files"))
1506
3959a095
GH
1507 (let ((nfiles (+ (if rm-file 1 0) (if co-file 1 0))))
1508 (when (yes-or-no-p (format "Revert %d file%s? "
1509 nfiles
1510 (if (= nfiles 1) "" "s")))
1511 (stgit-capture-output nil
1512 (when rm-file
1513 (stgit-run-git "rm" "-f" "-q" "--" rm-file))
1514 (when co-file
1515 (stgit-run-git "checkout" "HEAD" co-file)))
556345d3
GH
1516 (stgit-reload)
1517 (stgit-goto-patch patch-name next-file)))))
1629f59f
GH
1518
1519(defun stgit-revert ()
1520 "Revert the change at point, which must be the index, the work
1521tree, or a single change in either."
1522 (interactive)
9d04c657 1523 (stgit-assert-mode)
1629f59f
GH
1524 (let ((patched-file (stgit-patched-file-at-point)))
1525 (if patched-file
1526 (stgit-revert-file)
1527 (let* ((patch-name (or (stgit-patch-name-at-point)
1528 (error "No patch or file at point")))
1529 (patch-desc (case patch-name
1530 (:index "index")
1531 (:work "work tree")
1532 (t (error (substitute-command-keys
1533 "Use \\[stgit-delete] to delete a patch"))))))
1534 (when (if (eq patch-name :work)
1535 (stgit-work-tree-empty-p)
1536 (stgit-index-empty-p))
1537 (error (format "There are no changes in the %s to revert"
1538 patch-desc)))
1539 (and (eq patch-name :index)
1540 (not (stgit-work-tree-empty-p))
1541 (error "Cannot revert index as work tree contains unstaged changes"))
1542
1543 (when (yes-or-no-p (format "Revert all changes in the %s? "
1544 patch-desc))
1545 (if (eq patch-name :index)
1546 (stgit-run-git-silent "reset" "--hard" "-q")
1547 (stgit-run-git-silent "checkout" "--" "."))
1548 (stgit-refresh-index)
1549 (stgit-refresh-worktree)
1550 (stgit-goto-patch patch-name))))))
3959a095 1551
51783171
GH
1552(defun stgit-resolve-file ()
1553 "Resolve conflict in the file at point."
1554 (interactive)
9d04c657 1555 (stgit-assert-mode)
51783171
GH
1556 (let* ((patched-file (stgit-patched-file-at-point))
1557 (patch (stgit-patch-at-point))
413f9909
GH
1558 (patch-name (and patch (stgit-patch->name patch)))
1559 (status (and patched-file (stgit-file->status patched-file))))
51783171
GH
1560
1561 (unless (memq patch-name '(:work :index))
1562 (error "No index or working tree file on this line"))
1563
1564 (unless (eq status 'unmerged)
1565 (error "No conflict to resolve at the current line"))
1566
1567 (stgit-capture-output nil
413f9909 1568 (stgit-move-change-to-index (stgit-file->file patched-file)))
51783171
GH
1569
1570 (stgit-reload)))
1571
0b661144
DK
1572(defun stgit-push-next (npatches)
1573 "Push the first unapplied patch.
1574With numeric prefix argument, push that many patches."
1575 (interactive "p")
9d04c657 1576 (stgit-assert-mode)
d51722b7 1577 (stgit-capture-output nil (stgit-run "push" "-n" npatches))
074a4fb0
GH
1578 (stgit-reload)
1579 (stgit-refresh-git-status))
56d81fe5 1580
0b661144
DK
1581(defun stgit-pop-next (npatches)
1582 "Pop the topmost applied patch.
1583With numeric prefix argument, pop that many patches."
1584 (interactive "p")
9d04c657 1585 (stgit-assert-mode)
d51722b7 1586 (stgit-capture-output nil (stgit-run "pop" "-n" npatches))
074a4fb0
GH
1587 (stgit-reload)
1588 (stgit-refresh-git-status))
56d81fe5 1589
7c11b754
GH
1590(defun stgit-applied-patches (&optional only-patches)
1591 "Return a list of the applied patches.
1592
1593If ONLY-PATCHES is not nil, exclude index and work tree."
1594 (let ((states (if only-patches
1595 '(applied top)
1596 '(applied top index work)))
1597 result)
413f9909 1598 (ewoc-map (lambda (patch) (when (memq (stgit-patch->status patch) states)
7c11b754
GH
1599 (setq result (cons patch result))))
1600 stgit-ewoc)
1601 result))
1602
1603(defun stgit-applied-patchsyms (&optional only-patches)
1604 "Return a list of the symbols of the applied patches.
1605
1606If ONLY-PATCHES is not nil, exclude index and work tree."
413f9909 1607 (mapcar #'stgit-patch->name (stgit-applied-patches only-patches)))
f9182fca
KH
1608
1609(defun stgit-push-or-pop ()
7c11b754 1610 "Push or pop the marked patches."
f9182fca 1611 (interactive)
9d04c657 1612 (stgit-assert-mode)
7c11b754
GH
1613 (let* ((patchsyms (stgit-patches-marked-or-at-point t t))
1614 (applied-syms (stgit-applied-patchsyms t))
1615 (unapplied (set-difference patchsyms applied-syms)))
f9182fca 1616 (stgit-capture-output nil
7c11b754
GH
1617 (apply 'stgit-run
1618 (if unapplied "push" "pop")
1619 "--"
1620 (stgit-sort-patches (if unapplied unapplied patchsyms)))))
1621 (stgit-reload))
f9182fca 1622
c7adf5ef 1623(defun stgit-goto ()
48d0a850
GH
1624 "Go to the patch on the current line.
1625
1626Pops or pushes patches to make this patch topmost."
c7adf5ef 1627 (interactive)
9d04c657 1628 (stgit-assert-mode)
2c862b07 1629 (let ((patchsym (stgit-patch-name-at-point t)))
c7adf5ef 1630 (stgit-capture-output nil
d51722b7 1631 (stgit-run "goto" patchsym))
1f0bf00f 1632 (stgit-reload)))
c7adf5ef 1633
d51722b7 1634(defun stgit-id (patchsym)
50d88c67
DK
1635 "Return the git commit id for PATCHSYM.
1636If PATCHSYM is a keyword, returns PATCHSYM unmodified."
1637 (if (keywordp patchsym)
1638 patchsym
1639 (let ((result (with-output-to-string
1640 (stgit-run-silent "id" patchsym))))
1641 (unless (string-match "^\\([0-9A-Fa-f]\\{40\\}\\)$" result)
1642 (error "Cannot find commit id for %s" patchsym))
1643 (match-string 1 result))))
378a003d 1644
1aece5c0 1645(defun stgit-show-patch (unmerged-stage ignore-whitespace)
d9b954c7
GH
1646 "Show the patch on the current line.
1647
1648UNMERGED-STAGE is the argument to `git-diff' that that selects
1649which stage to diff against in the case of unmerged files."
1aece5c0
GH
1650 (let ((space-arg (when (numberp ignore-whitespace)
1651 (cond ((> ignore-whitespace 4)
1652 "--ignore-all-space")
1653 ((> ignore-whitespace 1)
1654 "--ignore-space-change"))))
1655 (patch-name (stgit-patch-name-at-point t)))
1656 (stgit-capture-output "*StGit patch*"
1657 (case (get-text-property (point) 'entry-type)
1658 ('file
1659 (let* ((patched-file (stgit-patched-file-at-point))
1660 (patch-id (let ((id (stgit-id patch-name)))
1661 (if (and (eq id :index)
413f9909 1662 (eq (stgit-file->status patched-file)
1aece5c0
GH
1663 'unmerged))
1664 :work
1665 id)))
1666 (args (append (and space-arg (list space-arg))
413f9909 1667 (and (stgit-file->cr-from patched-file)
1aece5c0
GH
1668 (list (stgit-find-copies-harder-diff-arg)))
1669 (cond ((eq patch-id :index)
1670 '("--cached"))
1671 ((eq patch-id :work)
1672 (list unmerged-stage))
1673 (t
1674 (list (concat patch-id "^") patch-id)))
1675 '("--")
413f9909
GH
1676 (if (stgit-file->copy-or-rename patched-file)
1677 (list (stgit-file->cr-from patched-file)
1678 (stgit-file->cr-to patched-file))
1679 (list (stgit-file->file patched-file))))))
1aece5c0
GH
1680 (apply 'stgit-run-git "diff" args)))
1681 ('patch
1682 (let* ((patch-id (stgit-id patch-name)))
1683 (if (or (eq patch-id :index) (eq patch-id :work))
1684 (apply 'stgit-run-git "diff"
1685 (stgit-find-copies-harder-diff-arg)
1686 (append (and space-arg (list space-arg))
1687 (if (eq patch-id :index)
1688 '("--cached")
1689 (list unmerged-stage))))
1690 (let ((args (append '("show" "-O" "--patch-with-stat" "-O" "-M")
1691 (and space-arg (list "-O" space-arg))
1692 (list (stgit-patch-name-at-point)))))
1693 (apply 'stgit-run args)))))
6a73154a
GH
1694 (t
1695 (error "No patch or file at point")))
1aece5c0
GH
1696 (with-current-buffer standard-output
1697 (goto-char (point-min))
1698 (diff-mode)))))
1699
1700(defmacro stgit-define-diff (name diff-arg &optional unmerged-action)
1701 `(defun ,name (&optional ignore-whitespace)
1702 ,(format "Show the patch on the current line.
1703
1704%sWith a prefix argument, ignore whitespace. With a prefix argument
1705greater than four (e.g., \\[universal-argument] \
1706\\[universal-argument] \\[%s]), ignore all whitespace."
1707 (if unmerged-action
1708 (format "For unmerged files, %s.\n\n" unmerged-action)
1709 "")
1710 name)
1711 (interactive "p")
9d04c657 1712 (stgit-assert-mode)
1aece5c0
GH
1713 (stgit-show-patch ,diff-arg ignore-whitespace)))
1714
1715(stgit-define-diff stgit-diff
1716 "--ours" nil)
1717(stgit-define-diff stgit-diff-ours
1718 "--ours"
1719 "diff against our branch")
1720(stgit-define-diff stgit-diff-theirs
1721 "--theirs"
1722 "diff against their branch")
1723(stgit-define-diff stgit-diff-base
1724 "--base"
1725 "diff against the merge base")
1726(stgit-define-diff stgit-diff-combined
1727 "--cc"
1728 "show a combined diff")
d9b954c7 1729
f87c2e22
GH
1730(defun stgit-move-change-to-index (file &optional force)
1731 "Copies the work tree state of FILE to index, using git add or git rm.
1732
1733If FORCE is not nil, use --force."
306b37a6
GH
1734 (let ((op (if (or (file-exists-p file) (file-symlink-p file))
1735 '("add") '("rm" "-q"))))
37cb5766 1736 (stgit-capture-output "*git output*"
f87c2e22
GH
1737 (apply 'stgit-run-git (append op (and force '("--force"))
1738 '("--") (list file))))))
37cb5766 1739
fd9fe574 1740(defun stgit-remove-change-from-index (file)
37cb5766
DK
1741 "Unstages the change in FILE from the index"
1742 (stgit-capture-output "*git output*"
1743 (stgit-run-git "reset" "-q" "--" file)))
1744
dde3ab4d
GH
1745(defun stgit-git-index-unmerged-p ()
1746 (let (result)
1747 (with-output-to-string
1748 (setq result (not (zerop (stgit-run-git-silent "diff-index" "--cached"
1749 "--diff-filter=U"
1750 "--quiet" "HEAD")))))
1751 result))
1752
37cb5766 1753(defun stgit-file-toggle-index ()
a9089e68
GH
1754 "Move modified file in or out of the index.
1755
1756Leaves the point where it is, but moves the mark to where the
1757file ended up. You can then jump to the file with \
1758\\[exchange-point-and-mark]."
37cb5766 1759 (interactive)
9d04c657 1760 (stgit-assert-mode)
612f999a
GH
1761 (let* ((patched-file (or (stgit-patched-file-at-point)
1762 (error "No file on the current line")))
413f9909 1763 (patched-status (stgit-file->status patched-file)))
612f999a 1764 (when (eq patched-status 'unmerged)
51783171 1765 (error (substitute-command-keys "Use \\[stgit-resolve-file] to move an unmerged file to the index")))
a9089e68 1766 (let* ((patch (stgit-patch-at-point))
413f9909 1767 (patch-name (stgit-patch->name patch))
612f999a 1768 (mark-file (if (eq patched-status 'rename)
413f9909
GH
1769 (stgit-file->cr-to patched-file)
1770 (stgit-file->file patched-file)))
612f999a 1771 (point-file (if (eq patched-status 'rename)
413f9909 1772 (stgit-file->cr-from patched-file)
6a73154a 1773 (stgit-neighbour-file))))
a9089e68 1774
37cb5766 1775 (cond ((eq patch-name :work)
413f9909 1776 (stgit-move-change-to-index (stgit-file->file patched-file)
f87c2e22 1777 (eq patched-status 'ignore)))
37cb5766 1778 ((eq patch-name :index)
413f9909 1779 (stgit-remove-change-from-index (stgit-file->file patched-file)))
37cb5766 1780 (t
612f999a 1781 (error "Can only move files between working tree and index")))
a9089e68
GH
1782 (stgit-refresh-worktree)
1783 (stgit-refresh-index)
612f999a 1784 (stgit-goto-patch (if (eq patch-name :index) :work :index) mark-file)
a9089e68 1785 (push-mark nil t t)
612f999a 1786 (stgit-goto-patch patch-name point-file))))
37cb5766 1787
dde3ab4d
GH
1788(defun stgit-toggle-index ()
1789 "Move change in or out of the index.
1790
1791Works on index and work tree, as well as files in either.
1792
1793Leaves the point where it is, but moves the mark to where the
1794file ended up. You can then jump to the file with \
1795\\[exchange-point-and-mark]."
1796 (interactive)
9d04c657 1797 (stgit-assert-mode)
dde3ab4d
GH
1798 (if (stgit-patched-file-at-point)
1799 (stgit-file-toggle-index)
1800 (let ((patch-name (stgit-patch-name-at-point)))
1801 (unless (memq patch-name '(:index :work))
1802 (error "Can only move changes between working tree and index"))
1803 (when (stgit-git-index-unmerged-p)
1804 (error "Resolve unmerged changes with \\[stgit-resolve-file] first"))
1805 (if (if (eq patch-name :index)
1806 (stgit-index-empty-p)
1807 (stgit-work-tree-empty-p))
1808 (message "No changes to be moved")
1809 (stgit-capture-output nil
1810 (if (eq patch-name :work)
1811 (stgit-run-git "add" "--update")
1812 (stgit-run-git "reset" "--mixed" "-q")))
1813 (stgit-refresh-worktree)
1814 (stgit-refresh-index))
1815 (stgit-goto-patch (if (eq patch-name :index) :work :index)))))
1816
0bca35c8 1817(defun stgit-edit ()
a53347d9 1818 "Edit the patch on the current line."
0bca35c8 1819 (interactive)
9d04c657 1820 (stgit-assert-mode)
64ada6f5 1821 (let ((patchsym (stgit-patch-name-at-point t t))
0780be79 1822 (edit-buf (get-buffer-create "*StGit edit*"))
0bca35c8
DK
1823 (dir default-directory))
1824 (log-edit 'stgit-confirm-edit t nil edit-buf)
d51722b7 1825 (set (make-local-variable 'stgit-edit-patchsym) patchsym)
0bca35c8
DK
1826 (setq default-directory dir)
1827 (let ((standard-output edit-buf))
655a3977
GH
1828 (save-excursion
1829 (stgit-run-silent "edit" "--save-template=-" patchsym)))))
0bca35c8
DK
1830
1831(defun stgit-confirm-edit ()
1832 (interactive)
1833 (let ((file (make-temp-file "stgit-edit-")))
1834 (write-region (point-min) (point-max) file)
1835 (stgit-capture-output nil
d51722b7 1836 (stgit-run "edit" "-f" file stgit-edit-patchsym))
0bca35c8 1837 (with-current-buffer log-edit-parent-buffer
1f0bf00f 1838 (stgit-reload))))
0bca35c8 1839
2acb7116 1840(defun stgit-new (add-sign &optional refresh)
aa04f831
GH
1841 "Create a new patch.
1842With a prefix argument, include a \"Signed-off-by:\" line at the
1843end of the patch."
1844 (interactive "P")
9d04c657 1845 (stgit-assert-mode)
c5d45b92
GH
1846 (let ((edit-buf (get-buffer-create "*StGit edit*"))
1847 (dir default-directory))
1848 (log-edit 'stgit-confirm-new t nil edit-buf)
aa04f831 1849 (setq default-directory dir)
2acb7116 1850 (set (make-local-variable 'stgit-refresh-after-new) refresh)
aa04f831
GH
1851 (when add-sign
1852 (save-excursion
1853 (let ((standard-output (current-buffer)))
1854 (stgit-run-silent "new" "--sign" "--save-template=-"))))))
64c097a0
DK
1855
1856(defun stgit-confirm-new ()
1857 (interactive)
2acb7116
DK
1858 (let ((file (make-temp-file "stgit-edit-"))
1859 (refresh stgit-refresh-after-new))
64c097a0
DK
1860 (write-region (point-min) (point-max) file)
1861 (stgit-capture-output nil
27b0f9e4 1862 (stgit-run "new" "-f" file))
64c097a0 1863 (with-current-buffer log-edit-parent-buffer
2acb7116
DK
1864 (if refresh
1865 (stgit-refresh)
1866 (stgit-reload)))))
1867
1868(defun stgit-new-and-refresh (add-sign)
1869 "Create a new patch and refresh it with the current changes.
1870
1871With a prefix argument, include a \"Signed-off-by:\" line at the
1872end of the patch.
1873
1874This works just like running `stgit-new' followed by `stgit-refresh'."
1875 (interactive "P")
9d04c657 1876 (stgit-assert-mode)
2acb7116 1877 (stgit-new add-sign t))
64c097a0
DK
1878
1879(defun stgit-create-patch-name (description)
1880 "Create a patch name from a long description"
1881 (let ((patch ""))
1882 (while (> (length description) 0)
1883 (cond ((string-match "\\`[a-zA-Z_-]+" description)
8439f657
GH
1884 (setq patch (downcase (concat patch
1885 (match-string 0 description))))
64c097a0
DK
1886 (setq description (substring description (match-end 0))))
1887 ((string-match "\\` +" description)
1888 (setq patch (concat patch "-"))
1889 (setq description (substring description (match-end 0))))
1890 ((string-match "\\`[^a-zA-Z_-]+" description)
1891 (setq description (substring description (match-end 0))))))
1892 (cond ((= (length patch) 0)
1893 "patch")
1894 ((> (length patch) 20)
1895 (substring patch 0 20))
1896 (t patch))))
0bca35c8 1897
9008e45b 1898(defun stgit-delete (patchsyms &optional spill-p)
d51722b7 1899 "Delete the patches in PATCHSYMS.
9008e45b
GH
1900Interactively, delete the marked patches, or the patch at point.
1901
1902With a prefix argument, or SPILL-P, spill the patch contents to
1903the work tree and index."
beac0f14 1904 (interactive (list (stgit-patches-marked-or-at-point t t)
9008e45b 1905 current-prefix-arg))
9d04c657 1906 (stgit-assert-mode)
e7231e4f
GH
1907 (unless patchsyms
1908 (error "No patches to delete"))
64ada6f5
GH
1909 (when (memq :index patchsyms)
1910 (error "Cannot delete the index"))
1911 (when (memq :work patchsyms)
1912 (error "Cannot delete the work tree"))
1913
d51722b7 1914 (let ((npatches (length patchsyms)))
9008e45b 1915 (when (yes-or-no-p (format "Really delete %d patch%s%s? "
e7231e4f 1916 npatches
9008e45b
GH
1917 (if (= 1 npatches) "" "es")
1918 (if spill-p
1919 " (spilling contents to index)"
1920 "")))
1921 (let ((args (if spill-p
1922 (cons "--spill" patchsyms)
1923 patchsyms)))
1924 (stgit-capture-output nil
1925 (apply 'stgit-run "delete" args))
1926 (stgit-reload)))))
d51722b7 1927
7cc45294
GH
1928(defun stgit-move-patches-target ()
1929 "Return the patchsym indicating a target patch for
1930`stgit-move-patches'.
1931
2547179e
GH
1932This is either the first unmarked patch at or after point, or one
1933of :top and :bottom if the point is after or before the applied
1934patches."
1935
1936 (save-excursion
1937 (let (result)
1938 (while (not result)
1939 (let ((patchsym (stgit-patch-name-at-point)))
1940 (cond ((memq patchsym '(:work :index)) (setq result :top))
1941 (patchsym (if (memq patchsym stgit-marked-patches)
1942 (stgit-next-patch)
1943 (setq result patchsym)))
1944 ((re-search-backward "^>" nil t) (setq result :top))
1945 (t (setq result :bottom)))))
1946 result)))
7cc45294 1947
95369f6c
GH
1948(defun stgit-sort-patches (patchsyms)
1949 "Returns the list of patches in PATCHSYMS sorted according to
1950their position in the patch series, bottommost first.
1951
2d7bcbd9 1952PATCHSYMS must not contain duplicate entries."
95369f6c
GH
1953 (let (sorted-patchsyms
1954 (series (with-output-to-string
1955 (with-current-buffer standard-output
1956 (stgit-run-silent "series" "--noprefix"))))
1957 start)
1958 (while (string-match "^\\(.+\\)" series start)
1959 (let ((patchsym (intern (match-string 1 series))))
1960 (when (memq patchsym patchsyms)
1961 (setq sorted-patchsyms (cons patchsym sorted-patchsyms))))
1962 (setq start (match-end 0)))
1963 (setq sorted-patchsyms (nreverse sorted-patchsyms))
1964
1965 (unless (= (length patchsyms) (length sorted-patchsyms))
1966 (error "Internal error"))
1967
1968 sorted-patchsyms))
1969
7cc45294
GH
1970(defun stgit-move-patches (patchsyms target-patch)
1971 "Move the patches in PATCHSYMS to below TARGET-PATCH.
1972If TARGET-PATCH is :bottom or :top, move the patches to the
1973bottom or top of the stack, respectively.
1974
1975Interactively, move the marked patches to where the point is."
1976 (interactive (list stgit-marked-patches
1977 (stgit-move-patches-target)))
9d04c657 1978 (stgit-assert-mode)
7cc45294
GH
1979 (unless patchsyms
1980 (error "Need at least one patch to move"))
1981
1982 (unless target-patch
1983 (error "Point not at a patch"))
1984
2547179e
GH
1985 ;; need to have patchsyms sorted by position in the stack
1986 (let ((sorted-patchsyms (stgit-sort-patches patchsyms)))
1987 (stgit-capture-output nil
1988 (if (eq target-patch :top)
1989 (apply 'stgit-run "float" sorted-patchsyms)
1990 (apply 'stgit-run
1991 "sink"
1992 (append (unless (eq target-patch :bottom)
1993 (list "--to" target-patch))
1994 '("--")
1995 sorted-patchsyms)))))
7cc45294
GH
1996 (stgit-reload))
1997
594aa463
KH
1998(defun stgit-squash (patchsyms)
1999 "Squash the patches in PATCHSYMS.
693d179b
GH
2000Interactively, squash the marked patches.
2001
2002Unless there are any conflicts, the patches will be merged into
2003one patch, which will occupy the same spot in the series as the
2004deepest patch had before the squash."
d51722b7 2005 (interactive (list stgit-marked-patches))
9d04c657 2006 (stgit-assert-mode)
d51722b7 2007 (when (< (length patchsyms) 2)
594aa463 2008 (error "Need at least two patches to squash"))
32d7545d
GH
2009 (let ((stgit-buffer (current-buffer))
2010 (edit-buf (get-buffer-create "*StGit edit*"))
693d179b
GH
2011 (dir default-directory)
2012 (sorted-patchsyms (stgit-sort-patches patchsyms)))
594aa463 2013 (log-edit 'stgit-confirm-squash t nil edit-buf)
693d179b 2014 (set (make-local-variable 'stgit-patchsyms) sorted-patchsyms)
ea0def18 2015 (setq default-directory dir)
32d7545d 2016 (let ((result (let ((standard-output edit-buf))
655a3977
GH
2017 (save-excursion
2018 (apply 'stgit-run-silent "squash"
2019 "--save-template=-" sorted-patchsyms)))))
32d7545d
GH
2020
2021 ;; stg squash may have reordered the patches or caused conflicts
2022 (with-current-buffer stgit-buffer
2023 (stgit-reload))
2024
2025 (unless (eq 0 result)
2026 (fundamental-mode)
2027 (rename-buffer "*StGit error*")
2028 (resize-temp-buffer-window)
2029 (switch-to-buffer-other-window stgit-buffer)
2030 (error "stg squash failed")))))
ea0def18 2031
594aa463 2032(defun stgit-confirm-squash ()
ea0def18
DK
2033 (interactive)
2034 (let ((file (make-temp-file "stgit-edit-")))
2035 (write-region (point-min) (point-max) file)
2036 (stgit-capture-output nil
594aa463 2037 (apply 'stgit-run "squash" "-f" file stgit-patchsyms))
ea0def18 2038 (with-current-buffer log-edit-parent-buffer
e6b1fdae
DK
2039 (stgit-clear-marks)
2040 ;; Go to first marked patch and stay there
2041 (goto-char (point-min))
2042 (re-search-forward (concat "^[>+-]\\*") nil t)
2043 (move-to-column goal-column)
2044 (let ((pos (point)))
1f0bf00f 2045 (stgit-reload)
e6b1fdae 2046 (goto-char pos)))))
ea0def18 2047
0663524d
KH
2048(defun stgit-help ()
2049 "Display help for the StGit mode."
2050 (interactive)
2051 (describe-function 'stgit-mode))
3a59f3db 2052
83e51dbf
DK
2053(defun stgit-undo (&optional arg)
2054 "Run stg undo.
b8463f1d
GH
2055With prefix argument, run it with the --hard flag.
2056
2057See also `stgit-redo'."
83e51dbf 2058 (interactive "P")
9d04c657 2059 (stgit-assert-mode)
83e51dbf
DK
2060 (stgit-capture-output nil
2061 (if arg
2062 (stgit-run "undo" "--hard")
2063 (stgit-run "undo")))
1f0bf00f 2064 (stgit-reload))
83e51dbf 2065
b8463f1d
GH
2066(defun stgit-redo (&optional arg)
2067 "Run stg redo.
2068With prefix argument, run it with the --hard flag.
2069
2070See also `stgit-undo'."
2071 (interactive "P")
9d04c657 2072 (stgit-assert-mode)
b8463f1d
GH
2073 (stgit-capture-output nil
2074 (if arg
2075 (stgit-run "redo" "--hard")
2076 (stgit-run "redo")))
2077 (stgit-reload))
2078
4d73c4d8
DK
2079(defun stgit-refresh (&optional arg)
2080 "Run stg refresh.
36a4eacd
GH
2081If the index contains any changes, only refresh from index.
2082
a53347d9 2083With prefix argument, refresh the marked patch or the patch under point."
4d73c4d8 2084 (interactive "P")
9d04c657 2085 (stgit-assert-mode)
4d73c4d8 2086 (let ((patchargs (if arg
beac0f14
GH
2087 (let ((patches (stgit-patches-marked-or-at-point nil t)))
2088 (when (> (length patches) 1)
2089 (error "Too many patches marked"))
2090 (cons "-p" patches))
b0424080 2091 nil)))
36a4eacd
GH
2092 (unless (stgit-index-empty-p)
2093 (setq patchargs (cons "--index" patchargs)))
4d73c4d8 2094 (stgit-capture-output nil
074a4fb0
GH
2095 (apply 'stgit-run "refresh" patchargs))
2096 (stgit-refresh-git-status))
4d73c4d8
DK
2097 (stgit-reload))
2098
ce3b6130 2099(defvar stgit-show-worktree nil
8f702de4 2100 "If nil, inhibit showing work tree and index in the stgit buffer.
ce3b6130 2101
8f702de4 2102See also `stgit-show-worktree-mode'.")
ce3b6130 2103
d9473917
GH
2104(defvar stgit-show-ignored nil
2105 "If nil, inhibit showing files ignored by git.")
2106
2107(defvar stgit-show-unknown nil
2108 "If nil, inhibit showing files not registered with git.")
2109
a0045b87
DK
2110(defvar stgit-show-patch-names t
2111 "If nil, inhibit showing patch names.")
2112
ce3b6130
DK
2113(defun stgit-toggle-worktree (&optional arg)
2114 "Toggle the visibility of the work tree.
2d7bcbd9 2115With ARG, show the work tree if ARG is positive.
ce3b6130 2116
8f702de4
GH
2117Its initial setting is controlled by `stgit-default-show-worktree'.
2118
2119`stgit-show-worktree-mode' controls where on screen the index and
2120work tree will show up."
ce3b6130 2121 (interactive)
9d04c657 2122 (stgit-assert-mode)
ce3b6130
DK
2123 (setq stgit-show-worktree
2124 (if (numberp arg)
2125 (> arg 0)
2126 (not stgit-show-worktree)))
2127 (stgit-reload))
2128
d9473917
GH
2129(defun stgit-toggle-ignored (&optional arg)
2130 "Toggle the visibility of files ignored by git in the work
2131tree. With ARG, show these files if ARG is positive.
2132
2133Use \\[stgit-toggle-worktree] to show the work tree."
2134 (interactive)
9d04c657 2135 (stgit-assert-mode)
d9473917
GH
2136 (setq stgit-show-ignored
2137 (if (numberp arg)
2138 (> arg 0)
2139 (not stgit-show-ignored)))
2140 (stgit-reload))
2141
2142(defun stgit-toggle-unknown (&optional arg)
2143 "Toggle the visibility of files not registered with git in the
2144work tree. With ARG, show these files if ARG is positive.
2145
2146Use \\[stgit-toggle-worktree] to show the work tree."
2147 (interactive)
9d04c657 2148 (stgit-assert-mode)
d9473917
GH
2149 (setq stgit-show-unknown
2150 (if (numberp arg)
2151 (> arg 0)
2152 (not stgit-show-unknown)))
2153 (stgit-reload))
2154
a0045b87
DK
2155(defun stgit-toggle-patch-names (&optional arg)
2156 "Toggle the visibility of patch names. With ARG, show patch names
2157if ARG is positive.
2158
2159The initial setting is controlled by `stgit-default-show-patch-names'."
2160 (interactive)
2161 (stgit-assert-mode)
2162 (setq stgit-show-patch-names
2163 (if (numberp arg)
2164 (> arg 0)
2165 (not stgit-show-patch-names)))
2166 (stgit-reload))
2167
3a59f3db 2168(provide 'stgit)