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