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