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