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