stgit.el: Add stgit-{git,stg}-program variables
[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)
a4a01630 17(require 'comint)
98230edd 18(require 'ewoc)
5038381d 19(require 'easymenu)
0b9ea6b8 20(require 'format-spec)
0f076fe6 21
4ba91e80
DK
22(defun stgit-set-default (symbol value)
23 "Set default value of SYMBOL to VALUE using `set-default' and
24reload all StGit buffers."
25 (set-default symbol value)
26 (dolist (buf (buffer-list))
27 (with-current-buffer buf
28 (when (eq major-mode 'stgit-mode)
29 (stgit-reload)))))
30
31(defgroup stgit nil
32 "A user interface for the StGit patch maintenance tool."
33 :group 'tools
34 :link '(function-link stgit)
35 :link '(url-link "http://www.procode.org/stgit/"))
36
4ba91e80
DK
37(defcustom stgit-default-show-worktree t
38 "Set to non-nil to by default show the working tree in a new stgit buffer.
39
bc11fb08
GH
40Use \\<stgit-mode-map>\\[stgit-toggle-worktree] to toggle this \
41setting in an already-started StGit buffer."
4ba91e80
DK
42 :type 'boolean
43 :group 'stgit
44 :link '(variable-link stgit-show-worktree))
45
bc11fb08
GH
46(defcustom stgit-default-show-unknown nil
47 "Set to non-nil to by default show unknown files a new stgit buffer.
48
49Use \\<stgit-mode-map>\\[stgit-toggle-unknown] to toggle this \
50setting in an already-started StGit buffer."
51 :type 'boolean
52 :group 'stgit
53 :link '(variable-link stgit-show-unknown))
54
55(defcustom stgit-default-show-ignored nil
56 "Set to non-nil to by default show ignored files a new stgit buffer.
57
58Use \\<stgit-mode-map>\\[stgit-toggle-ignored] to toggle this \
59setting in an already-started StGit buffer."
60 :type 'boolean
61 :group 'stgit
62 :link '(variable-link stgit-show-ignored))
63
fefc11a4
GH
64(defcustom stgit-default-show-patch-names t
65 "If non-nil, default to showing patch names in a new stgit buffer.
66
67Use \\<stgit-mode-map>\\[stgit-toggle-patch-names] \
68to toggle the this setting in an already-started StGit buffer."
69 :type 'boolean
70 :group 'stgit
71 :link '(variable-link stgit-show-patch-names))
72
73(defcustom stgit-default-show-committed nil
74 "Set to nil to inhibit showing of historical git commits by default.
75
76Use \\<stgit-mode-map>\\[stgit-toggle-committed] \
77to toggle this setting and to control how many commits are
78shown."
79 :type 'boolean
80 :group 'stgit
81 :link '(variable-link stgit-default-committed-count)
82 :link '(variable-link stgit-show-committed))
83
84(defcustom stgit-default-committed-count 5
85 "The number of historical commits to show when `stgit-show-committed'
86is enabled."
87 :type 'number
88 :group 'stgit
89 :link '(variable-link stgit-default-show-committed)
90 :link '(variable-link stgit-committed-count))
91
137b315b
GH
92(defcustom stgit-default-show-svn t
93 "Set to non-nil to by default show subversion information in a
94new stgit buffer.
95
96Use \\<stgit-mode-map>\\[stgit-toggle-svn] to toggle this \
97setting in an already-started StGit buffer."
98 :type 'boolean
99 :group 'stgit
100 :link '(variable-link stgit-show-worktree))
101
fefc11a4
GH
102(defcustom stgit-abbreviate-copies-and-renames t
103 "If non-nil, abbreviate copies and renames as \"dir/{old -> new}/file\"
104instead of \"dir/old/file -> dir/new/file\"."
105 :type 'boolean
106 :group 'stgit
107 :set 'stgit-set-default)
108
4ba91e80
DK
109(defcustom stgit-find-copies-harder nil
110 "Try harder to find copied files when listing patches.
111
112When not nil, runs git diff-tree with the --find-copies-harder
113flag, which reduces performance."
114 :type 'boolean
115 :group 'stgit
116 :set 'stgit-set-default)
117
118(defcustom stgit-show-worktree-mode 'center
119 "This variable controls where the \"Index\" and \"Work tree\"
120will be shown on in the buffer.
121
122It can be set to 'top (above all patches), 'center (show between
123applied and unapplied patches), and 'bottom (below all patches)."
124 :type '(radio (const :tag "above all patches (top)" top)
125 (const :tag "between applied and unapplied patches (center)"
126 center)
127 (const :tag "below all patches (bottom)" bottom))
128 :group 'stgit
129 :link '(variable-link stgit-show-worktree)
130 :set 'stgit-set-default)
131
0b9ea6b8
DK
132(defcustom stgit-patch-line-format "%s%m%-30n %e%d"
133 "The format string used to format patch lines.
134The format string is passed to `format-spec' and the following
135format characters are recognized:
136
137 %s - A '+', '-', '>' or space, depending on whether the patch is
138 applied, unapplied, top, or something else.
139
140 %m - An asterisk if the patch is marked, and a space otherwise.
141
142 %n - The patch name.
143
144 %e - The string \"(empty) \" if the patch is empty.
145
a0045b87
DK
146 %d - The short patch description.
147
148 %D - The short patch description, or the patch name.
149
150When `stgit-show-patch-names' is non-nil, the `stgit-noname-patch-line-format'
151variable is used instead."
152 :type 'string
153 :group 'stgit
154 :set 'stgit-set-default)
155
156(defcustom stgit-noname-patch-line-format "%s%m%e%D"
157 "The alternate format string used to format patch lines.
158It has the same semantics as `stgit-patch-line-format', and the
f3dbdbc0
GH
159display can be toggled between the two formats using \
160\\<stgit-mode-map>\\[stgit-toggle-patch-names].
a0045b87
DK
161
162The alternate form is used when the patch name is hidden."
0b9ea6b8
DK
163 :type 'string
164 :group 'stgit
165 :set 'stgit-set-default)
166
43ee50b6
DK
167(defcustom stgit-file-line-format " %-11s %-2m %n %c"
168 "The format string used to format file lines.
169The format string is passed to `format-spec' and the following
170format characters are recognized:
171
172 %s - A string describing the status of the file.
173
174 %m - Mode change information
175
176 %n - The file name.
177
178 %c - A description of file changes."
179 :type 'string
180 :group 'stgit
181 :set 'stgit-set-default)
182
548ddfa6
GH
183(defcustom stgit-git-program "git"
184 "The program used by `stgit-mode' to run git."
185 :type 'string
186 :group 'stgit)
187
188(defcustom stgit-stg-program "stg"
189 "The program used by `stgit-mode' to run StGit."
190 :type 'string
191 :group 'stgit)
192
fefc11a4
GH
193(defgroup stgit-faces nil
194 "Faces for `stgit-mode'."
195 :group 'stgit)
196
4ba91e80
DK
197(defface stgit-branch-name-face
198 '((t :inherit bold))
199 "The face used for the StGit branch name"
fefc11a4 200 :group 'stgit-faces)
4ba91e80
DK
201
202(defface stgit-top-patch-face
203 '((((background dark)) (:weight bold :foreground "yellow"))
204 (((background light)) (:weight bold :foreground "purple"))
205 (t (:weight bold)))
206 "The face used for the top patch names"
fefc11a4 207 :group 'stgit-faces)
4ba91e80
DK
208
209(defface stgit-applied-patch-face
210 '((((background dark)) (:foreground "light yellow"))
211 (((background light)) (:foreground "purple"))
212 (t ()))
213 "The face used for applied patch names"
fefc11a4 214 :group 'stgit-faces)
4ba91e80
DK
215
216(defface stgit-unapplied-patch-face
217 '((((background dark)) (:foreground "gray80"))
218 (((background light)) (:foreground "orchid"))
219 (t ()))
220 "The face used for unapplied patch names"
fefc11a4 221 :group 'stgit-faces)
4ba91e80 222
bf7e391c
GH
223(defface stgit-committed-patch-face
224 '((((background dark)) (:foreground "gray50"))
225 (((background light)) (:foreground "gray50"))
226 (t ()))
227 "The face used for already committed patch names"
fefc11a4 228 :group 'stgit-faces)
bf7e391c 229
4ba91e80
DK
230(defface stgit-description-face
231 '((((background dark)) (:foreground "tan"))
232 (((background light)) (:foreground "dark red")))
233 "The face used for StGit descriptions"
fefc11a4 234 :group 'stgit-faces)
4ba91e80
DK
235
236(defface stgit-index-work-tree-title-face
237 '((((supports :slant italic)) :slant italic)
238 (t :inherit bold))
239 "StGit mode face used for the \"Index\" and \"Work tree\" titles"
fefc11a4 240 :group 'stgit-faces)
4ba91e80
DK
241
242(defface stgit-unmerged-file-face
243 '((((class color) (background light)) (:foreground "red" :bold t))
244 (((class color) (background dark)) (:foreground "red" :bold t)))
245 "StGit mode face used for unmerged file status"
fefc11a4 246 :group 'stgit-faces)
4ba91e80
DK
247
248(defface stgit-unknown-file-face
249 '((((class color) (background light)) (:foreground "goldenrod" :bold t))
250 (((class color) (background dark)) (:foreground "goldenrod" :bold t)))
251 "StGit mode face used for unknown file status"
fefc11a4 252 :group 'stgit-faces)
4ba91e80
DK
253
254(defface stgit-ignored-file-face
255 '((((class color) (background light)) (:foreground "grey60"))
256 (((class color) (background dark)) (:foreground "grey40")))
257 "StGit mode face used for ignored files")
258
259(defface stgit-file-permission-face
260 '((((class color) (background light)) (:foreground "green" :bold t))
261 (((class color) (background dark)) (:foreground "green" :bold t)))
262 "StGit mode face used for permission changes."
fefc11a4 263 :group 'stgit-faces)
4ba91e80
DK
264
265(defface stgit-modified-file-face
266 '((((class color) (background light)) (:foreground "purple"))
267 (((class color) (background dark)) (:foreground "salmon")))
268 "StGit mode face used for modified file status"
fefc11a4 269 :group 'stgit-faces)
4ba91e80 270
56d81fe5 271(defun stgit (dir)
fdf5e327
GH
272 "Manage StGit patches for the tree in DIR.
273
274See `stgit-mode' for commands available."
56d81fe5 275 (interactive "DDirectory: \n")
52144ce5 276 (switch-to-stgit-buffer (git-get-top-dir dir))
1f0bf00f 277 (stgit-reload))
56d81fe5 278
9d04c657
GH
279(defun stgit-assert-mode ()
280 "Signal an error if not in an StGit buffer."
281 (assert (derived-mode-p 'stgit-mode) nil "Not an StGit buffer"))
282
074a4fb0
GH
283(unless (fboundp 'git-get-top-dir)
284 (defun git-get-top-dir (dir)
285 "Retrieve the top-level directory of a git tree."
286 (let ((cdup (with-output-to-string
287 (with-current-buffer standard-output
288 (cd dir)
548ddfa6 289 (unless (eq 0 (call-process stgit-git-program nil t nil
074a4fb0 290 "rev-parse" "--show-cdup"))
df283a8b 291 (error "Cannot find top-level git tree for %s" dir))))))
074a4fb0
GH
292 (expand-file-name (concat (file-name-as-directory dir)
293 (car (split-string cdup "\n")))))))
294
295(defun stgit-refresh-git-status (&optional dir)
296 "If it exists, refresh the `git-status' buffer belonging to
297directory DIR or `default-directory'"
298 (when (and (fboundp 'git-find-status-buffer)
299 (fboundp 'git-refresh-status))
300 (let* ((top-dir (git-get-top-dir (or dir default-directory)))
301 (git-status-buffer (and top-dir (git-find-status-buffer top-dir))))
302 (when git-status-buffer
303 (with-current-buffer git-status-buffer
304 (git-refresh-status))))))
52144ce5 305
b894e680
DK
306(defun stgit-find-buffer (dir)
307 "Return the buffer displaying StGit patches for DIR, or nil if none."
56d81fe5
DK
308 (setq dir (file-name-as-directory dir))
309 (let ((buffers (buffer-list)))
310 (while (and buffers
311 (not (with-current-buffer (car buffers)
312 (and (eq major-mode 'stgit-mode)
313 (string= default-directory dir)))))
314 (setq buffers (cdr buffers)))
b894e680
DK
315 (and buffers (car buffers))))
316
317(defun switch-to-stgit-buffer (dir)
318 "Switch to a (possibly new) buffer displaying StGit patches for DIR."
319 (setq dir (file-name-as-directory dir))
320 (let ((buffer (stgit-find-buffer dir)))
321 (switch-to-buffer (or buffer
322 (create-stgit-buffer dir)))))
323
413f9909
GH
324(defstruct (stgit-patch
325 (:conc-name stgit-patch->))
3164eec6 326 status name desc empty files-ewoc)
56d81fe5 327
0b9ea6b8 328(defun stgit-patch-display-name (patch)
413f9909 329 (let ((name (stgit-patch->name patch)))
0b9ea6b8
DK
330 (case name
331 (:index "Index")
332 (:work "Work Tree")
333 (t (symbol-name name)))))
334
da30db2a
GH
335(defun stgit-insert-without-trailing-whitespace (text)
336 "Insert TEXT in buffer using `insert', without trailing whitespace.
337A newline is appended."
338 (unless (string-match "\\(.*?\\) *$" text)
339 (error))
340 (insert (match-string 1 text) ?\n))
341
6fdc3546
GH
342(defun stgit-line-format ()
343 "Return the current line format; one of
344`stgit-patch-line-format' and `stgit-noname-patch-line-format'"
345 (if stgit-show-patch-names
346 stgit-patch-line-format
347 stgit-noname-patch-line-format))
348
98230edd 349(defun stgit-patch-pp (patch)
413f9909 350 (let* ((status (stgit-patch->status patch))
9153ce3a 351 (start (point))
413f9909 352 (name (stgit-patch->name patch))
0b9ea6b8 353 (face (cdr (assq status stgit-patch-status-face-alist)))
6fdc3546 354 (fmt (stgit-line-format))
0b9ea6b8
DK
355 (spec (format-spec-make
356 ?s (case status
357 ('applied "+")
358 ('top ">")
359 ('unapplied "-")
360 (t " "))
361 ?m (if (memq name stgit-marked-patches)
362 "*" " ")
363 ?n (propertize (stgit-patch-display-name patch)
364 'face face
365 'syntax-table (string-to-syntax "w"))
413f9909
GH
366 ?e (if (stgit-patch->empty patch) "(empty) " "")
367 ?d (propertize (or (stgit-patch->desc patch) "")
a0045b87 368 'face 'stgit-description-face)
48047215
GH
369 ?D (propertize (let ((desc (stgit-patch->desc patch)))
370 (if (zerop (length desc))
371 (stgit-patch-display-name patch)
372 desc))
da30db2a
GH
373 'face face)))
374 (text (format-spec fmt spec)))
0b9ea6b8 375
da30db2a 376 (stgit-insert-without-trailing-whitespace text)
f9b82d36 377 (put-text-property start (point) 'entry-type 'patch)
98230edd 378 (when (memq name stgit-expanded-patches)
0de6881a 379 (stgit-insert-patch-files patch))
98230edd
DK
380 (put-text-property start (point) 'patch-data patch)))
381
56d81fe5
DK
382(defun create-stgit-buffer (dir)
383 "Create a buffer for showing StGit patches.
384Argument DIR is the repository path."
385 (let ((buf (create-file-buffer (concat dir "*stgit*")))
386 (inhibit-read-only t))
387 (with-current-buffer buf
388 (setq default-directory dir)
389 (stgit-mode)
98230edd 390 (set (make-local-variable 'stgit-ewoc)
4f7ff561 391 (ewoc-create #'stgit-patch-pp "Branch:\n\n" "--\n" t))
56d81fe5
DK
392 (setq buffer-read-only t))
393 buf))
394
072e96c5
GH
395(def-edebug-spec stgit-capture-output
396 (form body))
56d81fe5 397(defmacro stgit-capture-output (name &rest body)
e558a4ab
GH
398 "Capture StGit output and, if there was any output, show it in a window
399at the end.
400Returns nil if there was no output."
94baef5a
DK
401 (declare (debug ([&or stringp null] body))
402 (indent 1))
34afb86c
DK
403 `(let ((output-buf (get-buffer-create ,(or name "*StGit output*")))
404 (stgit-dir default-directory)
405 (inhibit-read-only t))
56d81fe5 406 (with-current-buffer output-buf
277b52af 407 (buffer-disable-undo)
34afb86c
DK
408 (erase-buffer)
409 (setq default-directory stgit-dir)
410 (setq buffer-read-only t))
56d81fe5
DK
411 (let ((standard-output output-buf))
412 ,@body)
34afb86c
DK
413 (with-current-buffer output-buf
414 (set-buffer-modified-p nil)
415 (setq buffer-read-only t)
416 (if (< (point-min) (point-max))
417 (display-buffer output-buf t)))))
56d81fe5 418
d51722b7
GH
419(defun stgit-make-run-args (args)
420 "Return a copy of ARGS with its elements converted to strings."
421 (mapcar (lambda (x)
422 ;; don't use (format "%s" ...) to limit type errors
423 (cond ((stringp x) x)
424 ((integerp x) (number-to-string x))
425 ((symbolp x) (symbol-name x))
426 (t
427 (error "Bad element in stgit-make-run-args args: %S" x))))
428 args))
429
1b471044
GH
430(defvar stgit-inhibit-messages nil
431 "Set to non-nil to inhibit messages when running `stg' commands.
432See also `stgit-message'.")
433(defun stgit-message (format-spec &rest args)
434 "Call `message' on the arguments unless `stgit-inhibit-messages' is non-nil."
435 (unless stgit-inhibit-messages
436 (apply 'message format-spec args)))
56d81fe5 437
9aecd505 438(defun stgit-run (&rest args)
d51722b7 439 (setq args (stgit-make-run-args args))
548ddfa6
GH
440 (let ((msgcmd (mapconcat #'identity (cons stgit-stg-program args) " ")))
441 (stgit-message "Running %s..." msgcmd)
1b471044 442 (prog1
548ddfa6
GH
443 (apply 'call-process stgit-stg-program nil standard-output nil args)
444 (stgit-message "Running %s...done" msgcmd))))
1b471044
GH
445
446(defun stgit-run-silent (&rest args)
447 (let ((stgit-inhibit-messages t))
448 (apply 'stgit-run args)))
9aecd505 449
378a003d 450(defun stgit-run-git (&rest args)
d51722b7 451 (setq args (stgit-make-run-args args))
548ddfa6
GH
452 (let ((msgcmd (mapconcat #'identity (cons stgit-git-program args) " ")))
453 (stgit-message "Running %s..." msgcmd)
1b471044 454 (prog1
548ddfa6
GH
455 (apply 'call-process stgit-git-program nil standard-output nil args)
456 (stgit-message "Running %s...done" msgcmd))))
378a003d 457
1f60181a 458(defun stgit-run-git-silent (&rest args)
1b471044
GH
459 (let ((stgit-inhibit-messages t))
460 (apply 'stgit-run-git args)))
1f60181a 461
b894e680
DK
462(defun stgit-index-empty-p ()
463 "Returns non-nil if the index contains no changes from HEAD."
464 (zerop (stgit-run-git-silent "diff-index" "--cached" "--quiet" "HEAD")))
465
1629f59f
GH
466(defun stgit-work-tree-empty-p ()
467 "Returns non-nil if the work tree contains no changes from index."
468 (zerop (stgit-run-git-silent "diff-files" "--quiet")))
469
e44674e3
GH
470(defvar stgit-did-advise nil
471 "Set to non-nil if appropriate (non-stgit) git functions have
472been advised to update the stgit status when necessary.")
473
3e528fb0
GH
474(defconst stgit-allowed-branch-name-re
475 ;; Disallow control characters, space, del, and "/:@^{}~" in
476 ;; "/"-separated parts; parts may not start with a period (.)
477 "^[^\0- ./:@^{}~\177][^\0- /:@^{}~\177]*\
478\\(/[^\0- ./:@^{}~\177][^\0- /:@^{}~\177]*\\)*$"
479 "Regular expression that (new) branch names must match.")
480
210a2a52
DK
481(defun stgit-refresh-index ()
482 (when stgit-index-node
483 (ewoc-invalidate (car stgit-index-node) (cdr stgit-index-node))))
484
485(defun stgit-refresh-worktree ()
486 (when stgit-worktree-node
487 (ewoc-invalidate (car stgit-worktree-node) (cdr stgit-worktree-node))))
488
8f702de4
GH
489(defun stgit-run-series-insert-index (ewoc)
490 (setq index-node (cons ewoc (ewoc-enter-last ewoc
491 (make-stgit-patch
492 :status 'index
493 :name :index
494 :desc nil
495 :empty nil)))
496 worktree-node (cons ewoc (ewoc-enter-last ewoc
497 (make-stgit-patch
498 :status 'work
499 :name :work
500 :desc nil
501 :empty nil)))))
502
137b315b
GH
503(defun stgit-svn-find-rev (sha1 hash)
504 "Return the subversion revision corresponding to SHA1 as
505reported by git svn.
506
507Cached data is stored in HASH, which must have been created
508using (make-hash-table :test 'equal)."
509 (let ((result (gethash sha1 hash t)))
510 (when (eq result t)
511 (let ((svn-rev (with-output-to-string
512 (stgit-run-git-silent "svn" "find-rev"
513 "--" sha1))))
514 (setq result (when (string-match "\\`[0-9]+" svn-rev)
515 (string-to-number (match-string 0 svn-rev))))
516 (puthash sha1 result hash)))
517 result))
518
98230edd 519(defun stgit-run-series (ewoc)
8f702de4
GH
520 (setq stgit-index-node nil
521 stgit-worktree-node nil)
bf7e391c
GH
522 (let (all-patchsyms)
523 (when stgit-show-committed
137b315b
GH
524 (let* ((show-svn stgit-show-svn)
525 (svn-hash stgit-svn-find-rev-hash)
2c2b8ec2
GH
526 (nentries (format "-%s" stgit-committed-count))
527 (base (stgit-id "{base}")))
bf7e391c
GH
528 (with-temp-buffer
529 (let* ((standard-output (current-buffer))
530 (fmt (stgit-line-format))
531 (commit-abbrev (when (string-match "%-\\([0-9]+\\)n" fmt)
137b315b
GH
532 (string-to-number (match-string 1 fmt))))
533 (exit-status (stgit-run-git-silent "--no-pager" "log"
534 "--reverse"
535 "--pretty=oneline"
2c2b8ec2
GH
536 nentries
537 base)))
bf7e391c
GH
538 (goto-char (point-min))
539 (if (not (zerop exit-status))
540 (message "Failed to run git log")
541 (while (not (eobp))
542 (unless (looking-at
543 "\\([0-9a-f]+\\)\\(\\.\\.\\.\\)? \\(.*\\)")
544 (error "Syntax error in output from git log"))
545 (let* ((state 'committed)
137b315b 546 (name (match-string 1))
bf7e391c
GH
547 (desc (match-string 3))
548 (empty nil))
137b315b
GH
549
550 (when show-svn
551 (let ((svn-rev (stgit-svn-find-rev name svn-hash)))
552 (when svn-rev
553 (setq desc (format "(r%s) %s" svn-rev desc)))))
554
555 (and commit-abbrev
556 (< commit-abbrev (length name))
557 (setq name (substring name 0 commit-abbrev)))
558
559 (setq name (intern name))
560
bf7e391c
GH
561 (setq all-patchsyms (cons name all-patchsyms))
562 (ewoc-enter-last ewoc
563 (make-stgit-patch
564 :status state
565 :name name
566 :desc desc
567 :empty empty)))
568 (forward-line 1)))))))
569 (let ((inserted-index (not stgit-show-worktree))
570 index-node
571 worktree-node)
572 (with-temp-buffer
573 (let* ((standard-output (current-buffer))
574 (exit-status (stgit-run-silent "series"
575 "--description" "--empty")))
576 (goto-char (point-min))
577 (if (not (zerop exit-status))
578 (cond ((looking-at "stg series: \\(.*\\)")
579 (setq inserted-index t)
580 (ewoc-set-hf ewoc (car (ewoc-get-hf ewoc))
581 (substitute-command-keys
582 "-- not initialized; run \\[stgit-init]")))
583 ((looking-at ".*")
584 (error "Error running stg: %s"
585 (match-string 0))))
586 (while (not (eobp))
587 (unless (looking-at
588 "\\([0 ]\\)\\([>+-]\\)\\( \\)\\([^ ]+\\) *[|#] \\(.*\\)")
589 (error "Syntax error in output from stg series"))
590 (let* ((state-str (match-string 2))
591 (state (cond ((string= state-str ">") 'top)
592 ((string= state-str "+") 'applied)
593 ((string= state-str "-") 'unapplied)))
594 (name (intern (match-string 4)))
595 (desc (match-string 5))
596 (empty (string= (match-string 1) "0")))
597 (unless inserted-index
598 (when (or (eq stgit-show-worktree-mode 'top)
599 (and (eq stgit-show-worktree-mode 'center)
600 (eq state 'unapplied)))
601 (setq inserted-index t)
602 (stgit-run-series-insert-index ewoc)))
603 (setq all-patchsyms (cons name all-patchsyms))
604 (ewoc-enter-last ewoc
605 (make-stgit-patch
606 :status state
607 :name name
608 :desc desc
609 :empty empty)))
610 (forward-line 1)))))
8f702de4 611 (unless inserted-index
bf7e391c
GH
612 (stgit-run-series-insert-index ewoc))
613 (setq stgit-index-node index-node
614 stgit-worktree-node worktree-node
615 stgit-marked-patches (intersection stgit-marked-patches
616 all-patchsyms)))))
98230edd 617
3e528fb0
GH
618(defun stgit-current-branch ()
619 "Return the name of the current branch."
620 (substring (with-output-to-string
621 (stgit-run-silent "branch"))
622 0 -1))
623
f9533abf
GH
624(defun stgit-reload (&optional description)
625 "Update the contents of the StGit buffer.
626
627If DESCRIPTION is non-nil, it is displayed as a status message
628during the operation."
56d81fe5 629 (interactive)
9d04c657 630 (stgit-assert-mode)
f9533abf
GH
631 (when description
632 (message "%s..." description))
56d81fe5
DK
633 (let ((inhibit-read-only t)
634 (curline (line-number-at-pos))
a9089e68 635 (curpatch (stgit-patch-name-at-point))
f9533abf
GH
636 (curfile (stgit-patched-file-at-point))
637 (stgit-inhibit-messages description))
98230edd
DK
638 (ewoc-filter stgit-ewoc #'(lambda (x) nil))
639 (ewoc-set-hf stgit-ewoc
640 (concat "Branch: "
3e528fb0
GH
641 (propertize (stgit-current-branch)
642 'face 'stgit-branch-name-face)
4f7ff561 643 "\n\n")
ce3b6130
DK
644 (if stgit-show-worktree
645 "--"
646 (propertize
647 (substitute-command-keys "--\n\"\\[stgit-toggle-worktree]\"\
648 shows the working tree\n")
6a73154a 649 'face 'stgit-description-face)))
98230edd 650 (stgit-run-series stgit-ewoc)
2570f6e5
GH
651 (unless (and curpatch
652 (stgit-goto-patch curpatch
653 (and curfile (stgit-file->file curfile))))
79473ca5
GH
654 (goto-char (point-min))
655 (forward-line (1- curline))
f9533abf
GH
656 (move-to-column (stgit-goal-column)))
657 (stgit-refresh-git-status))
658 (when description
659 (message "%s...done" description)))
56d81fe5 660
1f60181a
GH
661(defconst stgit-file-status-code-strings
662 (mapcar (lambda (arg)
663 (cons (car arg)
a6d9a852
GH
664 (propertize (cadr arg) 'face (car (cddr arg)))))
665 '((add "Added" stgit-modified-file-face)
666 (copy "Copied" stgit-modified-file-face)
667 (delete "Deleted" stgit-modified-file-face)
668 (modify "Modified" stgit-modified-file-face)
669 (rename "Renamed" stgit-modified-file-face)
670 (mode-change "Mode change" stgit-modified-file-face)
671 (unmerged "Unmerged" stgit-unmerged-file-face)
d9473917
GH
672 (unknown "Unknown" stgit-unknown-file-face)
673 (ignore "Ignored" stgit-ignored-file-face)))
1f60181a
GH
674 "Alist of code symbols to description strings")
675
000f337c
GH
676(defconst stgit-patch-status-face-alist
677 '((applied . stgit-applied-patch-face)
678 (top . stgit-top-patch-face)
679 (unapplied . stgit-unapplied-patch-face)
bf7e391c 680 (committed . stgit-committed-patch-face)
9153ce3a
GH
681 (index . stgit-index-work-tree-title-face)
682 (work . stgit-index-work-tree-title-face))
000f337c
GH
683 "Alist of face to use for a given patch status")
684
3164eec6
DK
685(defun stgit-file-status-code-as-string (file)
686 "Return stgit status code for FILE as a string"
413f9909 687 (let* ((code (assq (stgit-file->status file)
3164eec6 688 stgit-file-status-code-strings))
413f9909 689 (score (stgit-file->cr-score file)))
3164eec6 690 (when code
43ee50b6
DK
691 (if (and score (/= score 100))
692 (format "%s %s" (cdr code)
693 (propertize (format "%d%%" score)
694 'face 'stgit-description-face))
695 (cdr code)))))
1f60181a 696
a6d9a852 697(defun stgit-file-status-code (str &optional score)
1f60181a
GH
698 "Return stgit status code from git status string"
699 (let ((code (assoc str '(("A" . add)
700 ("C" . copy)
701 ("D" . delete)
d9473917 702 ("I" . ignore)
1f60181a
GH
703 ("M" . modify)
704 ("R" . rename)
705 ("T" . mode-change)
706 ("U" . unmerged)
707 ("X" . unknown)))))
a6d9a852
GH
708 (setq code (if code (cdr code) 'unknown))
709 (when (stringp score)
710 (if (> (length score) 0)
711 (setq score (string-to-number score))
712 (setq score nil)))
713 (if score (cons code score) code)))
714
715(defconst stgit-file-type-strings
716 '((#o100 . "file")
717 (#o120 . "symlink")
718 (#o160 . "subproject"))
719 "Alist of names of file types")
720
721(defun stgit-file-type-string (type)
47271f41
GH
722 "Return string describing file type TYPE (the high bits of file permission).
723Cf. `stgit-file-type-strings' and `stgit-file-type-change-string'."
a6d9a852
GH
724 (let ((type-str (assoc type stgit-file-type-strings)))
725 (or (and type-str (cdr type-str))
726 (format "unknown type %o" type))))
727
728(defun stgit-file-type-change-string (old-perm new-perm)
47271f41
GH
729 "Return string describing file type change from OLD-PERM to NEW-PERM.
730Cf. `stgit-file-type-string'."
a6d9a852
GH
731 (let ((old-type (lsh old-perm -9))
732 (new-type (lsh new-perm -9)))
733 (cond ((= old-type new-type) "")
734 ((zerop new-type) "")
735 ((zerop old-type)
736 (if (= new-type #o100)
737 ""
43ee50b6
DK
738 (format "(%s)" (stgit-file-type-string new-type))))
739 (t (format "(%s -> %s)"
a6d9a852
GH
740 (stgit-file-type-string old-type)
741 (stgit-file-type-string new-type))))))
742
743(defun stgit-file-mode-change-string (old-perm new-perm)
47271f41
GH
744 "Return string describing file mode change from OLD-PERM to NEW-PERM.
745Cf. `stgit-file-type-change-string'."
a6d9a852
GH
746 (setq old-perm (logand old-perm #o777)
747 new-perm (logand new-perm #o777))
748 (if (or (= old-perm new-perm)
749 (zerop old-perm)
750 (zerop new-perm))
751 ""
752 (let* ((modified (logxor old-perm new-perm))
753 (not-x-modified (logand (logxor old-perm new-perm) #o666)))
754 (cond ((zerop modified) "")
755 ((and (zerop not-x-modified)
756 (or (and (eq #o111 (logand old-perm #o111))
757 (propertize "-x" 'face 'stgit-file-permission-face))
758 (and (eq #o111 (logand new-perm #o111))
759 (propertize "+x" 'face
760 'stgit-file-permission-face)))))
761 (t (concat (propertize (format "%o" old-perm)
762 'face 'stgit-file-permission-face)
763 (propertize " -> "
764 'face 'stgit-description-face)
765 (propertize (format "%o" new-perm)
766 'face 'stgit-file-permission-face)))))))
1f60181a 767
413f9909
GH
768(defstruct (stgit-file
769 (:conc-name stgit-file->))
0de6881a
DK
770 old-perm new-perm copy-or-rename cr-score cr-from cr-to status file)
771
ca027a87 772(defun stgit-describe-copy-or-rename (file)
6a73154a
GH
773 (let ((arrow (concat " " (propertize "->" 'face 'stgit-description-face) " "))
774 from to common-head common-tail)
ca027a87
GH
775
776 (when stgit-abbreviate-copies-and-renames
413f9909
GH
777 (setq from (split-string (stgit-file->cr-from file) "/")
778 to (split-string (stgit-file->cr-to file) "/"))
ca027a87
GH
779
780 (while (and from to (cdr from) (cdr to)
781 (string-equal (car from) (car to)))
782 (setq common-head (cons (car from) common-head)
783 from (cdr from)
784 to (cdr to)))
785 (setq common-head (nreverse common-head)
786 from (nreverse from)
787 to (nreverse to))
788 (while (and from to (cdr from) (cdr to)
789 (string-equal (car from) (car to)))
790 (setq common-tail (cons (car from) common-tail)
791 from (cdr from)
792 to (cdr to)))
793 (setq from (nreverse from)
794 to (nreverse to)))
795
796 (if (or common-head common-tail)
797 (concat (if common-head
798 (mapconcat #'identity common-head "/")
799 "")
800 (if common-head "/" "")
801 (propertize "{" 'face 'stgit-description-face)
802 (mapconcat #'identity from "/")
803 arrow
804 (mapconcat #'identity to "/")
805 (propertize "}" 'face 'stgit-description-face)
806 (if common-tail "/" "")
807 (if common-tail
808 (mapconcat #'identity common-tail "/")
809 ""))
413f9909 810 (concat (stgit-file->cr-from file) arrow (stgit-file->cr-to file)))))
ca027a87 811
3164eec6 812(defun stgit-file-pp (file)
43ee50b6
DK
813 (let ((start (point))
814 (spec (format-spec-make
815 ?s (stgit-file-status-code-as-string file)
816 ?m (stgit-file-mode-change-string
413f9909
GH
817 (stgit-file->old-perm file)
818 (stgit-file->new-perm file))
819 ?n (if (stgit-file->copy-or-rename file)
43ee50b6 820 (stgit-describe-copy-or-rename file)
413f9909 821 (stgit-file->file file))
43ee50b6 822 ?c (propertize (stgit-file-type-change-string
413f9909
GH
823 (stgit-file->old-perm file)
824 (stgit-file->new-perm file))
43ee50b6 825 'face 'stgit-description-face))))
da30db2a
GH
826 (stgit-insert-without-trailing-whitespace
827 (format-spec stgit-file-line-format spec))
0de6881a 828 (add-text-properties start (point)
3164eec6
DK
829 (list 'entry-type 'file
830 'file-data file))))
0de6881a 831
7567401c
GH
832(defun stgit-find-copies-harder-diff-arg ()
833 "Return the flag to use with `git-diff' depending on the
b6df231c
GH
834`stgit-find-copies-harder' flag."
835 (if stgit-find-copies-harder "--find-copies-harder" "-C"))
7567401c 836
d9473917
GH
837(defun stgit-insert-ls-files (args file-flag)
838 (let ((start (point)))
839 (apply 'stgit-run-git
840 (append '("ls-files" "--exclude-standard" "-z") args))
841 (goto-char start)
842 (while (looking-at "\\([^\0]*\\)\0")
843 (let ((name-len (- (match-end 0) (match-beginning 0))))
844 (insert ":0 0 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 " file-flag "\0")
845 (forward-char name-len)))))
846
7f972e9b
GH
847(defun stgit-process-files (callback)
848 (goto-char (point-min))
849 (when (looking-at "[0-9A-Fa-f]\\{40\\}\0")
850 (goto-char (match-end 0)))
851 (while (looking-at ":\\([0-7]+\\) \\([0-7]+\\) [0-9A-Fa-f]\\{40\\} [0-9A-Fa-f]\\{40\\} ")
852 (let ((old-perm (string-to-number (match-string 1) 8))
853 (new-perm (string-to-number (match-string 2) 8)))
854 (goto-char (match-end 0))
855 (let ((file
856 (cond ((looking-at
857 "\\([CR]\\)\\([0-9]*\\)\0\\([^\0]*\\)\0\\([^\0]*\\)\0")
858 (let* ((patch-status (stgit-patch->status patch))
859 (file-subexp (if (eq patch-status 'unapplied)
860 3
861 4))
862 (file (match-string file-subexp)))
863 (make-stgit-file
864 :old-perm old-perm
865 :new-perm new-perm
866 :copy-or-rename t
867 :cr-score (string-to-number (match-string 2))
868 :cr-from (match-string 3)
869 :cr-to (match-string 4)
870 :status (stgit-file-status-code
871 (match-string 1))
872 :file file)))
873 ((looking-at "\\([ABD-QS-Z]\\)\0\\([^\0]*\\)\0")
874 (make-stgit-file
875 :old-perm old-perm
876 :new-perm new-perm
877 :copy-or-rename nil
878 :cr-score nil
879 :cr-from nil
880 :cr-to nil
881 :status (stgit-file-status-code
882 (match-string 1))
883 :file (match-string 2))))))
884 (goto-char (match-end 0))
885 (funcall callback file)))))
886
887
0de6881a 888(defun stgit-insert-patch-files (patch)
88134ff7
GH
889 "Expand (show modification of) the patch PATCH after the line
890at point."
d2efc9d5
GH
891 (let* ((patchsym (stgit-patch->name patch))
892 (end (point-marker))
893 (args (list "-z" (stgit-find-copies-harder-diff-arg)))
894 (ewoc (ewoc-create #'stgit-file-pp nil nil t))
895 (show-ignored stgit-show-ignored)
896 (show-unknown stgit-show-unknown))
0434bec1 897 (set-marker-insertion-type end t)
413f9909 898 (setf (stgit-patch->files-ewoc patch) ewoc)
0de6881a 899 (with-temp-buffer
ea305902
GH
900 (let ((standard-output (current-buffer)))
901 (apply 'stgit-run-git
902 (cond ((eq patchsym :work)
0b71b4dc
GH
903 (let (standard-output)
904 (stgit-run-git "update-index" "--refresh"))
ea305902
GH
905 `("diff-files" "-0" ,@args))
906 ((eq patchsym :index)
907 `("diff-index" ,@args "--cached" "HEAD"))
908 (t
909 `("diff-tree" ,@args "-r" ,(stgit-id patchsym)))))
910
911 (when (and (eq patchsym :work))
d2efc9d5 912 (when show-ignored
ea305902 913 (stgit-insert-ls-files '("--ignored" "--others") "I"))
d2efc9d5 914 (when show-unknown
7f972e9b
GH
915 (stgit-insert-ls-files '("--directory" "--no-empty-directory"
916 "--others")
917 "X"))
ea305902
GH
918 (sort-regexp-fields nil ":[^\0]*\0\\([^\0]*\\)\0" "\\1"
919 (point-min) (point-max)))
920
7f972e9b 921 (stgit-process-files (lambda (file) (ewoc-enter-last ewoc file)))
ea305902
GH
922
923 (unless (ewoc-nth ewoc 0)
924 (ewoc-set-hf ewoc ""
925 (concat " "
926 (propertize "<no files>"
927 'face 'stgit-description-face)
928 "\n")))))
0434bec1 929 (goto-char end)))
07f464e0 930
030f0535
GH
931(defun stgit-find-file (&optional other-window)
932 (let* ((file (or (stgit-patched-file-at-point)
933 (error "No file at point")))
413f9909 934 (filename (expand-file-name (stgit-file->file file))))
0de6881a
DK
935 (unless (file-exists-p filename)
936 (error "File does not exist"))
030f0535
GH
937 (funcall (if other-window 'find-file-other-window 'find-file)
938 filename)
413f9909 939 (when (eq (stgit-file->status file) 'unmerged)
030f0535 940 (smerge-mode 1))))
acc5652f 941
afbf766b 942(defun stgit-expand (&optional patches collapse)
fd64ee57 943 "Show the contents of marked patches, or the patch at point.
afbf766b
GH
944
945See also `stgit-collapse'.
946
947Non-interactively, operate on PATCHES, and collapse instead of
948expand if COLLAPSE is not nil."
beac0f14 949 (interactive (list (stgit-patches-marked-or-at-point t)))
9d04c657 950 (stgit-assert-mode)
afbf766b
GH
951 (let ((patches-diff (funcall (if collapse #'intersection #'set-difference)
952 patches stgit-expanded-patches)))
953 (setq stgit-expanded-patches
954 (if collapse
955 (set-difference stgit-expanded-patches patches-diff)
956 (append stgit-expanded-patches patches-diff)))
957 (ewoc-map #'(lambda (patch)
413f9909 958 (memq (stgit-patch->name patch) patches-diff))
afbf766b
GH
959 stgit-ewoc))
960 (move-to-column (stgit-goal-column)))
961
962(defun stgit-collapse (&optional patches)
fd64ee57 963 "Hide the contents of marked patches, or the patch at point.
afbf766b
GH
964
965See also `stgit-expand'."
beac0f14 966 (interactive (list (stgit-patches-marked-or-at-point t)))
9d04c657 967 (stgit-assert-mode)
afbf766b
GH
968 (stgit-expand patches t))
969
50d88c67 970(defun stgit-select-patch ()
98230edd 971 (let ((patchname (stgit-patch-name-at-point)))
afbf766b
GH
972 (stgit-expand (list patchname)
973 (memq patchname stgit-expanded-patches))))
acc5652f 974
7f972e9b
GH
975(defun stgit-expand-directory (file)
976 (let* ((patch (stgit-patch-at-point))
977 (ewoc (stgit-patch->files-ewoc patch))
978 (node (ewoc-locate ewoc))
979 (filename (stgit-file->file file))
980 (start (make-marker))
981 (end (make-marker)))
982
983 (save-excursion
984 (forward-line 1)
985 (set-marker start (point))
986 (set-marker end (point))
987 (set-marker-insertion-type end t))
988
989 (assert (string-match "/$" filename))
990 ;; remove trailing "/"
991 (setf (stgit-file->file file) (substring filename 0 -1))
992 (ewoc-invalidate ewoc node)
993
994 (with-temp-buffer
995 (let ((standard-output (current-buffer)))
996 (stgit-insert-ls-files (list "--directory" "--others"
997 "--no-empty-directory" "--"
998 filename)
999 "X")
1000 (stgit-process-files (lambda (f)
1001 (setq node (ewoc-enter-after ewoc node f))))))
1002
62548eec
GH
1003 (move-to-column (stgit-goal-column))
1004
7f972e9b
GH
1005 (let ((inhibit-read-only t))
1006 (put-text-property start end 'patch-data patch))))
1007
1008(defun stgit-select-file ()
1009 (let* ((file (or (stgit-patched-file-at-point)
1010 (error "No file at point")))
1011 (filename (stgit-file->file file)))
1012 (if (string-match "/$" filename)
1013 (stgit-expand-directory file)
1014 (stgit-find-file))))
1015
378a003d 1016(defun stgit-select ()
da01a29b
GH
1017 "With point on a patch, toggle showing files in the patch.
1018
1019With point on a file, open the associated file. Opens the target
1020file for (applied) copies and renames."
378a003d 1021 (interactive)
9d04c657 1022 (stgit-assert-mode)
50d88c67
DK
1023 (case (get-text-property (point) 'entry-type)
1024 ('patch
1025 (stgit-select-patch))
1026 ('file
7f972e9b 1027 (stgit-select-file))
50d88c67
DK
1028 (t
1029 (error "No patch or file on line"))))
378a003d
GH
1030
1031(defun stgit-find-file-other-window ()
1032 "Open file at point in other window"
1033 (interactive)
9d04c657 1034 (stgit-assert-mode)
030f0535 1035 (stgit-find-file t))
378a003d 1036
d9b954c7
GH
1037(defun stgit-find-file-merge ()
1038 "Open file at point and merge it using `smerge-ediff'."
1039 (interactive)
9d04c657 1040 (stgit-assert-mode)
d9b954c7
GH
1041 (stgit-find-file t)
1042 (smerge-ediff))
1043
83327d53 1044(defun stgit-quit ()
a53347d9 1045 "Hide the stgit buffer."
83327d53 1046 (interactive)
9d04c657 1047 (stgit-assert-mode)
83327d53
GH
1048 (bury-buffer))
1049
0f076fe6 1050(defun stgit-git-status ()
a53347d9 1051 "Show status using `git-status'."
0f076fe6 1052 (interactive)
9d04c657 1053 (stgit-assert-mode)
0f076fe6 1054 (unless (fboundp 'git-status)
df283a8b 1055 (error "The stgit-git-status command requires git-status"))
0f076fe6
GH
1056 (let ((dir default-directory))
1057 (save-selected-window
1058 (pop-to-buffer nil)
1059 (git-status dir))))
1060
58f72f16
GH
1061(defun stgit-goal-column ()
1062 "Return goal column for the current line"
50d88c67
DK
1063 (case (get-text-property (point) 'entry-type)
1064 ('patch 2)
1065 ('file 4)
1066 (t 0)))
58f72f16
GH
1067
1068(defun stgit-next-line (&optional arg)
378a003d 1069 "Move cursor vertically down ARG lines"
58f72f16 1070 (interactive "p")
9d04c657 1071 (stgit-assert-mode)
58f72f16
GH
1072 (next-line arg)
1073 (move-to-column (stgit-goal-column)))
378a003d 1074
58f72f16 1075(defun stgit-previous-line (&optional arg)
378a003d 1076 "Move cursor vertically up ARG lines"
58f72f16 1077 (interactive "p")
9d04c657 1078 (stgit-assert-mode)
58f72f16
GH
1079 (previous-line arg)
1080 (move-to-column (stgit-goal-column)))
378a003d
GH
1081
1082(defun stgit-next-patch (&optional arg)
98230edd 1083 "Move cursor down ARG patches."
378a003d 1084 (interactive "p")
9d04c657 1085 (stgit-assert-mode)
006cd3e5
GH
1086 (unless arg (setq arg 1))
1087 (cond ((< arg 0)
1088 (stgit-previous-patch (- arg)))
1089 ((zerop arg)
1090 (move-to-column (stgit-goal-column)))
1091 (t
1092 (when (stgit-at-header-p)
1093 (ewoc-goto-node stgit-ewoc (ewoc-nth stgit-ewoc 0))
1094 (setq arg (1- arg)))
1095 (ewoc-goto-next stgit-ewoc arg)
1096 (move-to-column goal-column))))
378a003d
GH
1097
1098(defun stgit-previous-patch (&optional arg)
98230edd 1099 "Move cursor up ARG patches."
378a003d 1100 (interactive "p")
9d04c657 1101 (stgit-assert-mode)
006cd3e5
GH
1102 (unless arg (setq arg 1))
1103 (cond ((< arg 0)
1104 (stgit-next-patch (- arg)))
1105 ((zerop arg)
1106 (move-to-column (stgit-goal-column)))
1107 ((stgit-at-header-p)
1108 (goto-char (point-min)))
1109 (t
1110 (let ((opatch (stgit-patch-at-point)))
1111 (ewoc-goto-prev stgit-ewoc arg)
1112 (unless (zerop arg)
1113 (when (eq opatch (stgit-patch-at-point))
1114 (goto-char (point-min)))))
1115 (move-to-column (stgit-goal-column)))))
1116
1117(defun stgit-previous-patch-group (&optional arg)
1118 "Move to the previous group of patches.
1119
1120If ARG is non-nil, do this ARG times. If ARG is negative, move
1121-ARG groups forward instead; cf. `stgit-next-patch-group'."
1122 (interactive "p")
1123 (stgit-assert-mode)
1124 (if (< arg 0)
1125 (stgit-previous-patch-group (- arg))
1126 (while (and (not (bobp))
1127 (> arg 0))
1128 (stgit-previous-patch 1)
1129 (let* ((opoint (point))
1130 (patch (stgit-patch-at-point))
1131 (status (and patch (stgit-patch->status patch))))
1132 (while (and (not (bobp))
1133 (let* ((npatch (stgit-patch-at-point))
1134 (nstatus (and npatch (stgit-patch->status npatch))))
1135 (eq status nstatus)))
1136 (setq opoint (point))
1137 (stgit-previous-patch 1))
1138 (goto-char opoint))
1139 (setq arg (1- arg)))))
1140
1141(defun stgit-next-patch-group (&optional arg)
1142 "Move to the next group of patches.
1143
1144If ARG is non-nil, do this ARG times. If ARG is negative, move
1145-ARG groups backwards instead; cf. `stgit-previous-patch-group'."
1146 (interactive "p")
1147 (stgit-assert-mode)
1148 (if (< arg 0)
1149 (stgit-previous-patch-group (- arg))
1150 (while (and (not (eobp))
1151 (> arg 0))
1152 (let* ((patch (stgit-patch-at-point))
1153 (status (and patch (stgit-patch->status patch))))
1154 (while (and (not (eobp))
1155 (let* ((npatch (stgit-patch-at-point))
1156 (nstatus (and npatch (stgit-patch->status npatch))))
1157 (eq status nstatus)))
1158 (stgit-next-patch 1)))
1159 (setq arg (1- arg)))))
378a003d 1160
56d81fe5
DK
1161(defvar stgit-mode-hook nil
1162 "Run after `stgit-mode' is setup.")
1163
1164(defvar stgit-mode-map nil
1165 "Keymap for StGit major mode.")
1166
1167(unless stgit-mode-map
5038381d
GH
1168 (let ((diff-map (make-sparse-keymap))
1169 (toggle-map (make-sparse-keymap)))
d9b954c7
GH
1170 (mapc (lambda (arg) (define-key diff-map (car arg) (cdr arg)))
1171 '(("b" . stgit-diff-base)
1172 ("c" . stgit-diff-combined)
1173 ("m" . stgit-find-file-merge)
1174 ("o" . stgit-diff-ours)
e02b46e5 1175 ("r" . stgit-diff-range)
d9b954c7 1176 ("t" . stgit-diff-theirs)))
ce3b6130 1177 (mapc (lambda (arg) (define-key toggle-map (car arg) (cdr arg)))
a0045b87
DK
1178 '(("n" . stgit-toggle-patch-names)
1179 ("t" . stgit-toggle-worktree)
bf7e391c 1180 ("h" . stgit-toggle-committed)
d9473917 1181 ("i" . stgit-toggle-ignored)
137b315b
GH
1182 ("u" . stgit-toggle-unknown)
1183 ("s" . stgit-toggle-svn)))
ce3b6130
DK
1184 (setq stgit-mode-map (make-keymap))
1185 (suppress-keymap stgit-mode-map)
1186 (mapc (lambda (arg) (define-key stgit-mode-map (car arg) (cdr arg)))
d11e0621
GH
1187 `((" " . stgit-mark-down)
1188 ("m" . stgit-mark-down)
ce3b6130
DK
1189 ("\d" . stgit-unmark-up)
1190 ("u" . stgit-unmark-down)
1191 ("?" . stgit-help)
1192 ("h" . stgit-help)
1193 ("\C-p" . stgit-previous-line)
1194 ("\C-n" . stgit-next-line)
1195 ([up] . stgit-previous-line)
1196 ([down] . stgit-next-line)
1197 ("p" . stgit-previous-patch)
1198 ("n" . stgit-next-patch)
006cd3e5
GH
1199 ("\M-{" . stgit-previous-patch-group)
1200 ("\M-}" . stgit-next-patch-group)
1201 ([(control up)] . stgit-previous-patch-group)
1202 ([(control down)] . stgit-next-patch-group)
ce3b6130 1203 ("s" . stgit-git-status)
408fa7cb 1204 ("g" . stgit-reload-or-repair)
ce3b6130
DK
1205 ("r" . stgit-refresh)
1206 ("\C-c\C-r" . stgit-rename)
1207 ("e" . stgit-edit)
1208 ("M" . stgit-move-patches)
1209 ("S" . stgit-squash)
1210 ("N" . stgit-new)
2acb7116 1211 ("c" . stgit-new-and-refresh)
e9fdd4ea
GH
1212 ("\C-c\C-c" . stgit-commit)
1213 ("\C-c\C-u" . stgit-uncommit)
1629f59f 1214 ("U" . stgit-revert)
51783171 1215 ("R" . stgit-resolve-file)
ce3b6130 1216 ("\r" . stgit-select)
afbf766b
GH
1217 ("+" . stgit-expand)
1218 ("-" . stgit-collapse)
ce3b6130 1219 ("o" . stgit-find-file-other-window)
dde3ab4d 1220 ("i" . stgit-toggle-index)
ce3b6130
DK
1221 (">" . stgit-push-next)
1222 ("<" . stgit-pop-next)
1223 ("P" . stgit-push-or-pop)
1224 ("G" . stgit-goto)
d9b954c7 1225 ("=" . stgit-diff)
ce3b6130 1226 ("D" . stgit-delete)
b8463f1d 1227 ([?\C-/] . stgit-undo)
ce3b6130 1228 ("\C-_" . stgit-undo)
b8463f1d
GH
1229 ([?\C-c ?\C-/] . stgit-redo)
1230 ("\C-c\C-_" . stgit-redo)
ce3b6130 1231 ("B" . stgit-branch)
380a021f 1232 ("\C-c\C-b" . stgit-rebase)
ce3b6130 1233 ("t" . ,toggle-map)
d9b954c7 1234 ("d" . ,diff-map)
e8faa44b
DK
1235 ("q" . stgit-quit)
1236 ("!" . stgit-execute))))
5038381d
GH
1237
1238 (let ((at-unmerged-file '(let ((file (stgit-patched-file-at-point)))
413f9909 1239 (and file (eq (stgit-file->status file)
5038381d
GH
1240 'unmerged))))
1241 (patch-collapsed-p '(lambda (p) (not (memq p stgit-expanded-patches)))))
1242 (easy-menu-define stgit-menu stgit-mode-map
1243 "StGit Menu"
1244 `("StGit"
1245 ["Reload" stgit-reload-or-repair
1246 :help "Reload StGit status from disk"]
1247 ["Repair" stgit-repair
1248 :keys "\\[universal-argument] \\[stgit-reload-or-repair]"
1249 :help "Repair StGit metadata"]
1250 "-"
1251 ["Undo" stgit-undo t]
1252 ["Redo" stgit-redo t]
1253 "-"
1254 ["Git status" stgit-git-status :active (fboundp 'git-status)]
1255 "-"
1256 ["New patch" stgit-new-and-refresh
1257 :help "Create a new patch from changes in index or work tree"
1258 :active (not (and (stgit-index-empty-p) (stgit-work-tree-empty-p)))]
1259 ["New empty patch" stgit-new
1260 :help "Create a new, empty patch"]
1261 ["(Un)mark patch" stgit-toggle-mark
1262 :label (if (memq (stgit-patch-name-at-point nil t)
1263 stgit-marked-patches)
1264 "Unmark patch" "Mark patch")
1265 :active (stgit-patch-name-at-point nil t)]
1266 ["Expand/collapse patch"
1267 (let ((patches (stgit-patches-marked-or-at-point)))
1268 (if (member-if ,patch-collapsed-p patches)
1269 (stgit-expand patches)
1270 (stgit-collapse patches)))
1271 :label (if (member-if ,patch-collapsed-p
1272 (stgit-patches-marked-or-at-point))
1273 "Expand patches"
1274 "Collapse patches")
1275 :active (stgit-patches-marked-or-at-point)]
1276 ["Edit patch" stgit-edit
1277 :help "Edit patch comment"
1278 :active (stgit-patch-name-at-point nil t)]
1279 ["Rename patch" stgit-rename :active (stgit-patch-name-at-point nil t)]
1280 ["Push/pop patch" stgit-push-or-pop
7c11b754
GH
1281 :label (if (subsetp (stgit-patches-marked-or-at-point nil t)
1282 (stgit-applied-patchsyms t))
1283 "Pop patches" "Push patches")]
beac0f14
GH
1284 ["Delete patches" stgit-delete
1285 :active (stgit-patches-marked-or-at-point nil t)]
5038381d
GH
1286 "-"
1287 ["Move patches" stgit-move-patches
1288 :active stgit-marked-patches
fd64ee57 1289 :help "Move marked patch(es) to point"]
5038381d
GH
1290 ["Squash patches" stgit-squash
1291 :active (> (length stgit-marked-patches) 1)
fd64ee57 1292 :help "Merge marked patches into one"]
5038381d
GH
1293 "-"
1294 ["Refresh top patch" stgit-refresh
1295 :active (not (and (stgit-index-empty-p) (stgit-work-tree-empty-p)))
1296 :help "Refresh the top patch with changes in index or work tree"]
1297 ["Refresh this patch" (stgit-refresh t)
1298 :keys "\\[universal-argument] \\[stgit-refresh]"
fd64ee57 1299 :help "Refresh marked patch with changes in index or work tree"
5038381d
GH
1300 :active (and (not (and (stgit-index-empty-p)
1301 (stgit-work-tree-empty-p)))
1302 (stgit-patch-name-at-point nil t))]
1303 "-"
1304 ["Find file" stgit-select
1305 :active (eq (get-text-property (point) 'entry-type) 'file)]
1306 ["Open file" stgit-find-file-other-window
1307 :active (eq (get-text-property (point) 'entry-type) 'file)]
1308 ["Toggle file index" stgit-toggle-index
1309 :active (and (eq (get-text-property (point) 'entry-type) 'file)
1310 (memq (stgit-patch-name-at-point) '(:work :index)))
1311 :label (if (eq (stgit-patch-name-at-point) :work)
1312 "Move change to index"
1313 "Move change to work tree")]
1314 "-"
1315 ["Show diff" stgit-diff
1316 :active (get-text-property (point) 'entry-type)]
e02b46e5
KW
1317 ["Show diff for range of applied patches" stgit-diff-range
1318 :active (= (length stgit-marked-patches) 1)]
5038381d
GH
1319 ("Merge"
1320 :active (stgit-git-index-unmerged-p)
1321 ["Combined diff" stgit-diff-combined
1322 :active (memq (stgit-patch-name-at-point nil nil) '(:work :index))]
1323 ["Diff against base" stgit-diff-base
1324 :help "Show diff against the common base"
1325 :active (memq (stgit-patch-name-at-point nil nil) '(:work :index))]
1326 ["Diff against ours" stgit-diff-ours
1327 :help "Show diff against our branch"
1328 :active (memq (stgit-patch-name-at-point nil nil) '(:work :index))]
1329 ["Diff against theirs" stgit-diff-theirs
1330 :help "Show diff against their branch"
1331 :active (memq (stgit-patch-name-at-point nil nil) '(:work :index))]
1332 "-"
1333 ["Interactive merge" stgit-find-file-merge
1334 :help "Interactively merge the file"
1335 :active ,at-unmerged-file]
1336 ["Resolve file" stgit-resolve-file
1337 :help "Mark file conflict as resolved"
1338 :active ,at-unmerged-file]
1339 )
1340 "-"
1341 ["Show index & work tree" stgit-toggle-worktree :style toggle
1342 :selected stgit-show-worktree]
1343 ["Show unknown files" stgit-toggle-unknown :style toggle
1344 :selected stgit-show-unknown :active stgit-show-worktree]
1345 ["Show ignored files" stgit-toggle-ignored :style toggle
1346 :selected stgit-show-ignored :active stgit-show-worktree]
a0045b87
DK
1347 ["Show patch names" stgit-toggle-patch-names :style toggle
1348 :selected stgit-show-patch-names]
bf7e391c
GH
1349 ["Show recent commits" stgit-toggle-committed :style toggle
1350 :selected stgit-show-committed]
137b315b
GH
1351 ["Show subversion info" stgit-toggle-svn :style toggle
1352 :selected stgit-show-svn]
5038381d
GH
1353 "-"
1354 ["Switch branches" stgit-branch t
3e528fb0 1355 :help "Switch to or create another branch"]
5038381d
GH
1356 ["Rebase branch" stgit-rebase t
1357 :help "Rebase the current branch"]
06115546
GH
1358 "-"
1359 ["Customize StGit" (customize-group 'stgit)]
5038381d
GH
1360 ))))
1361
1362;; disable tool bar editing buttons
1363(put 'stgit-mode 'mode-class 'special)
56d81fe5
DK
1364
1365(defun stgit-mode ()
1366 "Major mode for interacting with StGit.
fdf5e327
GH
1367
1368Start StGit using \\[stgit].
1369
1370Basic commands:
1371\\<stgit-mode-map>\
1372\\[stgit-help] Show this help text
1373\\[stgit-quit] Hide the StGit buffer
1374\\[describe-bindings] Show all key bindings
1375
1376\\[stgit-reload-or-repair] Reload the StGit buffer
1377\\[universal-argument] \\[stgit-reload-or-repair] Repair StGit metadata
1378
1379\\[stgit-undo] Undo most recent StGit operation
1380\\[stgit-redo] Undo recent undo
1381
1382\\[stgit-git-status] Run `git-status' (if available)
1383
e8faa44b
DK
1384\\[stgit-execute] Run an stg shell command
1385
fdf5e327
GH
1386Movement commands:
1387\\[stgit-previous-line] Move to previous line
1388\\[stgit-next-line] Move to next line
1389\\[stgit-previous-patch] Move to previous patch
1390\\[stgit-next-patch] Move to next patch
006cd3e5
GH
1391\\[stgit-previous-patch-group] Move to previous patch group
1392\\[stgit-next-patch-group] Move to next patch group
fdf5e327 1393
d11e0621 1394\\[stgit-mark-down] Mark patch and move down
fdf5e327
GH
1395\\[stgit-unmark-up] Unmark patch and move up
1396\\[stgit-unmark-down] Unmark patch and move down
1397
1398Commands for patches:
1399\\[stgit-select] Toggle showing changed files in patch
1400\\[stgit-refresh] Refresh patch with changes in index or work tree
1401\\[stgit-diff] Show the patch log and diff
1402
fd64ee57
GH
1403\\[stgit-expand] Show changes in marked patches
1404\\[stgit-collapse] Hide changes in marked patches
afbf766b 1405
2acb7116 1406\\[stgit-new-and-refresh] Create a new patch from index or work tree
c20b20a5
GH
1407\\[stgit-new] Create a new, empty patch
1408
fdf5e327
GH
1409\\[stgit-rename] Rename patch
1410\\[stgit-edit] Edit patch description
1411\\[stgit-delete] Delete patch(es)
1412
1629f59f 1413\\[stgit-revert] Revert all changes in index or work tree
dde3ab4d 1414\\[stgit-toggle-index] Toggle all changes between index and work tree
1629f59f 1415
fdf5e327
GH
1416\\[stgit-push-next] Push next patch onto stack
1417\\[stgit-pop-next] Pop current patch from stack
c20b20a5
GH
1418\\[stgit-push-or-pop] Push or pop marked patches
1419\\[stgit-goto] Make patch at point current by popping or pushing
fdf5e327
GH
1420
1421\\[stgit-squash] Squash (meld together) patches
c20b20a5 1422\\[stgit-move-patches] Move marked patches to point
fdf5e327
GH
1423
1424\\[stgit-commit] Commit patch(es)
1425\\[stgit-uncommit] Uncommit patch(es)
1426
1427Commands for files:
1428\\[stgit-select] Open the file in this window
1429\\[stgit-find-file-other-window] Open the file in another window
1430\\[stgit-diff] Show the file's diff
1431
dde3ab4d 1432\\[stgit-toggle-index] Toggle change between index and work tree
1629f59f 1433\\[stgit-revert] Revert changes to file
fdf5e327
GH
1434
1435Display commands:
a0045b87 1436\\[stgit-toggle-patch-names] Toggle showing patch names
fdf5e327
GH
1437\\[stgit-toggle-worktree] Toggle showing index and work tree
1438\\[stgit-toggle-unknown] Toggle showing unknown files
1439\\[stgit-toggle-ignored] Toggle showing ignored files
bf7e391c 1440\\[stgit-toggle-committed] Toggle showing recent commits
137b315b 1441\\[stgit-toggle-svn] Toggle showing subversion information
fdf5e327
GH
1442
1443Commands for diffs:
1444\\[stgit-diff] Show diff of patch or file
e02b46e5 1445\\[stgit-diff-range] Show diff for range of patches
fdf5e327
GH
1446\\[stgit-diff-base] Show diff against the merge base
1447\\[stgit-diff-ours] Show diff against our branch
1448\\[stgit-diff-theirs] Show diff against their branch
1449
1450 With one prefix argument (e.g., \\[universal-argument] \\[stgit-diff]), \
1451ignore space changes.
1452 With two prefix arguments (e.g., \\[universal-argument] \
1453\\[universal-argument] \\[stgit-diff]), ignore all space changes.
1454
1455Commands for merge conflicts:
1456\\[stgit-find-file-merge] Resolve conflicts using `smerge-ediff'
1457\\[stgit-resolve-file] Mark unmerged file as resolved
1458
1459Commands for branches:
3e528fb0 1460\\[stgit-branch] Switch to or create another branch
380a021f 1461\\[stgit-rebase] Rebase the current branch
fdf5e327
GH
1462
1463Customization variables:
1464`stgit-abbreviate-copies-and-renames'
bc11fb08 1465`stgit-default-show-ignored'
a0045b87 1466`stgit-default-show-patch-names'
bc11fb08 1467`stgit-default-show-unknown'
fdf5e327 1468`stgit-default-show-worktree'
bf7e391c 1469`stgit-default-show-committed'
137b315b 1470`stgit-default-show-svn'
bf7e391c 1471`stgit-default-committed-count'
fdf5e327
GH
1472`stgit-find-copies-harder'
1473`stgit-show-worktree-mode'
1474
1475See also \\[customize-group] for the \"stgit\" group."
56d81fe5
DK
1476 (kill-all-local-variables)
1477 (buffer-disable-undo)
1478 (setq mode-name "StGit"
1479 major-mode 'stgit-mode
1480 goal-column 2)
1481 (use-local-map stgit-mode-map)
a0689e11
GH
1482 (mapc (lambda (x) (set (make-local-variable (car x)) (cdr x)))
1483 `((list-buffers-directory . ,default-directory)
1484 (parse-sexp-lookup-properties . t)
1485 (stgit-expanded-patches . (:work :index))
1486 (stgit-index-node . nil)
1487 (stgit-worktree-node . nil)
1488 (stgit-marked-patches . nil)
137b315b 1489 (stgit-svn-find-rev-hash . ,(make-hash-table :test 'equal))
bf7e391c
GH
1490 (stgit-committed-count . ,stgit-default-committed-count)
1491 (stgit-show-committed . ,stgit-default-show-committed)
a0689e11
GH
1492 (stgit-show-ignored . ,stgit-default-show-ignored)
1493 (stgit-show-patch-names . ,stgit-default-show-patch-names)
137b315b 1494 (stgit-show-svn . ,stgit-default-show-svn)
a0689e11
GH
1495 (stgit-show-unknown . ,stgit-default-show-unknown)
1496 (stgit-show-worktree . ,stgit-default-show-worktree)))
2870f8b8 1497 (set-variable 'truncate-lines 't)
e44674e3
GH
1498 (add-hook 'after-save-hook 'stgit-update-stgit-for-buffer)
1499 (unless stgit-did-advise
1500 (stgit-advise)
1501 (setq stgit-did-advise t))
56d81fe5
DK
1502 (run-hooks 'stgit-mode-hook))
1503
e44674e3
GH
1504(defun stgit-advise-funlist (funlist)
1505 "Add advice to the functions in FUNLIST so we can refresh the
1506stgit buffers as the git status of files change."
1507 (mapc (lambda (sym)
1508 (when (fboundp sym)
1509 (eval `(defadvice ,sym (after stgit-update-stgit-for-buffer)
1510 (stgit-update-stgit-for-buffer t)))
1511 (ad-activate sym)))
1512 funlist))
1513
1514(defun stgit-advise ()
1515 "Add advice to appropriate (non-stgit) git functions so we can
1516refresh the stgit buffers as the git status of files change."
1517 (mapc (lambda (arg)
1518 (let ((feature (car arg))
1519 (funlist (cdr arg)))
1520 (if (featurep feature)
1521 (stgit-advise-funlist funlist)
1522 (add-to-list 'after-load-alist
1523 `(,feature (stgit-advise-funlist
1524 (quote ,funlist)))))))
e9a9f3fa 1525 ;; lists of (<feature> <function> <function> ...) to be advised
e44674e3 1526 '((vc-git vc-git-rename-file vc-git-revert vc-git-register)
e9a9f3fa
GH
1527 (git git-add-file git-checkout git-revert-file git-remove-file)
1528 (dired dired-delete-file))))
e44674e3 1529
455c9f7e
GH
1530(defvar stgit-pending-refresh-buffers nil
1531 "Alist of (cons `buffer' `refresh-index') of buffers that need
1532to be refreshed. `refresh-index' is non-nil if both work tree
1533and index need to be refreshed.")
1534
1535(defun stgit-run-pending-refreshs ()
1536 "Run all pending stgit buffer updates as posted by `stgit-post-refresh'."
1537 (let ((buffers stgit-pending-refresh-buffers)
1538 (stgit-inhibit-messages t))
1539 (setq stgit-pending-refresh-buffers nil)
1540 (while buffers
1541 (let* ((elem (car buffers))
1542 (buffer (car elem))
1543 (refresh-index (cdr elem)))
1544 (when (buffer-name buffer)
1545 (with-current-buffer buffer
1546 (stgit-refresh-worktree)
1547 (when refresh-index (stgit-refresh-index)))))
1548 (setq buffers (cdr buffers)))))
1549
1550(defun stgit-post-refresh (buffer refresh-index)
1551 "Update worktree status in BUFFER when Emacs becomes idle. If
1552REFRESH-INDEX is non-nil, also update the index."
1553 (unless stgit-pending-refresh-buffers
1554 (run-with-idle-timer 0.1 nil 'stgit-run-pending-refreshs))
1555 (let ((elem (assq buffer stgit-pending-refresh-buffers)))
1556 (if elem
1557 ;; if buffer is already present, set its refresh-index flag if
1558 ;; necessary
1559 (when refresh-index
1560 (setcdr elem t))
1561 ;; new entry
1562 (setq stgit-pending-refresh-buffers
1563 (cons (cons buffer refresh-index)
1564 stgit-pending-refresh-buffers)))))
1565
e44674e3 1566(defun stgit-update-stgit-for-buffer (&optional refresh-index)
455c9f7e
GH
1567 "When Emacs becomes idle, refresh worktree status in any
1568`stgit-mode' buffer that shows the status of the current buffer.
e44674e3 1569
455c9f7e 1570If REFRESH-INDEX is non-nil, also update the index."
e9a9f3fa 1571 (let* ((dir (cond ((derived-mode-p 'stgit-status-mode 'dired-mode)
e44674e3
GH
1572 default-directory)
1573 (buffer-file-name
1574 (file-name-directory
1575 (expand-file-name buffer-file-name)))))
1576 (gitdir (and dir (condition-case nil (git-get-top-dir dir)
1577 (error nil))))
b894e680
DK
1578 (buffer (and gitdir (stgit-find-buffer gitdir))))
1579 (when buffer
455c9f7e 1580 (stgit-post-refresh buffer refresh-index))))
b894e680 1581
d51722b7
GH
1582(defun stgit-add-mark (patchsym)
1583 "Mark the patch PATCHSYM."
8036afdd 1584 (setq stgit-marked-patches (cons patchsym stgit-marked-patches)))
6df83d42 1585
d51722b7
GH
1586(defun stgit-remove-mark (patchsym)
1587 "Unmark the patch PATCHSYM."
8036afdd 1588 (setq stgit-marked-patches (delq patchsym stgit-marked-patches)))
6df83d42 1589
e6b1fdae 1590(defun stgit-clear-marks ()
47271f41 1591 "Unmark all patches."
e6b1fdae
DK
1592 (setq stgit-marked-patches '()))
1593
735cb7ec 1594(defun stgit-patch-at-point (&optional cause-error)
2c862b07
DK
1595 (get-text-property (point) 'patch-data))
1596
bf7e391c 1597(defun stgit-patch-name-at-point (&optional cause-error types)
d51722b7 1598 "Return the patch name on the current line as a symbol.
64ada6f5 1599If CAUSE-ERROR is not nil, signal an error if none found.
bf7e391c
GH
1600
1601TYPES controls which types of commits and patches can be returned.
1602If it is t, only allow stgit patches; if 'allow-committed, also
1603allow historical commits; if nil, also allow work tree and index."
2c862b07 1604 (let ((patch (stgit-patch-at-point)))
64ada6f5 1605 (and patch
bf7e391c
GH
1606 (memq (stgit-patch->status patch)
1607 (case types
1608 ((nil) nil)
1609 ((allow-committed) '(work index))
1610 ((t) '(work index committed))
1611 (t (error "Bad value"))))
64ada6f5 1612 (setq patch nil))
2c862b07 1613 (cond (patch
413f9909 1614 (stgit-patch->name patch))
2c862b07
DK
1615 (cause-error
1616 (error "No patch on this line")))))
378a003d 1617
3164eec6
DK
1618(defun stgit-patched-file-at-point ()
1619 (get-text-property (point) 'file-data))
56d81fe5 1620
bf7e391c 1621(defun stgit-patches-marked-or-at-point (&optional cause-error types)
beac0f14
GH
1622 "Return the symbols of the marked patches, or the patch on the current line.
1623If CAUSE-ERRROR is not nil, signal an error if none found.
bf7e391c
GH
1624
1625TYPES controls which types of commits and patches can be returned.
1626If it is t, only allow stgit patches; if 'allow-committed, also
1627allow historical commits; if nil, also allow work tree and index."
7755d7f1 1628 (if stgit-marked-patches
d51722b7 1629 stgit-marked-patches
bf7e391c 1630 (let ((patch (stgit-patch-name-at-point nil types)))
beac0f14
GH
1631 (cond (patch (list patch))
1632 (cause-error (error "No patches marked or at this line"))
1633 (t nil)))))
7755d7f1 1634
a9089e68 1635(defun stgit-goto-patch (patchsym &optional file)
2570f6e5
GH
1636 "Move point to the line containing patch PATCHSYM and return non-nil.
1637If that patch cannot be found, do nothing and return nil.
a9089e68
GH
1638
1639If the patch was found and FILE is not nil, instead move to that
1640file's line. If FILE cannot be found, stay on the line of
1641PATCHSYM."
f9b82d36 1642 (let ((node (ewoc-nth stgit-ewoc 0)))
413f9909 1643 (while (and node (not (eq (stgit-patch->name (ewoc-data node))
f9b82d36
DK
1644 patchsym)))
1645 (setq node (ewoc-next stgit-ewoc node)))
a9089e68 1646 (when (and node file)
413f9909 1647 (let* ((file-ewoc (stgit-patch->files-ewoc (ewoc-data node)))
a9089e68 1648 (file-node (ewoc-nth file-ewoc 0)))
ea696de9
GH
1649 (while (and file-node
1650 (not (equal (stgit-file->file (ewoc-data file-node))
1651 file)))
a9089e68
GH
1652 (setq file-node (ewoc-next file-ewoc file-node)))
1653 (when file-node
1654 (ewoc-goto-node file-ewoc file-node)
1655 (move-to-column (stgit-goal-column))
1656 (setq node nil))))
f9b82d36
DK
1657 (when node
1658 (ewoc-goto-node stgit-ewoc node)
d51722b7 1659 (move-to-column goal-column))))
56d81fe5 1660
1c2426dc 1661(defun stgit-init ()
a53347d9 1662 "Run stg init."
1c2426dc 1663 (interactive)
9d04c657 1664 (stgit-assert-mode)
1c2426dc 1665 (stgit-capture-output nil
b0424080 1666 (stgit-run "init"))
1f0bf00f 1667 (stgit-reload))
1c2426dc 1668
d11e0621
GH
1669(defun stgit-toggle-mark ()
1670 "Toggle mark on the patch under point."
1671 (interactive)
1672 (stgit-assert-mode)
1673 (if (memq (stgit-patch-name-at-point t t) stgit-marked-patches)
1674 (stgit-unmark)
1675 (stgit-mark)))
1676
6df83d42 1677(defun stgit-mark ()
a53347d9 1678 "Mark the patch under point."
6df83d42 1679 (interactive)
9d04c657 1680 (stgit-assert-mode)
8036afdd 1681 (let* ((node (ewoc-locate stgit-ewoc))
bf7e391c
GH
1682 (patch (ewoc-data node)))
1683 (case (stgit-patch->status patch)
1684 (work (error "Cannot mark the work tree"))
1685 (index (error "Cannot mark the index"))
1686 (committed (error "Cannot mark a committed patch")))
413f9909 1687 (stgit-add-mark (stgit-patch->name patch))
d11e0621
GH
1688 (let ((column (current-column)))
1689 (ewoc-invalidate stgit-ewoc node)
1690 (move-to-column column))))
1691
1692(defun stgit-mark-down ()
1693 "Mark the patch under point and move to the next patch."
1694 (interactive)
1695 (stgit-mark)
378a003d 1696 (stgit-next-patch))
6df83d42 1697
d11e0621
GH
1698(defun stgit-unmark ()
1699 "Remove mark from the patch on the current line."
6df83d42 1700 (interactive)
9d04c657 1701 (stgit-assert-mode)
8036afdd
DK
1702 (let* ((node (ewoc-locate stgit-ewoc))
1703 (patch (ewoc-data node)))
413f9909 1704 (stgit-remove-mark (stgit-patch->name patch))
d11e0621
GH
1705 (let ((column (current-column)))
1706 (ewoc-invalidate stgit-ewoc node)
1707 (move-to-column column))))
1708
1709(defun stgit-unmark-up ()
1710 "Remove mark from the patch on the previous line."
1711 (interactive)
1712 (stgit-assert-mode)
1713 (stgit-previous-patch)
1714 (stgit-unmark))
9b151b27
GH
1715
1716(defun stgit-unmark-down ()
a53347d9 1717 "Remove mark from the patch on the current line."
9b151b27 1718 (interactive)
9d04c657 1719 (stgit-assert-mode)
d11e0621 1720 (stgit-unmark)
1288eda2 1721 (stgit-next-patch))
6df83d42 1722
56d81fe5 1723(defun stgit-rename (name)
018fa1ac 1724 "Rename the patch under point to NAME."
64ada6f5
GH
1725 (interactive (list
1726 (read-string "Patch name: "
1727 (symbol-name (stgit-patch-name-at-point t t)))))
9d04c657 1728 (stgit-assert-mode)
64ada6f5 1729 (let ((old-patchsym (stgit-patch-name-at-point t t)))
56d81fe5 1730 (stgit-capture-output nil
69db9714 1731 (stgit-run "rename" "--" old-patchsym name))
d51722b7
GH
1732 (let ((name-sym (intern name)))
1733 (when (memq old-patchsym stgit-expanded-patches)
378a003d 1734 (setq stgit-expanded-patches
6a73154a 1735 (cons name-sym (delq old-patchsym stgit-expanded-patches))))
d51722b7 1736 (when (memq old-patchsym stgit-marked-patches)
378a003d 1737 (setq stgit-marked-patches
6a73154a 1738 (cons name-sym (delq old-patchsym stgit-marked-patches))))
d51722b7
GH
1739 (stgit-reload)
1740 (stgit-goto-patch name-sym))))
56d81fe5 1741
408fa7cb
GH
1742(defun stgit-reload-or-repair (repair)
1743 "Update the contents of the StGit buffer (`stgit-reload').
1744
1745With a prefix argument, repair the StGit metadata if the branch
1746was modified with git commands (`stgit-repair')."
1747 (interactive "P")
9d04c657 1748 (stgit-assert-mode)
408fa7cb
GH
1749 (if repair
1750 (stgit-repair)
1751 (stgit-reload)))
1752
26201d96 1753(defun stgit-repair ()
a53347d9 1754 "Run stg repair."
26201d96 1755 (interactive)
9d04c657 1756 (stgit-assert-mode)
26201d96 1757 (stgit-capture-output nil
b0424080 1758 (stgit-run "repair"))
1f0bf00f 1759 (stgit-reload))
26201d96 1760
3e528fb0
GH
1761(defun stgit-available-branches (&optional all)
1762 "Returns a list of the names of the available stg branches as strings.
1763
1764If ALL is not nil, also return non-stgit branches."
adeef6bc
GH
1765 (let ((output (with-output-to-string
1766 (stgit-run "branch" "--list")))
3e528fb0
GH
1767 (pattern (format "^>?\\s-+%c\\s-+\\(\\S-+\\)"
1768 (if all ?. ?s)))
adeef6bc
GH
1769 (start 0)
1770 result)
3e528fb0 1771 (while (string-match pattern output start)
adeef6bc
GH
1772 (setq result (cons (match-string 1 output) result))
1773 (setq start (match-end 0)))
1774 result))
1775
1776(defun stgit-branch (branch)
3e528fb0 1777 "Switch to or create branch BRANCH."
adeef6bc
GH
1778 (interactive (list (completing-read "Switch to branch: "
1779 (stgit-available-branches))))
9d04c657 1780 (stgit-assert-mode)
3e528fb0
GH
1781 (when (cond ((equal branch (stgit-current-branch))
1782 (error "Branch is already current"))
1783 ((member branch (stgit-available-branches t))
1784 (stgit-capture-output nil (stgit-run "branch" "--" branch))
1785 t)
1786 ((not (string-match stgit-allowed-branch-name-re branch))
1787 (error "Invalid branch name"))
1788 ((yes-or-no-p (format "Create branch \"%s\"? " branch))
84e1850a
GH
1789 (let ((branch-point (completing-read
1790 "Branch from (default current branch): "
1791 (stgit-available-branches))))
1792 (stgit-capture-output nil
1793 (apply 'stgit-run
1794 `("branch" "--create" "--"
1795 ,branch
1796 ,@(unless (zerop (length branch-point))
1797 (list branch-point)))))
1798 t)))
3e528fb0 1799 (stgit-reload)))
adeef6bc 1800
380a021f
GH
1801(defun stgit-available-refs (&optional omit-stgit)
1802 "Returns a list of the available git refs.
1803If OMIT-STGIT is not nil, filter out \"resf/heads/*.stgit\"."
1804 (let* ((output (with-output-to-string
1805 (stgit-run-git-silent "for-each-ref" "--format=%(refname)"
1806 "refs/tags" "refs/heads"
1807 "refs/remotes")))
1808 (result (split-string output "\n" t)))
1809 (mapcar (lambda (s)
1810 (if (string-match "^refs/\\(heads\\|tags\\|remotes\\)/" s)
1811 (substring s (match-end 0))
1812 s))
1813 (if omit-stgit
1814 (delete-if (lambda (s)
1815 (string-match "^refs/heads/.*\\.stgit$" s))
1816 result)
1817 result))))
1818
d6e17ce0
GH
1819(defun stgit-parent-branch ()
1820 "Return the parent branch of the current stg branch as per
1821git-config setting branch.<branch>.stgit.parentbranch."
1822 (let ((output (with-output-to-string
1823 (stgit-run-git-silent "config"
1824 (format "branch.%s.stgit.parentbranch"
1825 (stgit-current-branch))))))
1826 (when (string-match ".*" output)
1827 (match-string 0 output))))
1828
380a021f 1829(defun stgit-rebase (new-base)
d6e17ce0
GH
1830 "Rebase the current branch to NEW-BASE.
1831
1832Interactively, first ask which branch to rebase to. Defaults to
1833what git-config branch.<branch>.stgit.parentbranch is set to."
380a021f 1834 (interactive (list (completing-read "Rebase to: "
d6e17ce0
GH
1835 (stgit-available-refs t)
1836 nil nil
1837 (stgit-parent-branch))))
9d04c657 1838 (stgit-assert-mode)
69db9714 1839 (stgit-capture-output nil (stgit-run "rebase" "--" new-base))
380a021f
GH
1840 (stgit-reload))
1841
41c1c59c
GH
1842(defun stgit-commit (count)
1843 "Run stg commit on COUNT commits.
e552cb5f
GH
1844Interactively, the prefix argument is used as COUNT.
1845A negative COUNT will uncommit instead."
41c1c59c 1846 (interactive "p")
9d04c657 1847 (stgit-assert-mode)
e552cb5f
GH
1848 (if (< count 0)
1849 (stgit-uncommit (- count))
1850 (stgit-capture-output nil (stgit-run "commit" "-n" count))
1851 (stgit-reload)))
1852
1853(defun stgit-uncommit (count)
1854 "Run stg uncommit on COUNT commits.
1855Interactively, the prefix argument is used as COUNT.
1856A negative COUNT will commit instead."
1857 (interactive "p")
9d04c657 1858 (stgit-assert-mode)
e552cb5f
GH
1859 (if (< count 0)
1860 (stgit-commit (- count))
1861 (stgit-capture-output nil (stgit-run "uncommit" "-n" count))
1862 (stgit-reload)))
c4aad9a7 1863
556345d3
GH
1864(defun stgit-neighbour-file ()
1865 "Return the file name of the next file after point, or the
1866previous file if point is at the last file within a patch."
1867 (let ((old-point (point))
1868 neighbour-file)
1869 (and (zerop (forward-line 1))
1870 (let ((f (stgit-patched-file-at-point)))
413f9909 1871 (and f (setq neighbour-file (stgit-file->file f)))))
556345d3
GH
1872 (goto-char old-point)
1873 (unless neighbour-file
1874 (and (zerop (forward-line -1))
1875 (let ((f (stgit-patched-file-at-point)))
413f9909 1876 (and f (setq neighbour-file (stgit-file->file f)))))
556345d3
GH
1877 (goto-char old-point))
1878 neighbour-file))
1879
3959a095
GH
1880(defun stgit-revert-file ()
1881 "Revert the file at point, which must be in the index or the
1882working tree."
1883 (interactive)
9d04c657 1884 (stgit-assert-mode)
3959a095
GH
1885 (let* ((patched-file (or (stgit-patched-file-at-point)
1886 (error "No file on the current line")))
1887 (patch-name (stgit-patch-name-at-point))
413f9909
GH
1888 (file-status (stgit-file->status patched-file))
1889 (rm-file (cond ((stgit-file->copy-or-rename patched-file)
1890 (stgit-file->cr-to patched-file))
3959a095 1891 ((eq file-status 'add)
413f9909 1892 (stgit-file->file patched-file))))
3959a095 1893 (co-file (cond ((eq file-status 'rename)
413f9909 1894 (stgit-file->cr-from patched-file))
3959a095 1895 ((not (memq file-status '(copy add)))
413f9909 1896 (stgit-file->file patched-file))))
556345d3 1897 (next-file (stgit-neighbour-file)))
3959a095
GH
1898
1899 (unless (memq patch-name '(:work :index))
1900 (error "No index or working tree file on this line"))
1901
d9473917
GH
1902 (when (eq file-status 'ignore)
1903 (error "Cannot revert ignored files"))
1904
1905 (when (eq file-status 'unknown)
1906 (error "Cannot revert unknown files"))
1907
3959a095
GH
1908 (let ((nfiles (+ (if rm-file 1 0) (if co-file 1 0))))
1909 (when (yes-or-no-p (format "Revert %d file%s? "
1910 nfiles
1911 (if (= nfiles 1) "" "s")))
1912 (stgit-capture-output nil
1913 (when rm-file
1914 (stgit-run-git "rm" "-f" "-q" "--" rm-file))
1915 (when co-file
1916 (stgit-run-git "checkout" "HEAD" co-file)))
556345d3
GH
1917 (stgit-reload)
1918 (stgit-goto-patch patch-name next-file)))))
1629f59f
GH
1919
1920(defun stgit-revert ()
1921 "Revert the change at point, which must be the index, the work
1922tree, or a single change in either."
1923 (interactive)
9d04c657 1924 (stgit-assert-mode)
1629f59f
GH
1925 (let ((patched-file (stgit-patched-file-at-point)))
1926 (if patched-file
1927 (stgit-revert-file)
1928 (let* ((patch-name (or (stgit-patch-name-at-point)
1929 (error "No patch or file at point")))
1930 (patch-desc (case patch-name
1931 (:index "index")
1932 (:work "work tree")
1933 (t (error (substitute-command-keys
1934 "Use \\[stgit-delete] to delete a patch"))))))
1935 (when (if (eq patch-name :work)
1936 (stgit-work-tree-empty-p)
1937 (stgit-index-empty-p))
1938 (error (format "There are no changes in the %s to revert"
1939 patch-desc)))
1940 (and (eq patch-name :index)
1941 (not (stgit-work-tree-empty-p))
1942 (error "Cannot revert index as work tree contains unstaged changes"))
1943
1944 (when (yes-or-no-p (format "Revert all changes in the %s? "
1945 patch-desc))
1946 (if (eq patch-name :index)
1947 (stgit-run-git-silent "reset" "--hard" "-q")
1948 (stgit-run-git-silent "checkout" "--" "."))
1949 (stgit-refresh-index)
1950 (stgit-refresh-worktree)
1951 (stgit-goto-patch patch-name))))))
3959a095 1952
51783171
GH
1953(defun stgit-resolve-file ()
1954 "Resolve conflict in the file at point."
1955 (interactive)
9d04c657 1956 (stgit-assert-mode)
51783171
GH
1957 (let* ((patched-file (stgit-patched-file-at-point))
1958 (patch (stgit-patch-at-point))
413f9909
GH
1959 (patch-name (and patch (stgit-patch->name patch)))
1960 (status (and patched-file (stgit-file->status patched-file))))
51783171
GH
1961
1962 (unless (memq patch-name '(:work :index))
1963 (error "No index or working tree file on this line"))
1964
1965 (unless (eq status 'unmerged)
1966 (error "No conflict to resolve at the current line"))
1967
1968 (stgit-capture-output nil
413f9909 1969 (stgit-move-change-to-index (stgit-file->file patched-file)))
51783171
GH
1970
1971 (stgit-reload)))
1972
d47ee133
GH
1973(defun stgit-push-or-pop-patches (do-push npatches)
1974 "Push (if DO-PUSH is not nil) or pop (if DO-PUSH is nil)
1975NPATCHES patches, or all patches if NPATCHES is t."
1976 (stgit-assert-mode)
1977 (stgit-capture-output nil
1978 (apply 'stgit-run
1979 (if do-push "push" "pop")
1980 (if (eq npatches t)
1981 '("--all")
1982 (list "-n" npatches))))
1983 (stgit-reload)
1984 (stgit-refresh-git-status))
1985
0b661144
DK
1986(defun stgit-push-next (npatches)
1987 "Push the first unapplied patch.
1988With numeric prefix argument, push that many patches."
1989 (interactive "p")
d47ee133 1990 (stgit-push-or-pop-patches t npatches))
56d81fe5 1991
0b661144
DK
1992(defun stgit-pop-next (npatches)
1993 "Pop the topmost applied patch.
d47ee133
GH
1994With numeric prefix argument, pop that many patches.
1995
1996If NPATCHES is t, pop all patches."
0b661144 1997 (interactive "p")
d47ee133 1998 (stgit-push-or-pop-patches nil npatches))
56d81fe5 1999
7c11b754
GH
2000(defun stgit-applied-patches (&optional only-patches)
2001 "Return a list of the applied patches.
2002
2003If ONLY-PATCHES is not nil, exclude index and work tree."
2004 (let ((states (if only-patches
2005 '(applied top)
2006 '(applied top index work)))
2007 result)
9aa61946
GH
2008 (ewoc-map (lambda (patch)
2009 (when (memq (stgit-patch->status patch) states)
2010 (setq result (cons patch result)))
2011 nil)
7c11b754
GH
2012 stgit-ewoc)
2013 result))
2014
2015(defun stgit-applied-patchsyms (&optional only-patches)
2016 "Return a list of the symbols of the applied patches.
2017
2018If ONLY-PATCHES is not nil, exclude index and work tree."
413f9909 2019 (mapcar #'stgit-patch->name (stgit-applied-patches only-patches)))
f9182fca
KH
2020
2021(defun stgit-push-or-pop ()
7c11b754 2022 "Push or pop the marked patches."
f9182fca 2023 (interactive)
9d04c657 2024 (stgit-assert-mode)
7c11b754
GH
2025 (let* ((patchsyms (stgit-patches-marked-or-at-point t t))
2026 (applied-syms (stgit-applied-patchsyms t))
2027 (unapplied (set-difference patchsyms applied-syms)))
f9182fca 2028 (stgit-capture-output nil
7c11b754
GH
2029 (apply 'stgit-run
2030 (if unapplied "push" "pop")
2031 "--"
2032 (stgit-sort-patches (if unapplied unapplied patchsyms)))))
2033 (stgit-reload))
f9182fca 2034
006cd3e5
GH
2035(defun stgit-at-header-p ()
2036 "Return non-nil if point is in the header area above all patches."
2037 (not (previous-single-property-change (point) 'patch-data)))
2038
2039(defun stgit-at-footer-p ()
2040 "Return non-nil if point is in the footer area below all patches."
2041 (not (next-single-property-change (point) 'patch-data)))
2042
d47ee133 2043(defun stgit-goto-target ()
bf7e391c 2044 "Return the goto target at point: a patchsym, :top,
d47ee133 2045or :bottom."
bf7e391c
GH
2046 (let ((patch (stgit-patch-at-point)))
2047 (cond (patch
2048 (case (stgit-patch->status patch)
2049 ((work index) nil)
2050 ((committed) :bottom)
2051 (t (stgit-patch->name patch))))
006cd3e5 2052 ((stgit-at-footer-p)
d47ee133 2053 :top)
006cd3e5 2054 ((stgit-at-header-p)
d47ee133
GH
2055 :bottom))))
2056
c7adf5ef 2057(defun stgit-goto ()
48d0a850
GH
2058 "Go to the patch on the current line.
2059
d47ee133
GH
2060Push or pop patches to make this patch topmost. Push or pop all
2061patches if used on a line after or before all patches."
c7adf5ef 2062 (interactive)
9d04c657 2063 (stgit-assert-mode)
d47ee133
GH
2064 (let ((patchsym (stgit-goto-target)))
2065 (unless patchsym
2066 (error "No patch to go to on this line"))
2067 (case patchsym
2068 (:top (stgit-push-or-pop-patches t t))
2069 (:bottom (stgit-push-or-pop-patches nil t))
2070 (t (stgit-capture-output nil
69db9714 2071 (stgit-run "goto" "--" patchsym))
d47ee133 2072 (stgit-reload)))))
c7adf5ef 2073
d51722b7 2074(defun stgit-id (patchsym)
50d88c67
DK
2075 "Return the git commit id for PATCHSYM.
2076If PATCHSYM is a keyword, returns PATCHSYM unmodified."
2077 (if (keywordp patchsym)
2078 patchsym
2079 (let ((result (with-output-to-string
69db9714 2080 (stgit-run-silent "id" "--" patchsym))))
50d88c67
DK
2081 (unless (string-match "^\\([0-9A-Fa-f]\\{40\\}\\)$" result)
2082 (error "Cannot find commit id for %s" patchsym))
2083 (match-string 1 result))))
378a003d 2084
015a6dfa
KW
2085(defun stgit-whitespace-diff-arg (arg)
2086 (when (numberp arg)
2087 (cond ((> arg 4) "--ignore-all-space")
2088 ((> arg 1) "--ignore-space-change"))))
2089
1aece5c0 2090(defun stgit-show-patch (unmerged-stage ignore-whitespace)
d9b954c7
GH
2091 "Show the patch on the current line.
2092
2093UNMERGED-STAGE is the argument to `git-diff' that that selects
2094which stage to diff against in the case of unmerged files."
015a6dfa 2095 (let ((space-arg (stgit-whitespace-diff-arg ignore-whitespace))
1aece5c0
GH
2096 (patch-name (stgit-patch-name-at-point t)))
2097 (stgit-capture-output "*StGit patch*"
2098 (case (get-text-property (point) 'entry-type)
2099 ('file
2100 (let* ((patched-file (stgit-patched-file-at-point))
2101 (patch-id (let ((id (stgit-id patch-name)))
2102 (if (and (eq id :index)
413f9909 2103 (eq (stgit-file->status patched-file)
1aece5c0
GH
2104 'unmerged))
2105 :work
2106 id)))
2107 (args (append (and space-arg (list space-arg))
413f9909 2108 (and (stgit-file->cr-from patched-file)
1aece5c0
GH
2109 (list (stgit-find-copies-harder-diff-arg)))
2110 (cond ((eq patch-id :index)
2111 '("--cached"))
2112 ((eq patch-id :work)
2113 (list unmerged-stage))
2114 (t
2115 (list (concat patch-id "^") patch-id)))
108e5c89
GH
2116 (and (eq (stgit-file->status patched-file) 'copy)
2117 '("--diff-filter=C"))
1aece5c0 2118 '("--")
413f9909
GH
2119 (if (stgit-file->copy-or-rename patched-file)
2120 (list (stgit-file->cr-from patched-file)
2121 (stgit-file->cr-to patched-file))
2122 (list (stgit-file->file patched-file))))))
1aece5c0
GH
2123 (apply 'stgit-run-git "diff" args)))
2124 ('patch
2125 (let* ((patch-id (stgit-id patch-name)))
2126 (if (or (eq patch-id :index) (eq patch-id :work))
2127 (apply 'stgit-run-git "diff"
2128 (stgit-find-copies-harder-diff-arg)
2129 (append (and space-arg (list space-arg))
2130 (if (eq patch-id :index)
2131 '("--cached")
2132 (list unmerged-stage))))
a27d0b75
GH
2133 (let ((args (append '("show" "-O" "--patch-with-stat")
2134 `("-O" ,(stgit-find-copies-harder-diff-arg))
1aece5c0 2135 (and space-arg (list "-O" space-arg))
69db9714 2136 '("--")
1aece5c0
GH
2137 (list (stgit-patch-name-at-point)))))
2138 (apply 'stgit-run args)))))
6a73154a
GH
2139 (t
2140 (error "No patch or file at point")))
1aece5c0
GH
2141 (with-current-buffer standard-output
2142 (goto-char (point-min))
2143 (diff-mode)))))
2144
2145(defmacro stgit-define-diff (name diff-arg &optional unmerged-action)
2146 `(defun ,name (&optional ignore-whitespace)
2147 ,(format "Show the patch on the current line.
2148
2149%sWith a prefix argument, ignore whitespace. With a prefix argument
2150greater than four (e.g., \\[universal-argument] \
2151\\[universal-argument] \\[%s]), ignore all whitespace."
2152 (if unmerged-action
2153 (format "For unmerged files, %s.\n\n" unmerged-action)
2154 "")
2155 name)
2156 (interactive "p")
9d04c657 2157 (stgit-assert-mode)
1aece5c0
GH
2158 (stgit-show-patch ,diff-arg ignore-whitespace)))
2159
2160(stgit-define-diff stgit-diff
2161 "--ours" nil)
2162(stgit-define-diff stgit-diff-ours
2163 "--ours"
2164 "diff against our branch")
2165(stgit-define-diff stgit-diff-theirs
2166 "--theirs"
2167 "diff against their branch")
2168(stgit-define-diff stgit-diff-base
2169 "--base"
2170 "diff against the merge base")
2171(stgit-define-diff stgit-diff-combined
2172 "--cc"
2173 "show a combined diff")
d9b954c7 2174
e02b46e5
KW
2175(defun stgit-diff-range (&optional ignore-whitespace)
2176 "Show diff for the range of patches between point and the marked patch.
2177
2178With a prefix argument, ignore whitespace. With a prefix argument
2179greater than four (e.g., \\[universal-argument] \
2180\\[universal-argument] \\[stgit-diff-range]), ignore all whitespace."
2181 (interactive "p")
2182 (stgit-assert-mode)
2183 (unless (= (length stgit-marked-patches) 1)
2184 (error "Need exactly one patch marked"))
bf7e391c
GH
2185 (let* ((patches (stgit-sort-patches
2186 (cons (stgit-patch-name-at-point t 'allow-committed)
2187 stgit-marked-patches)
2188 t))
e02b46e5
KW
2189 (first-patch (car patches))
2190 (second-patch (if (cdr patches) (cadr patches) first-patch))
2191 (whitespace-arg (stgit-whitespace-diff-arg ignore-whitespace))
2192 (applied (stgit-applied-patchsyms t)))
2193 (unless (and (memq first-patch applied) (memq second-patch applied))
2194 (error "Can only show diff range for applied patches"))
2195 (stgit-capture-output (format "*StGit diff %s..%s*"
2196 first-patch second-patch)
de8efe1e
GH
2197 (apply 'stgit-run-git
2198 "diff" "--patch-with-stat"
2199 (stgit-find-copies-harder-diff-arg)
2200 (append (and whitespace-arg (list whitespace-arg))
2201 (list (format "%s^" (stgit-id first-patch))
2202 (stgit-id second-patch))))
e02b46e5
KW
2203 (with-current-buffer standard-output
2204 (goto-char (point-min))
2205 (diff-mode)))))
2206
f87c2e22
GH
2207(defun stgit-move-change-to-index (file &optional force)
2208 "Copies the work tree state of FILE to index, using git add or git rm.
2209
2210If FORCE is not nil, use --force."
306b37a6
GH
2211 (let ((op (if (or (file-exists-p file) (file-symlink-p file))
2212 '("add") '("rm" "-q"))))
37cb5766 2213 (stgit-capture-output "*git output*"
f87c2e22
GH
2214 (apply 'stgit-run-git (append op (and force '("--force"))
2215 '("--") (list file))))))
37cb5766 2216
fd9fe574 2217(defun stgit-remove-change-from-index (file)
37cb5766
DK
2218 "Unstages the change in FILE from the index"
2219 (stgit-capture-output "*git output*"
2220 (stgit-run-git "reset" "-q" "--" file)))
2221
dde3ab4d
GH
2222(defun stgit-git-index-unmerged-p ()
2223 (let (result)
2224 (with-output-to-string
2225 (setq result (not (zerop (stgit-run-git-silent "diff-index" "--cached"
2226 "--diff-filter=U"
2227 "--quiet" "HEAD")))))
2228 result))
2229
37cb5766 2230(defun stgit-file-toggle-index ()
a9089e68
GH
2231 "Move modified file in or out of the index.
2232
2233Leaves the point where it is, but moves the mark to where the
2234file ended up. You can then jump to the file with \
2235\\[exchange-point-and-mark]."
37cb5766 2236 (interactive)
9d04c657 2237 (stgit-assert-mode)
612f999a
GH
2238 (let* ((patched-file (or (stgit-patched-file-at-point)
2239 (error "No file on the current line")))
413f9909 2240 (patched-status (stgit-file->status patched-file)))
612f999a 2241 (when (eq patched-status 'unmerged)
51783171 2242 (error (substitute-command-keys "Use \\[stgit-resolve-file] to move an unmerged file to the index")))
a9089e68 2243 (let* ((patch (stgit-patch-at-point))
413f9909 2244 (patch-name (stgit-patch->name patch))
612f999a 2245 (mark-file (if (eq patched-status 'rename)
413f9909
GH
2246 (stgit-file->cr-to patched-file)
2247 (stgit-file->file patched-file)))
612f999a 2248 (point-file (if (eq patched-status 'rename)
413f9909 2249 (stgit-file->cr-from patched-file)
6a73154a 2250 (stgit-neighbour-file))))
a9089e68 2251
37cb5766 2252 (cond ((eq patch-name :work)
413f9909 2253 (stgit-move-change-to-index (stgit-file->file patched-file)
f87c2e22 2254 (eq patched-status 'ignore)))
37cb5766 2255 ((eq patch-name :index)
413f9909 2256 (stgit-remove-change-from-index (stgit-file->file patched-file)))
37cb5766 2257 (t
612f999a 2258 (error "Can only move files between working tree and index")))
a9089e68
GH
2259 (stgit-refresh-worktree)
2260 (stgit-refresh-index)
612f999a 2261 (stgit-goto-patch (if (eq patch-name :index) :work :index) mark-file)
a9089e68 2262 (push-mark nil t t)
612f999a 2263 (stgit-goto-patch patch-name point-file))))
37cb5766 2264
dde3ab4d
GH
2265(defun stgit-toggle-index ()
2266 "Move change in or out of the index.
2267
2268Works on index and work tree, as well as files in either.
2269
2270Leaves the point where it is, but moves the mark to where the
2271file ended up. You can then jump to the file with \
2272\\[exchange-point-and-mark]."
2273 (interactive)
9d04c657 2274 (stgit-assert-mode)
dde3ab4d
GH
2275 (if (stgit-patched-file-at-point)
2276 (stgit-file-toggle-index)
2277 (let ((patch-name (stgit-patch-name-at-point)))
2278 (unless (memq patch-name '(:index :work))
2279 (error "Can only move changes between working tree and index"))
2280 (when (stgit-git-index-unmerged-p)
2281 (error "Resolve unmerged changes with \\[stgit-resolve-file] first"))
2282 (if (if (eq patch-name :index)
2283 (stgit-index-empty-p)
2284 (stgit-work-tree-empty-p))
2285 (message "No changes to be moved")
2286 (stgit-capture-output nil
2287 (if (eq patch-name :work)
2288 (stgit-run-git "add" "--update")
2289 (stgit-run-git "reset" "--mixed" "-q")))
2290 (stgit-refresh-worktree)
2291 (stgit-refresh-index))
2292 (stgit-goto-patch (if (eq patch-name :index) :work :index)))))
2293
0bca35c8 2294(defun stgit-edit ()
a53347d9 2295 "Edit the patch on the current line."
0bca35c8 2296 (interactive)
9d04c657 2297 (stgit-assert-mode)
64ada6f5 2298 (let ((patchsym (stgit-patch-name-at-point t t))
0780be79 2299 (edit-buf (get-buffer-create "*StGit edit*"))
0bca35c8
DK
2300 (dir default-directory))
2301 (log-edit 'stgit-confirm-edit t nil edit-buf)
d51722b7 2302 (set (make-local-variable 'stgit-edit-patchsym) patchsym)
0bca35c8
DK
2303 (setq default-directory dir)
2304 (let ((standard-output edit-buf))
655a3977 2305 (save-excursion
69db9714 2306 (stgit-run-silent "edit" "--save-template=-" "--" patchsym)))))
0bca35c8
DK
2307
2308(defun stgit-confirm-edit ()
2309 (interactive)
2310 (let ((file (make-temp-file "stgit-edit-")))
2311 (write-region (point-min) (point-max) file)
2312 (stgit-capture-output nil
69db9714 2313 (stgit-run "edit" "-f" file "--" stgit-edit-patchsym))
0bca35c8 2314 (with-current-buffer log-edit-parent-buffer
1f0bf00f 2315 (stgit-reload))))
0bca35c8 2316
2acb7116 2317(defun stgit-new (add-sign &optional refresh)
aa04f831
GH
2318 "Create a new patch.
2319With a prefix argument, include a \"Signed-off-by:\" line at the
2320end of the patch."
2321 (interactive "P")
9d04c657 2322 (stgit-assert-mode)
c5d45b92
GH
2323 (let ((edit-buf (get-buffer-create "*StGit edit*"))
2324 (dir default-directory))
2325 (log-edit 'stgit-confirm-new t nil edit-buf)
aa04f831 2326 (setq default-directory dir)
2acb7116 2327 (set (make-local-variable 'stgit-refresh-after-new) refresh)
aa04f831
GH
2328 (when add-sign
2329 (save-excursion
2330 (let ((standard-output (current-buffer)))
2331 (stgit-run-silent "new" "--sign" "--save-template=-"))))))
64c097a0
DK
2332
2333(defun stgit-confirm-new ()
2334 (interactive)
2acb7116
DK
2335 (let ((file (make-temp-file "stgit-edit-"))
2336 (refresh stgit-refresh-after-new))
64c097a0
DK
2337 (write-region (point-min) (point-max) file)
2338 (stgit-capture-output nil
27b0f9e4 2339 (stgit-run "new" "-f" file))
64c097a0 2340 (with-current-buffer log-edit-parent-buffer
2acb7116
DK
2341 (if refresh
2342 (stgit-refresh)
2343 (stgit-reload)))))
2344
2345(defun stgit-new-and-refresh (add-sign)
2346 "Create a new patch and refresh it with the current changes.
2347
2348With a prefix argument, include a \"Signed-off-by:\" line at the
2349end of the patch.
2350
2351This works just like running `stgit-new' followed by `stgit-refresh'."
2352 (interactive "P")
9d04c657 2353 (stgit-assert-mode)
2acb7116 2354 (stgit-new add-sign t))
64c097a0
DK
2355
2356(defun stgit-create-patch-name (description)
2357 "Create a patch name from a long description"
2358 (let ((patch ""))
2359 (while (> (length description) 0)
2360 (cond ((string-match "\\`[a-zA-Z_-]+" description)
8439f657
GH
2361 (setq patch (downcase (concat patch
2362 (match-string 0 description))))
64c097a0
DK
2363 (setq description (substring description (match-end 0))))
2364 ((string-match "\\` +" description)
2365 (setq patch (concat patch "-"))
2366 (setq description (substring description (match-end 0))))
2367 ((string-match "\\`[^a-zA-Z_-]+" description)
2368 (setq description (substring description (match-end 0))))))
2369 (cond ((= (length patch) 0)
2370 "patch")
2371 ((> (length patch) 20)
2372 (substring patch 0 20))
2373 (t patch))))
0bca35c8 2374
9008e45b 2375(defun stgit-delete (patchsyms &optional spill-p)
d51722b7 2376 "Delete the patches in PATCHSYMS.
9008e45b
GH
2377Interactively, delete the marked patches, or the patch at point.
2378
2379With a prefix argument, or SPILL-P, spill the patch contents to
2380the work tree and index."
beac0f14 2381 (interactive (list (stgit-patches-marked-or-at-point t t)
9008e45b 2382 current-prefix-arg))
9d04c657 2383 (stgit-assert-mode)
e7231e4f
GH
2384 (unless patchsyms
2385 (error "No patches to delete"))
64ada6f5
GH
2386 (when (memq :index patchsyms)
2387 (error "Cannot delete the index"))
2388 (when (memq :work patchsyms)
2389 (error "Cannot delete the work tree"))
2390
d51722b7 2391 (let ((npatches (length patchsyms)))
9008e45b 2392 (when (yes-or-no-p (format "Really delete %d patch%s%s? "
e7231e4f 2393 npatches
9008e45b
GH
2394 (if (= 1 npatches) "" "es")
2395 (if spill-p
2396 " (spilling contents to index)"
2397 "")))
69db9714
GH
2398 (let ((args (append (when spill-p '("--spill"))
2399 '("--")
2400 patchsyms)))
9008e45b
GH
2401 (stgit-capture-output nil
2402 (apply 'stgit-run "delete" args))
2403 (stgit-reload)))))
d51722b7 2404
7cc45294
GH
2405(defun stgit-move-patches-target ()
2406 "Return the patchsym indicating a target patch for
2407`stgit-move-patches'.
2408
2547179e
GH
2409This is either the first unmarked patch at or after point, or one
2410of :top and :bottom if the point is after or before the applied
2411patches."
2412
2413 (save-excursion
2414 (let (result)
2415 (while (not result)
2416 (let ((patchsym (stgit-patch-name-at-point)))
2417 (cond ((memq patchsym '(:work :index)) (setq result :top))
2418 (patchsym (if (memq patchsym stgit-marked-patches)
2419 (stgit-next-patch)
2420 (setq result patchsym)))
2421 ((re-search-backward "^>" nil t) (setq result :top))
2422 (t (setq result :bottom)))))
2423 result)))
7cc45294 2424
c1412832 2425(defun stgit-sort-patches (patchsyms &optional allow-duplicates)
95369f6c
GH
2426 "Returns the list of patches in PATCHSYMS sorted according to
2427their position in the patch series, bottommost first.
2428
c1412832
KW
2429PATCHSYMS must not contain duplicate entries, unless
2430ALLOW-DUPLICATES is not nil."
b4f12bc1
GH
2431 (let (sorted-patchsyms)
2432 (ewoc-map #'(lambda (patch)
2433 (let ((name (stgit-patch->name patch)))
2434 (when (memq name patchsyms)
2435 (setq sorted-patchsyms (cons name sorted-patchsyms))))
2436 nil)
2437 stgit-ewoc)
95369f6c
GH
2438 (setq sorted-patchsyms (nreverse sorted-patchsyms))
2439
c1412832
KW
2440 (unless allow-duplicates
2441 (unless (= (length patchsyms) (length sorted-patchsyms))
2442 (error "Internal error")))
95369f6c
GH
2443
2444 sorted-patchsyms))
2445
7cc45294
GH
2446(defun stgit-move-patches (patchsyms target-patch)
2447 "Move the patches in PATCHSYMS to below TARGET-PATCH.
2448If TARGET-PATCH is :bottom or :top, move the patches to the
2449bottom or top of the stack, respectively.
2450
2451Interactively, move the marked patches to where the point is."
2452 (interactive (list stgit-marked-patches
2453 (stgit-move-patches-target)))
9d04c657 2454 (stgit-assert-mode)
7cc45294
GH
2455 (unless patchsyms
2456 (error "Need at least one patch to move"))
2457
2458 (unless target-patch
2459 (error "Point not at a patch"))
2460
2547179e
GH
2461 ;; need to have patchsyms sorted by position in the stack
2462 (let ((sorted-patchsyms (stgit-sort-patches patchsyms)))
2463 (stgit-capture-output nil
2464 (if (eq target-patch :top)
69db9714 2465 (apply 'stgit-run "float" "--" sorted-patchsyms)
2547179e
GH
2466 (apply 'stgit-run
2467 "sink"
2468 (append (unless (eq target-patch :bottom)
2469 (list "--to" target-patch))
2470 '("--")
2471 sorted-patchsyms)))))
7cc45294
GH
2472 (stgit-reload))
2473
594aa463
KH
2474(defun stgit-squash (patchsyms)
2475 "Squash the patches in PATCHSYMS.
693d179b
GH
2476Interactively, squash the marked patches.
2477
2478Unless there are any conflicts, the patches will be merged into
2479one patch, which will occupy the same spot in the series as the
2480deepest patch had before the squash."
d51722b7 2481 (interactive (list stgit-marked-patches))
9d04c657 2482 (stgit-assert-mode)
d51722b7 2483 (when (< (length patchsyms) 2)
594aa463 2484 (error "Need at least two patches to squash"))
32d7545d
GH
2485 (let ((stgit-buffer (current-buffer))
2486 (edit-buf (get-buffer-create "*StGit edit*"))
693d179b
GH
2487 (dir default-directory)
2488 (sorted-patchsyms (stgit-sort-patches patchsyms)))
594aa463 2489 (log-edit 'stgit-confirm-squash t nil edit-buf)
693d179b 2490 (set (make-local-variable 'stgit-patchsyms) sorted-patchsyms)
ea0def18 2491 (setq default-directory dir)
32d7545d 2492 (let ((result (let ((standard-output edit-buf))
655a3977
GH
2493 (save-excursion
2494 (apply 'stgit-run-silent "squash"
69db9714 2495 "--save-template=-" "--" sorted-patchsyms)))))
32d7545d
GH
2496
2497 ;; stg squash may have reordered the patches or caused conflicts
2498 (with-current-buffer stgit-buffer
2499 (stgit-reload))
2500
2501 (unless (eq 0 result)
2502 (fundamental-mode)
2503 (rename-buffer "*StGit error*")
2504 (resize-temp-buffer-window)
2505 (switch-to-buffer-other-window stgit-buffer)
2506 (error "stg squash failed")))))
ea0def18 2507
594aa463 2508(defun stgit-confirm-squash ()
ea0def18
DK
2509 (interactive)
2510 (let ((file (make-temp-file "stgit-edit-")))
2511 (write-region (point-min) (point-max) file)
2512 (stgit-capture-output nil
69db9714 2513 (apply 'stgit-run "squash" "-f" file "--" stgit-patchsyms))
ea0def18 2514 (with-current-buffer log-edit-parent-buffer
e6b1fdae
DK
2515 (stgit-clear-marks)
2516 ;; Go to first marked patch and stay there
2517 (goto-char (point-min))
2518 (re-search-forward (concat "^[>+-]\\*") nil t)
2519 (move-to-column goal-column)
2520 (let ((pos (point)))
1f0bf00f 2521 (stgit-reload)
e6b1fdae 2522 (goto-char pos)))))
ea0def18 2523
0663524d
KH
2524(defun stgit-help ()
2525 "Display help for the StGit mode."
2526 (interactive)
2527 (describe-function 'stgit-mode))
3a59f3db 2528
e8faa44b
DK
2529(defun stgit-execute-process-sentinel (process sentinel)
2530 (let (old-sentinel stgit-buf)
2531 (with-current-buffer (process-buffer process)
2532 (setq old-sentinel old-process-sentinel
2533 stgit-buf stgit-buffer))
2534 (and (memq (process-status process) '(exit signal))
2535 (buffer-live-p stgit-buf)
2536 (with-current-buffer stgit-buf
2537 (stgit-reload)))
2538 (funcall old-sentinel process sentinel)))
2539
a4a01630
GH
2540(defun stgit-execute-process-filter (process output)
2541 (with-current-buffer (process-buffer process)
2542 (let* ((old-point (point))
2543 (pmark (process-mark process))
2544 (insert-at (marker-position pmark))
2545 (at-pmark (= insert-at old-point)))
2546 (goto-char insert-at)
2547 (insert-before-markers output)
2548 (comint-carriage-motion insert-at (point))
2549 (set-marker pmark (point))
2550 (unless at-pmark
2551 (goto-char old-point)))))
2552
15d774ab 2553(defun stgit-execute (&optional git-mode)
e8faa44b
DK
2554 "Prompt for an stg command to execute in a shell.
2555
2556The names of any marked patches or the patch at point are
2557inserted in the command to be executed.
2558
15d774ab
GH
2559With a prefix argument, or if GIT-MODE is non-nil, insert SHA1
2560sums of the marked patches instead, and prompt for a git command.
2561
e8faa44b
DK
2562If the command ends in an ampersand, run it asynchronously.
2563
2564When the command has finished, reload the stgit buffer."
15d774ab 2565 (interactive "P")
e8faa44b 2566 (stgit-assert-mode)
8f489f41
GH
2567 (let* ((patches (stgit-sort-patches
2568 (stgit-patches-marked-or-at-point nil 'allow-committed)))
e8faa44b
DK
2569 (patch-names (mapcar 'symbol-name patches))
2570 (hyphens (find-if (lambda (s) (string-match "^-" s)) patch-names))
548ddfa6 2571 (program (if git-mode stgit-git-program stgit-stg-program))
e8faa44b 2572 (defaultcmd (if patches
548ddfa6
GH
2573 (concat program
2574 " "
e8faa44b 2575 (and hyphens "-- ")
15d774ab
GH
2576 (mapconcat (if git-mode 'stgit-id 'identity)
2577 patch-names " "))
548ddfa6
GH
2578 (concat stgit-stg-program " ")))
2579 (cmd (read-from-minibuffer "Shell command: "
2580 (cons defaultcmd (+ (length program) 2))
e8faa44b
DK
2581 nil nil 'shell-command-history))
2582 (async (string-match "&[ \t]*\\'" cmd))
2583 (buffer (get-buffer-create
2584 (if async
2585 "*Async Shell Command*"
2586 "*Shell Command Output*"))))
2587 ;; cannot use minibuffer as stgit-reload would overwrite it; if we
2588 ;; show the buffer, shell-command will not use the minibuffer
2589 (display-buffer buffer)
2590 (shell-command cmd)
2591 (if async
2592 (let ((old-buffer (current-buffer)))
2593 (with-current-buffer buffer
2594 (let ((process (get-buffer-process buffer)))
2595 (set (make-local-variable 'old-process-sentinel)
2596 (process-sentinel process))
2597 (set (make-local-variable 'stgit-buffer)
2598 old-buffer)
a4a01630 2599 (set-process-filter process 'stgit-execute-process-filter)
e8faa44b 2600 (set-process-sentinel process 'stgit-execute-process-sentinel))))
a4a01630
GH
2601 (with-current-buffer buffer
2602 (comint-carriage-motion (point-min) (point-max)))
e8faa44b
DK
2603 (shrink-window-if-larger-than-buffer (get-buffer-window buffer))
2604 (stgit-reload))))
2605
6c2d4962
GH
2606(defun stgit-undo-or-redo (redo hard)
2607 "Run stg undo or, if REDO is non-nil, stg redo.
2608
2609If HARD is non-nil, use the --hard flag."
2610 (stgit-assert-mode)
2611 (let ((cmd (if redo "redo" "undo")))
2612 (stgit-capture-output nil
2613 (if arg
2614 (when (or (and (stgit-index-empty-p)
2615 (stgit-work-tree-empty-p))
2616 (y-or-n-p (format "Hard %s may overwrite index/work tree changes. Continue? "
2617 cmd)))
2618 (stgit-run cmd "--hard"))
2619 (stgit-run cmd))))
2620 (stgit-reload))
2621
83e51dbf
DK
2622(defun stgit-undo (&optional arg)
2623 "Run stg undo.
b8463f1d
GH
2624With prefix argument, run it with the --hard flag.
2625
2626See also `stgit-redo'."
83e51dbf 2627 (interactive "P")
6c2d4962 2628 (stgit-undo-or-redo nil arg))
83e51dbf 2629
b8463f1d
GH
2630(defun stgit-redo (&optional arg)
2631 "Run stg redo.
2632With prefix argument, run it with the --hard flag.
2633
2634See also `stgit-undo'."
2635 (interactive "P")
6c2d4962 2636 (stgit-undo-or-redo t arg))
b8463f1d 2637
4d73c4d8
DK
2638(defun stgit-refresh (&optional arg)
2639 "Run stg refresh.
36a4eacd
GH
2640If the index contains any changes, only refresh from index.
2641
a53347d9 2642With prefix argument, refresh the marked patch or the patch under point."
4d73c4d8 2643 (interactive "P")
9d04c657 2644 (stgit-assert-mode)
4d73c4d8 2645 (let ((patchargs (if arg
b51a910f 2646 (let ((patches (stgit-patches-marked-or-at-point t t)))
beac0f14
GH
2647 (when (> (length patches) 1)
2648 (error "Too many patches marked"))
2649 (cons "-p" patches))
b0424080 2650 nil)))
36a4eacd
GH
2651 (unless (stgit-index-empty-p)
2652 (setq patchargs (cons "--index" patchargs)))
4d73c4d8 2653 (stgit-capture-output nil
074a4fb0
GH
2654 (apply 'stgit-run "refresh" patchargs))
2655 (stgit-refresh-git-status))
4d73c4d8
DK
2656 (stgit-reload))
2657
ce3b6130 2658(defvar stgit-show-worktree nil
8f702de4 2659 "If nil, inhibit showing work tree and index in the stgit buffer.
ce3b6130 2660
8f702de4 2661See also `stgit-show-worktree-mode'.")
ce3b6130 2662
d9473917
GH
2663(defvar stgit-show-ignored nil
2664 "If nil, inhibit showing files ignored by git.")
2665
2666(defvar stgit-show-unknown nil
2667 "If nil, inhibit showing files not registered with git.")
2668
a0045b87
DK
2669(defvar stgit-show-patch-names t
2670 "If nil, inhibit showing patch names.")
2671
bf7e391c
GH
2672(defvar stgit-show-committed nil
2673 "If nil, inhibit showing recent commits.")
2674
137b315b
GH
2675(defvar stgit-show-svn nil
2676 "If nil, inhibit showing git svn information.")
2677
bf7e391c
GH
2678(defvar stgit-committed-count nil
2679 "The number of recent commits to show.")
2680
f9533abf 2681(defmacro stgit-define-toggle-view (sym desc help)
8a619f55
GH
2682 (declare (indent 1))
2683 (let* ((name (symbol-name sym))
2684 (fun (intern (concat "stgit-toggle-" name)))
2685 (flag (intern (concat "stgit-show-" name))))
2686 ;; make help-follow find the correct function
2687 `(put (quote ,fun) 'definition-name 'stgit-define-toggle-view)
2688 `(defun ,fun (&optional arg)
2689 ,help
2690 (interactive "P")
2691 (stgit-assert-mode)
2692 (setq ,flag (if arg
2693 (> (prefix-numeric-value arg) 0)
2694 (not ,flag)))
f9533abf 2695 (stgit-reload (format "%s %s" (if ,flag "Showing" "Hiding") ,desc)))))
8a619f55
GH
2696
2697(stgit-define-toggle-view worktree
f9533abf 2698 "work tree and index"
ce3b6130 2699 "Toggle the visibility of the work tree.
2d7bcbd9 2700With ARG, show the work tree if ARG is positive.
ce3b6130 2701
8f702de4
GH
2702Its initial setting is controlled by `stgit-default-show-worktree'.
2703
2704`stgit-show-worktree-mode' controls where on screen the index and
8a619f55 2705work tree will show up.")
ce3b6130 2706
8a619f55 2707(stgit-define-toggle-view ignored
f9533abf 2708 "ignored files"
d9473917
GH
2709 "Toggle the visibility of files ignored by git in the work
2710tree. With ARG, show these files if ARG is positive.
2711
bc11fb08
GH
2712Its initial setting is controlled by `stgit-default-show-ignored'.
2713
8a619f55 2714Use \\[stgit-toggle-worktree] to show the work tree.")
d9473917 2715
8a619f55 2716(stgit-define-toggle-view unknown
f9533abf 2717 "unknown files"
d9473917
GH
2718 "Toggle the visibility of files not registered with git in the
2719work tree. With ARG, show these files if ARG is positive.
2720
bc11fb08
GH
2721Its initial setting is controlled by `stgit-default-show-unknown'.
2722
8a619f55 2723Use \\[stgit-toggle-worktree] to show the work tree.")
d9473917 2724
8a619f55 2725(stgit-define-toggle-view patch-names
f9533abf 2726 "patch names"
a0045b87
DK
2727 "Toggle the visibility of patch names. With ARG, show patch names
2728if ARG is positive.
2729
8a619f55 2730The initial setting is controlled by `stgit-default-show-patch-names'.")
a0045b87 2731
137b315b 2732(stgit-define-toggle-view svn
f9533abf 2733 "subversion revisions"
137b315b
GH
2734 "Toggle showing subversion information from git svn. With ARG,
2735show svn information if ARG is positive.
2736
2737The initial setting is controlled by `stgit-default-show-svn'.")
2738
bf7e391c
GH
2739(defun stgit-toggle-committed (&optional arg)
2740 "Toggle the visibility of historical git commits.
2741With ARG, set the number of commits to show to ARG, and disable
2742them if ARG is zero.
2743
2744The initial setting is controlled by `stgit-default-show-committed'."
2745 (interactive "P")
2746 (stgit-assert-mode)
2747 (if (null arg)
2748 (setq stgit-show-committed (not stgit-show-committed))
2749 (let ((n (prefix-numeric-value arg)))
2750 (setq stgit-show-committed (> n 0))
2751 (setq stgit-committed-count n)))
f9533abf
GH
2752 (stgit-reload (format "%s historical commits"
2753 (if (and stgit-show-committed
2754 (> stgit-committed-count 0))
2755 "Showing"
2756 "Hiding"))))
bf7e391c 2757
3a59f3db 2758(provide 'stgit)