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