c3c853150b7ba271734e27ce172d49d885f15e77
[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 (require 'git nil t)
13 (require 'cl)
14 (require 'ewoc)
15
16 (defun stgit (dir)
17 "Manage StGit patches for the tree in DIR."
18 (interactive "DDirectory: \n")
19 (switch-to-stgit-buffer (git-get-top-dir dir))
20 (stgit-reload))
21
22 (unless (fboundp 'git-get-top-dir)
23 (defun git-get-top-dir (dir)
24 "Retrieve the top-level directory of a git tree."
25 (let ((cdup (with-output-to-string
26 (with-current-buffer standard-output
27 (cd dir)
28 (unless (eq 0 (call-process "git" nil t nil
29 "rev-parse" "--show-cdup"))
30 (error "Cannot find top-level git tree for %s" dir))))))
31 (expand-file-name (concat (file-name-as-directory dir)
32 (car (split-string cdup "\n")))))))
33
34 (defun stgit-refresh-git-status (&optional dir)
35 "If it exists, refresh the `git-status' buffer belonging to
36 directory DIR or `default-directory'"
37 (when (and (fboundp 'git-find-status-buffer)
38 (fboundp 'git-refresh-status))
39 (let* ((top-dir (git-get-top-dir (or dir default-directory)))
40 (git-status-buffer (and top-dir (git-find-status-buffer top-dir))))
41 (when git-status-buffer
42 (with-current-buffer git-status-buffer
43 (git-refresh-status))))))
44
45 (defun stgit-find-buffer (dir)
46 "Return the buffer displaying StGit patches for DIR, or nil if none."
47 (setq dir (file-name-as-directory dir))
48 (let ((buffers (buffer-list)))
49 (while (and buffers
50 (not (with-current-buffer (car buffers)
51 (and (eq major-mode 'stgit-mode)
52 (string= default-directory dir)))))
53 (setq buffers (cdr buffers)))
54 (and buffers (car buffers))))
55
56 (defun switch-to-stgit-buffer (dir)
57 "Switch to a (possibly new) buffer displaying StGit patches for DIR."
58 (setq dir (file-name-as-directory dir))
59 (let ((buffer (stgit-find-buffer dir)))
60 (switch-to-buffer (or buffer
61 (create-stgit-buffer dir)))))
62
63 (defstruct (stgit-patch)
64 status name desc empty files-ewoc)
65
66 (defun stgit-patch-pp (patch)
67 (let ((status (stgit-patch-status patch))
68 (start (point))
69 (name (stgit-patch-name patch)))
70 (case name
71 (:index (insert " "
72 (propertize "Index"
73 'face 'stgit-index-work-tree-title-face)))
74 (:work (insert " "
75 (propertize "Work tree"
76 'face 'stgit-index-work-tree-title-face)))
77 (t (insert (case status
78 ('applied "+")
79 ('top ">")
80 ('unapplied "-"))
81 (if (memq name stgit-marked-patches)
82 "*" " ")
83 (format "%-30s"
84 (propertize
85 (symbol-name name)
86 'face (cdr (assq status
87 stgit-patch-status-face-alist))))
88 " "
89 (if (stgit-patch-empty patch) "(empty) " "")
90 (propertize (or (stgit-patch-desc patch) "")
91 'face 'stgit-description-face))))
92 (insert "\n")
93 (put-text-property start (point) 'entry-type 'patch)
94 (when (memq name stgit-expanded-patches)
95 (stgit-insert-patch-files patch))
96 (put-text-property start (point) 'patch-data patch)))
97
98 (defun create-stgit-buffer (dir)
99 "Create a buffer for showing StGit patches.
100 Argument DIR is the repository path."
101 (let ((buf (create-file-buffer (concat dir "*stgit*")))
102 (inhibit-read-only t))
103 (with-current-buffer buf
104 (setq default-directory dir)
105 (stgit-mode)
106 (set (make-local-variable 'stgit-ewoc)
107 (ewoc-create #'stgit-patch-pp "Branch:\n\n" "--\n" t))
108 (setq buffer-read-only t))
109 buf))
110
111 (defmacro stgit-capture-output (name &rest body)
112 "Capture StGit output and, if there was any output, show it in a window
113 at the end.
114 Returns nil if there was no output."
115 (declare (debug ([&or stringp null] body))
116 (indent 1))
117 `(let ((output-buf (get-buffer-create ,(or name "*StGit output*")))
118 (stgit-dir default-directory)
119 (inhibit-read-only t))
120 (with-current-buffer output-buf
121 (erase-buffer)
122 (setq default-directory stgit-dir)
123 (setq buffer-read-only t))
124 (let ((standard-output output-buf))
125 ,@body)
126 (with-current-buffer output-buf
127 (set-buffer-modified-p nil)
128 (setq buffer-read-only t)
129 (if (< (point-min) (point-max))
130 (display-buffer output-buf t)))))
131
132 (defun stgit-make-run-args (args)
133 "Return a copy of ARGS with its elements converted to strings."
134 (mapcar (lambda (x)
135 ;; don't use (format "%s" ...) to limit type errors
136 (cond ((stringp x) x)
137 ((integerp x) (number-to-string x))
138 ((symbolp x) (symbol-name x))
139 (t
140 (error "Bad element in stgit-make-run-args args: %S" x))))
141 args))
142
143 (defun stgit-run-silent (&rest args)
144 (setq args (stgit-make-run-args args))
145 (apply 'call-process "stg" nil standard-output nil args))
146
147 (defun stgit-run (&rest args)
148 (setq args (stgit-make-run-args args))
149 (let ((msgcmd (mapconcat #'identity args " ")))
150 (message "Running stg %s..." msgcmd)
151 (apply 'call-process "stg" nil standard-output nil args)
152 (message "Running stg %s...done" msgcmd)))
153
154 (defun stgit-run-git (&rest args)
155 (setq args (stgit-make-run-args args))
156 (let ((msgcmd (mapconcat #'identity args " ")))
157 (message "Running git %s..." msgcmd)
158 (apply 'call-process "git" nil standard-output nil args)
159 (message "Running git %s...done" msgcmd)))
160
161 (defun stgit-run-git-silent (&rest args)
162 (setq args (stgit-make-run-args args))
163 (apply 'call-process "git" nil standard-output nil args))
164
165 (defun stgit-index-empty-p ()
166 "Returns non-nil if the index contains no changes from HEAD."
167 (zerop (stgit-run-git-silent "diff-index" "--cached" "--quiet" "HEAD")))
168
169 (defvar stgit-index-node)
170 (defvar stgit-worktree-node)
171
172 (defun stgit-refresh-index ()
173 (when stgit-index-node
174 (ewoc-invalidate (car stgit-index-node) (cdr stgit-index-node))))
175
176 (defun stgit-refresh-worktree ()
177 (when stgit-worktree-node
178 (ewoc-invalidate (car stgit-worktree-node) (cdr stgit-worktree-node))))
179
180 (defun stgit-run-series-insert-index (ewoc)
181 (setq index-node (cons ewoc (ewoc-enter-last ewoc
182 (make-stgit-patch
183 :status 'index
184 :name :index
185 :desc nil
186 :empty nil)))
187 worktree-node (cons ewoc (ewoc-enter-last ewoc
188 (make-stgit-patch
189 :status 'work
190 :name :work
191 :desc nil
192 :empty nil)))))
193
194 (defun stgit-run-series (ewoc)
195 (setq stgit-index-node nil
196 stgit-worktree-node nil)
197 (let ((inserted-index (not stgit-show-worktree))
198 index-node
199 worktree-node
200 all-patchsyms)
201 (with-temp-buffer
202 (let ((exit-status (stgit-run-silent "series" "--description" "--empty")))
203 (goto-char (point-min))
204 (if (not (zerop exit-status))
205 (cond ((looking-at "stg series: \\(.*\\)")
206 (setq inserted-index t)
207 (ewoc-set-hf ewoc (car (ewoc-get-hf ewoc))
208 (substitute-command-keys
209 "-- not initialized; run \\[stgit-init]")))
210 ((looking-at ".*")
211 (error "Error running stg: %s"
212 (match-string 0))))
213 (while (not (eobp))
214 (unless (looking-at
215 "\\([0 ]\\)\\([>+-]\\)\\( \\)\\([^ ]+\\) *[|#] \\(.*\\)")
216 (error "Syntax error in output from stg series"))
217 (let* ((state-str (match-string 2))
218 (state (cond ((string= state-str ">") 'top)
219 ((string= state-str "+") 'applied)
220 ((string= state-str "-") 'unapplied)))
221 (name (intern (match-string 4)))
222 (desc (match-string 5))
223 (empty (string= (match-string 1) "0")))
224 (unless inserted-index
225 (when (or (eq stgit-show-worktree-mode 'top)
226 (and (eq stgit-show-worktree-mode 'center)
227 (eq state 'unapplied)))
228 (setq inserted-index t)
229 (stgit-run-series-insert-index ewoc)))
230 (setq all-patchsyms (cons name all-patchsyms))
231 (ewoc-enter-last ewoc
232 (make-stgit-patch
233 :status state
234 :name name
235 :desc desc
236 :empty empty)))
237 (forward-line 1))))
238 (unless inserted-index
239 (stgit-run-series-insert-index ewoc)))
240 (setq stgit-index-node index-node
241 stgit-worktree-node worktree-node
242 stgit-marked-patches (intersection stgit-marked-patches
243 all-patchsyms))))
244
245 (defun stgit-reload ()
246 "Update the contents of the StGit buffer."
247 (interactive)
248 (let ((inhibit-read-only t)
249 (curline (line-number-at-pos))
250 (curpatch (stgit-patch-name-at-point)))
251 (ewoc-filter stgit-ewoc #'(lambda (x) nil))
252 (ewoc-set-hf stgit-ewoc
253 (concat "Branch: "
254 (propertize
255 (with-temp-buffer
256 (stgit-run-silent "branch")
257 (buffer-substring (point-min) (1- (point-max))))
258 'face 'stgit-branch-name-face)
259 "\n\n")
260 (if stgit-show-worktree
261 "--"
262 (propertize
263 (substitute-command-keys "--\n\"\\[stgit-toggle-worktree]\"\
264 shows the working tree\n")
265 'face 'stgit-description-face)))
266 (stgit-run-series stgit-ewoc)
267 (if curpatch
268 (stgit-goto-patch curpatch)
269 (goto-line curline)))
270 (stgit-refresh-git-status))
271
272 (defgroup stgit nil
273 "A user interface for the StGit patch maintenance tool."
274 :group 'tools)
275
276 (defface stgit-description-face
277 '((((background dark)) (:foreground "tan"))
278 (((background light)) (:foreground "dark red")))
279 "The face used for StGit descriptions"
280 :group 'stgit)
281
282 (defface stgit-branch-name-face
283 '((t :inherit bold))
284 "The face used for the StGit branch name"
285 :group 'stgit)
286
287 (defface stgit-top-patch-face
288 '((((background dark)) (:weight bold :foreground "yellow"))
289 (((background light)) (:weight bold :foreground "purple"))
290 (t (:weight bold)))
291 "The face used for the top patch names"
292 :group 'stgit)
293
294 (defface stgit-applied-patch-face
295 '((((background dark)) (:foreground "light yellow"))
296 (((background light)) (:foreground "purple"))
297 (t ()))
298 "The face used for applied patch names"
299 :group 'stgit)
300
301 (defface stgit-unapplied-patch-face
302 '((((background dark)) (:foreground "gray80"))
303 (((background light)) (:foreground "orchid"))
304 (t ()))
305 "The face used for unapplied patch names"
306 :group 'stgit)
307
308 (defface stgit-modified-file-face
309 '((((class color) (background light)) (:foreground "purple"))
310 (((class color) (background dark)) (:foreground "salmon")))
311 "StGit mode face used for modified file status"
312 :group 'stgit)
313
314 (defface stgit-unmerged-file-face
315 '((((class color) (background light)) (:foreground "red" :bold t))
316 (((class color) (background dark)) (:foreground "red" :bold t)))
317 "StGit mode face used for unmerged file status"
318 :group 'stgit)
319
320 (defface stgit-unknown-file-face
321 '((((class color) (background light)) (:foreground "goldenrod" :bold t))
322 (((class color) (background dark)) (:foreground "goldenrod" :bold t)))
323 "StGit mode face used for unknown file status"
324 :group 'stgit)
325
326 (defface stgit-file-permission-face
327 '((((class color) (background light)) (:foreground "green" :bold t))
328 (((class color) (background dark)) (:foreground "green" :bold t)))
329 "StGit mode face used for permission changes."
330 :group 'stgit)
331
332 (defface stgit-index-work-tree-title-face
333 '((((supports :slant italic)) :slant italic)
334 (t :inherit bold))
335 "StGit mode face used for the \"Index\" and \"Work tree\" titles"
336 :group 'stgit)
337
338
339 (defcustom stgit-expand-find-copies-harder
340 nil
341 "Try harder to find copied files when listing patches.
342
343 When not nil, runs git diff-tree with the --find-copies-harder
344 flag, which reduces performance."
345 :type 'boolean
346 :group 'stgit)
347
348 (defconst stgit-file-status-code-strings
349 (mapcar (lambda (arg)
350 (cons (car arg)
351 (propertize (cadr arg) 'face (car (cddr arg)))))
352 '((add "Added" stgit-modified-file-face)
353 (copy "Copied" stgit-modified-file-face)
354 (delete "Deleted" stgit-modified-file-face)
355 (modify "Modified" stgit-modified-file-face)
356 (rename "Renamed" stgit-modified-file-face)
357 (mode-change "Mode change" stgit-modified-file-face)
358 (unmerged "Unmerged" stgit-unmerged-file-face)
359 (unknown "Unknown" stgit-unknown-file-face)))
360 "Alist of code symbols to description strings")
361
362 (defconst stgit-patch-status-face-alist
363 '((applied . stgit-applied-patch-face)
364 (top . stgit-top-patch-face)
365 (unapplied . stgit-unapplied-patch-face)
366 (index . nil)
367 (work . nil))
368 "Alist of face to use for a given patch status")
369
370 (defun stgit-file-status-code-as-string (file)
371 "Return stgit status code for FILE as a string"
372 (let* ((code (assq (stgit-file-status file)
373 stgit-file-status-code-strings))
374 (score (stgit-file-cr-score file)))
375 (when code
376 (format "%-11s "
377 (if (and score (/= score 100))
378 (format "%s %s" (cdr code)
379 (propertize (format "%d%%" score)
380 'face 'stgit-description-face))
381 (cdr code))))))
382
383 (defun stgit-file-status-code (str &optional score)
384 "Return stgit status code from git status string"
385 (let ((code (assoc str '(("A" . add)
386 ("C" . copy)
387 ("D" . delete)
388 ("M" . modify)
389 ("R" . rename)
390 ("T" . mode-change)
391 ("U" . unmerged)
392 ("X" . unknown)))))
393 (setq code (if code (cdr code) 'unknown))
394 (when (stringp score)
395 (if (> (length score) 0)
396 (setq score (string-to-number score))
397 (setq score nil)))
398 (if score (cons code score) code)))
399
400 (defconst stgit-file-type-strings
401 '((#o100 . "file")
402 (#o120 . "symlink")
403 (#o160 . "subproject"))
404 "Alist of names of file types")
405
406 (defun stgit-file-type-string (type)
407 "Return string describing file type TYPE (the high bits of file permission).
408 Cf. `stgit-file-type-strings' and `stgit-file-type-change-string'."
409 (let ((type-str (assoc type stgit-file-type-strings)))
410 (or (and type-str (cdr type-str))
411 (format "unknown type %o" type))))
412
413 (defun stgit-file-type-change-string (old-perm new-perm)
414 "Return string describing file type change from OLD-PERM to NEW-PERM.
415 Cf. `stgit-file-type-string'."
416 (let ((old-type (lsh old-perm -9))
417 (new-type (lsh new-perm -9)))
418 (cond ((= old-type new-type) "")
419 ((zerop new-type) "")
420 ((zerop old-type)
421 (if (= new-type #o100)
422 ""
423 (format " (%s)" (stgit-file-type-string new-type))))
424 (t (format " (%s -> %s)"
425 (stgit-file-type-string old-type)
426 (stgit-file-type-string new-type))))))
427
428 (defun stgit-file-mode-change-string (old-perm new-perm)
429 "Return string describing file mode change from OLD-PERM to NEW-PERM.
430 Cf. `stgit-file-type-change-string'."
431 (setq old-perm (logand old-perm #o777)
432 new-perm (logand new-perm #o777))
433 (if (or (= old-perm new-perm)
434 (zerop old-perm)
435 (zerop new-perm))
436 ""
437 (let* ((modified (logxor old-perm new-perm))
438 (not-x-modified (logand (logxor old-perm new-perm) #o666)))
439 (cond ((zerop modified) "")
440 ((and (zerop not-x-modified)
441 (or (and (eq #o111 (logand old-perm #o111))
442 (propertize "-x" 'face 'stgit-file-permission-face))
443 (and (eq #o111 (logand new-perm #o111))
444 (propertize "+x" 'face
445 'stgit-file-permission-face)))))
446 (t (concat (propertize (format "%o" old-perm)
447 'face 'stgit-file-permission-face)
448 (propertize " -> "
449 'face 'stgit-description-face)
450 (propertize (format "%o" new-perm)
451 'face 'stgit-file-permission-face)))))))
452
453 (defstruct (stgit-file)
454 old-perm new-perm copy-or-rename cr-score cr-from cr-to status file)
455
456 (defun stgit-file-pp (file)
457 (let ((status (stgit-file-status file))
458 (name (if (stgit-file-copy-or-rename file)
459 (concat (stgit-file-cr-from file)
460 (propertize " -> "
461 'face 'stgit-description-face)
462 (stgit-file-cr-to file))
463 (stgit-file-file file)))
464 (mode-change (stgit-file-mode-change-string
465 (stgit-file-old-perm file)
466 (stgit-file-new-perm file)))
467 (start (point)))
468 (insert (format " %-12s%1s%s%s\n"
469 (stgit-file-status-code-as-string file)
470 mode-change
471 name
472 (propertize (stgit-file-type-change-string
473 (stgit-file-old-perm file)
474 (stgit-file-new-perm file))
475 'face 'stgit-description-face)))
476 (add-text-properties start (point)
477 (list 'entry-type 'file
478 'file-data file))))
479
480 (defun stgit-find-copies-harder-diff-arg ()
481 "Return the flag to use with `git-diff' depending on the
482 `stgit-expand-find-copies-harder' flag."
483 (if stgit-expand-find-copies-harder
484 "--find-copies-harder"
485 "-C"))
486
487 (defun stgit-insert-patch-files (patch)
488 "Expand (show modification of) the patch PATCH after the line
489 at point."
490 (let* ((patchsym (stgit-patch-name patch))
491 (end (point-marker))
492 (args (list "-z" (stgit-find-copies-harder-diff-arg)))
493 (ewoc (ewoc-create #'stgit-file-pp nil nil t)))
494 (set-marker-insertion-type end t)
495 (setf (stgit-patch-files-ewoc patch) ewoc)
496 (with-temp-buffer
497 (apply 'stgit-run-git
498 (cond ((eq patchsym :work)
499 `("diff-files" ,@args))
500 ((eq patchsym :index)
501 `("diff-index" ,@args "--cached" "HEAD"))
502 (t
503 `("diff-tree" ,@args "-r" ,(stgit-id patchsym)))))
504 (goto-char (point-min))
505 (unless (or (eobp) (memq patchsym '(:work :index)))
506 (forward-char 41))
507 (while (looking-at ":\\([0-7]+\\) \\([0-7]+\\) [0-9A-Fa-f]\\{40\\} [0-9A-Fa-f]\\{40\\} ")
508 (let ((old-perm (string-to-number (match-string 1) 8))
509 (new-perm (string-to-number (match-string 2) 8)))
510 (goto-char (match-end 0))
511 (let ((file
512 (cond ((looking-at
513 "\\([CR]\\)\\([0-9]*\\)\0\\([^\0]*\\)\0\\([^\0]*\\)\0")
514 (let* ((patch-status (stgit-patch-status patch))
515 (file-subexp (if (eq patch-status 'unapplied)
516 3
517 4))
518 (file (match-string file-subexp)))
519 (make-stgit-file
520 :old-perm old-perm
521 :new-perm new-perm
522 :copy-or-rename t
523 :cr-score (string-to-number (match-string 2))
524 :cr-from (match-string 3)
525 :cr-to (match-string 4)
526 :status (stgit-file-status-code
527 (match-string 1))
528 :file file)))
529 ((looking-at "\\([ABD-QS-Z]\\)\0\\([^\0]*\\)\0")
530 (make-stgit-file
531 :old-perm old-perm
532 :new-perm new-perm
533 :copy-or-rename nil
534 :cr-score nil
535 :cr-from nil
536 :cr-to nil
537 :status (stgit-file-status-code
538 (match-string 1))
539 :file (match-string 2))))))
540 (ewoc-enter-last ewoc file))
541 (goto-char (match-end 0))))
542 (unless (ewoc-nth ewoc 0)
543 (ewoc-set-hf ewoc ""
544 (concat " "
545 (propertize "<no files>"
546 'face 'stgit-description-face)
547 "\n"))))
548 (goto-char end)))
549
550 (defun stgit-select-file ()
551 (let ((filename (expand-file-name
552 (stgit-file-file (stgit-patched-file-at-point)))))
553 (unless (file-exists-p filename)
554 (error "File does not exist"))
555 (find-file filename)))
556
557 (defun stgit-select-patch ()
558 (let ((patchname (stgit-patch-name-at-point)))
559 (if (memq patchname stgit-expanded-patches)
560 (setq stgit-expanded-patches (delq patchname stgit-expanded-patches))
561 (setq stgit-expanded-patches (cons patchname stgit-expanded-patches)))
562 (ewoc-invalidate stgit-ewoc (ewoc-locate stgit-ewoc)))
563 (move-to-column (stgit-goal-column)))
564
565 (defun stgit-select ()
566 "With point on a patch, toggle showing files in the patch.
567
568 With point on a file, open the associated file. Opens the target
569 file for (applied) copies and renames."
570 (interactive)
571 (case (get-text-property (point) 'entry-type)
572 ('patch
573 (stgit-select-patch))
574 ('file
575 (stgit-select-file))
576 (t
577 (error "No patch or file on line"))))
578
579 (defun stgit-find-file-other-window ()
580 "Open file at point in other window"
581 (interactive)
582 (let ((patched-file (stgit-patched-file-at-point)))
583 (unless patched-file
584 (error "No file on the current line"))
585 (let ((filename (expand-file-name (stgit-file-file patched-file))))
586 (unless (file-exists-p filename)
587 (error "File does not exist"))
588 (find-file-other-window filename))))
589
590 (defun stgit-quit ()
591 "Hide the stgit buffer."
592 (interactive)
593 (bury-buffer))
594
595 (defun stgit-git-status ()
596 "Show status using `git-status'."
597 (interactive)
598 (unless (fboundp 'git-status)
599 (error "The stgit-git-status command requires git-status"))
600 (let ((dir default-directory))
601 (save-selected-window
602 (pop-to-buffer nil)
603 (git-status dir))))
604
605 (defun stgit-goal-column ()
606 "Return goal column for the current line"
607 (case (get-text-property (point) 'entry-type)
608 ('patch 2)
609 ('file 4)
610 (t 0)))
611
612 (defun stgit-next-line (&optional arg)
613 "Move cursor vertically down ARG lines"
614 (interactive "p")
615 (next-line arg)
616 (move-to-column (stgit-goal-column)))
617
618 (defun stgit-previous-line (&optional arg)
619 "Move cursor vertically up ARG lines"
620 (interactive "p")
621 (previous-line arg)
622 (move-to-column (stgit-goal-column)))
623
624 (defun stgit-next-patch (&optional arg)
625 "Move cursor down ARG patches."
626 (interactive "p")
627 (ewoc-goto-next stgit-ewoc (or arg 1))
628 (move-to-column goal-column))
629
630 (defun stgit-previous-patch (&optional arg)
631 "Move cursor up ARG patches."
632 (interactive "p")
633 (ewoc-goto-prev stgit-ewoc (or arg 1))
634 (move-to-column goal-column))
635
636 (defvar stgit-mode-hook nil
637 "Run after `stgit-mode' is setup.")
638
639 (defvar stgit-mode-map nil
640 "Keymap for StGit major mode.")
641
642 (unless stgit-mode-map
643 (let ((toggle-map (make-keymap)))
644 (suppress-keymap toggle-map)
645 (mapc (lambda (arg) (define-key toggle-map (car arg) (cdr arg)))
646 '(("t" . stgit-toggle-worktree)))
647 (setq stgit-mode-map (make-keymap))
648 (suppress-keymap stgit-mode-map)
649 (mapc (lambda (arg) (define-key stgit-mode-map (car arg) (cdr arg)))
650 `((" " . stgit-mark)
651 ("m" . stgit-mark)
652 ("\d" . stgit-unmark-up)
653 ("u" . stgit-unmark-down)
654 ("?" . stgit-help)
655 ("h" . stgit-help)
656 ("\C-p" . stgit-previous-line)
657 ("\C-n" . stgit-next-line)
658 ([up] . stgit-previous-line)
659 ([down] . stgit-next-line)
660 ("p" . stgit-previous-patch)
661 ("n" . stgit-next-patch)
662 ("\M-{" . stgit-previous-patch)
663 ("\M-}" . stgit-next-patch)
664 ("s" . stgit-git-status)
665 ("g" . stgit-reload-or-repair)
666 ("r" . stgit-refresh)
667 ("\C-c\C-r" . stgit-rename)
668 ("e" . stgit-edit)
669 ("M" . stgit-move-patches)
670 ("S" . stgit-squash)
671 ("N" . stgit-new)
672 ("\C-c\C-c" . stgit-commit)
673 ("\C-c\C-u" . stgit-uncommit)
674 ("U" . stgit-revert-file)
675 ("R" . stgit-resolve-file)
676 ("\r" . stgit-select)
677 ("o" . stgit-find-file-other-window)
678 ("i" . stgit-file-toggle-index)
679 (">" . stgit-push-next)
680 ("<" . stgit-pop-next)
681 ("P" . stgit-push-or-pop)
682 ("G" . stgit-goto)
683 ("=" . stgit-show)
684 ("D" . stgit-delete)
685 ([(control ?/)] . stgit-undo)
686 ("\C-_" . stgit-undo)
687 ("B" . stgit-branch)
688 ("t" . ,toggle-map)
689 ("q" . stgit-quit)))))
690
691 (defun stgit-mode ()
692 "Major mode for interacting with StGit.
693 Commands:
694 \\{stgit-mode-map}"
695 (kill-all-local-variables)
696 (buffer-disable-undo)
697 (setq mode-name "StGit"
698 major-mode 'stgit-mode
699 goal-column 2)
700 (use-local-map stgit-mode-map)
701 (set (make-local-variable 'list-buffers-directory) default-directory)
702 (set (make-local-variable 'stgit-marked-patches) nil)
703 (set (make-local-variable 'stgit-expanded-patches) (list :work :index))
704 (set (make-local-variable 'stgit-show-worktree) stgit-default-show-worktree)
705 (set (make-local-variable 'stgit-index-node) nil)
706 (set (make-local-variable 'stgit-worktree-node) nil)
707 (set-variable 'truncate-lines 't)
708 (add-hook 'after-save-hook 'stgit-update-saved-file)
709 (run-hooks 'stgit-mode-hook))
710
711 (defun stgit-update-saved-file ()
712 (let* ((file (expand-file-name buffer-file-name))
713 (dir (file-name-directory file))
714 (gitdir (condition-case nil (git-get-top-dir dir)
715 (error nil)))
716 (buffer (and gitdir (stgit-find-buffer gitdir))))
717 (when buffer
718 (with-current-buffer buffer
719 (stgit-refresh-worktree)))))
720
721 (defun stgit-add-mark (patchsym)
722 "Mark the patch PATCHSYM."
723 (setq stgit-marked-patches (cons patchsym stgit-marked-patches)))
724
725 (defun stgit-remove-mark (patchsym)
726 "Unmark the patch PATCHSYM."
727 (setq stgit-marked-patches (delq patchsym stgit-marked-patches)))
728
729 (defun stgit-clear-marks ()
730 "Unmark all patches."
731 (setq stgit-marked-patches '()))
732
733 (defun stgit-patch-at-point (&optional cause-error)
734 (get-text-property (point) 'patch-data))
735
736 (defun stgit-patch-name-at-point (&optional cause-error only-patches)
737 "Return the patch name on the current line as a symbol.
738 If CAUSE-ERROR is not nil, signal an error if none found.
739 If ONLY-PATCHES is not nil, only allow real patches, and not
740 index or work tree."
741 (let ((patch (stgit-patch-at-point)))
742 (and patch
743 only-patches
744 (memq (stgit-patch-status patch) '(work index))
745 (setq patch nil))
746 (cond (patch
747 (stgit-patch-name patch))
748 (cause-error
749 (error "No patch on this line")))))
750
751 (defun stgit-patched-file-at-point ()
752 (get-text-property (point) 'file-data))
753
754 (defun stgit-patches-marked-or-at-point ()
755 "Return the symbols of the marked patches, or the patch on the current line."
756 (if stgit-marked-patches
757 stgit-marked-patches
758 (let ((patch (stgit-patch-name-at-point)))
759 (if patch
760 (list patch)
761 '()))))
762
763 (defun stgit-goto-patch (patchsym)
764 "Move point to the line containing patch PATCHSYM.
765 If that patch cannot be found, do nothing."
766 (let ((node (ewoc-nth stgit-ewoc 0)))
767 (while (and node (not (eq (stgit-patch-name (ewoc-data node))
768 patchsym)))
769 (setq node (ewoc-next stgit-ewoc node)))
770 (when node
771 (ewoc-goto-node stgit-ewoc node)
772 (move-to-column goal-column))))
773
774 (defun stgit-init ()
775 "Run stg init."
776 (interactive)
777 (stgit-capture-output nil
778 (stgit-run "init"))
779 (stgit-reload))
780
781 (defun stgit-mark ()
782 "Mark the patch under point."
783 (interactive)
784 (let* ((node (ewoc-locate stgit-ewoc))
785 (patch (ewoc-data node))
786 (name (stgit-patch-name patch)))
787 (when (eq name :work)
788 (error "Cannot mark the work tree"))
789 (when (eq name :index)
790 (error "Cannot mark the index"))
791 (stgit-add-mark (stgit-patch-name patch))
792 (ewoc-invalidate stgit-ewoc node))
793 (stgit-next-patch))
794
795 (defun stgit-unmark-up ()
796 "Remove mark from the patch on the previous line."
797 (interactive)
798 (stgit-previous-patch)
799 (let* ((node (ewoc-locate stgit-ewoc))
800 (patch (ewoc-data node)))
801 (stgit-remove-mark (stgit-patch-name patch))
802 (ewoc-invalidate stgit-ewoc node))
803 (move-to-column (stgit-goal-column)))
804
805 (defun stgit-unmark-down ()
806 "Remove mark from the patch on the current line."
807 (interactive)
808 (let* ((node (ewoc-locate stgit-ewoc))
809 (patch (ewoc-data node)))
810 (stgit-remove-mark (stgit-patch-name patch))
811 (ewoc-invalidate stgit-ewoc node))
812 (stgit-next-patch))
813
814 (defun stgit-rename (name)
815 "Rename the patch under point to NAME."
816 (interactive (list
817 (read-string "Patch name: "
818 (symbol-name (stgit-patch-name-at-point t t)))))
819 (let ((old-patchsym (stgit-patch-name-at-point t t)))
820 (stgit-capture-output nil
821 (stgit-run "rename" old-patchsym name))
822 (let ((name-sym (intern name)))
823 (when (memq old-patchsym stgit-expanded-patches)
824 (setq stgit-expanded-patches
825 (cons name-sym (delq old-patchsym stgit-expanded-patches))))
826 (when (memq old-patchsym stgit-marked-patches)
827 (setq stgit-marked-patches
828 (cons name-sym (delq old-patchsym stgit-marked-patches))))
829 (stgit-reload)
830 (stgit-goto-patch name-sym))))
831
832 (defun stgit-reload-or-repair (repair)
833 "Update the contents of the StGit buffer (`stgit-reload').
834
835 With a prefix argument, repair the StGit metadata if the branch
836 was modified with git commands (`stgit-repair')."
837 (interactive "P")
838 (if repair
839 (stgit-repair)
840 (stgit-reload)))
841
842 (defun stgit-repair ()
843 "Run stg repair."
844 (interactive)
845 (stgit-capture-output nil
846 (stgit-run "repair"))
847 (stgit-reload))
848
849 (defun stgit-available-branches ()
850 "Returns a list of the available stg branches"
851 (let ((output (with-output-to-string
852 (stgit-run "branch" "--list")))
853 (start 0)
854 result)
855 (while (string-match "^>?\\s-+s\\s-+\\(\\S-+\\)" output start)
856 (setq result (cons (match-string 1 output) result))
857 (setq start (match-end 0)))
858 result))
859
860 (defun stgit-branch (branch)
861 "Switch to branch BRANCH."
862 (interactive (list (completing-read "Switch to branch: "
863 (stgit-available-branches))))
864 (stgit-capture-output nil (stgit-run "branch" "--" branch))
865 (stgit-reload))
866
867 (defun stgit-commit (count)
868 "Run stg commit on COUNT commits.
869 Interactively, the prefix argument is used as COUNT."
870 (interactive "p")
871 (stgit-capture-output nil (stgit-run "commit" "-n" count))
872 (stgit-reload))
873
874 (defun stgit-revert-file ()
875 "Revert the file at point, which must be in the index or the
876 working tree."
877 (interactive)
878 (let* ((patched-file (or (stgit-patched-file-at-point)
879 (error "No file on the current line")))
880 (patch-name (stgit-patch-name-at-point))
881 (file-status (stgit-file-status patched-file))
882 (rm-file (cond ((stgit-file-copy-or-rename patched-file)
883 (stgit-file-cr-to patched-file))
884 ((eq file-status 'add)
885 (stgit-file-file patched-file))))
886 (co-file (cond ((eq file-status 'rename)
887 (stgit-file-cr-from patched-file))
888 ((not (memq file-status '(copy add)))
889 (stgit-file-file patched-file)))))
890
891 (unless (memq patch-name '(:work :index))
892 (error "No index or working tree file on this line"))
893
894 (let ((nfiles (+ (if rm-file 1 0) (if co-file 1 0))))
895 (when (yes-or-no-p (format "Revert %d file%s? "
896 nfiles
897 (if (= nfiles 1) "" "s")))
898 (stgit-capture-output nil
899 (when rm-file
900 (stgit-run-git "rm" "-f" "-q" "--" rm-file))
901 (when co-file
902 (stgit-run-git "checkout" "HEAD" co-file)))
903 (stgit-reload)))))
904
905 (defun stgit-resolve-file ()
906 "Resolve conflict in the file at point."
907 (interactive)
908 (let* ((patched-file (stgit-patched-file-at-point))
909 (patch (stgit-patch-at-point))
910 (patch-name (and patch (stgit-patch-name patch)))
911 (status (and patched-file (stgit-file-status patched-file))))
912
913 (unless (memq patch-name '(:work :index))
914 (error "No index or working tree file on this line"))
915
916 (unless (eq status 'unmerged)
917 (error "No conflict to resolve at the current line"))
918
919 (stgit-capture-output nil
920 (stgit-move-change-to-index (stgit-file-file patched-file)))
921
922 (stgit-reload)))
923
924 (defun stgit-uncommit (count)
925 "Run stg uncommit on COUNT commits.
926 Interactively, the prefix argument is used as COUNT."
927 (interactive "p")
928 (stgit-capture-output nil (stgit-run "uncommit" "-n" count))
929 (stgit-reload))
930
931 (defun stgit-push-next (npatches)
932 "Push the first unapplied patch.
933 With numeric prefix argument, push that many patches."
934 (interactive "p")
935 (stgit-capture-output nil (stgit-run "push" "-n" npatches))
936 (stgit-reload)
937 (stgit-refresh-git-status))
938
939 (defun stgit-pop-next (npatches)
940 "Pop the topmost applied patch.
941 With numeric prefix argument, pop that many patches."
942 (interactive "p")
943 (stgit-capture-output nil (stgit-run "pop" "-n" npatches))
944 (stgit-reload)
945 (stgit-refresh-git-status))
946
947 (defun stgit-applied-at-point ()
948 "Is the patch on the current line applied?"
949 (save-excursion
950 (beginning-of-line)
951 (looking-at "[>+]")))
952
953 (defun stgit-push-or-pop ()
954 "Push or pop the patch on the current line."
955 (interactive)
956 (let ((patchsym (stgit-patch-name-at-point t))
957 (applied (stgit-applied-at-point)))
958 (stgit-capture-output nil
959 (stgit-run (if applied "pop" "push") patchsym))
960 (stgit-reload)))
961
962 (defun stgit-goto ()
963 "Go to the patch on the current line."
964 (interactive)
965 (let ((patchsym (stgit-patch-name-at-point t)))
966 (stgit-capture-output nil
967 (stgit-run "goto" patchsym))
968 (stgit-reload)))
969
970 (defun stgit-id (patchsym)
971 "Return the git commit id for PATCHSYM.
972 If PATCHSYM is a keyword, returns PATCHSYM unmodified."
973 (if (keywordp patchsym)
974 patchsym
975 (let ((result (with-output-to-string
976 (stgit-run-silent "id" patchsym))))
977 (unless (string-match "^\\([0-9A-Fa-f]\\{40\\}\\)$" result)
978 (error "Cannot find commit id for %s" patchsym))
979 (match-string 1 result))))
980
981 (defun stgit-show ()
982 "Show the patch on the current line."
983 (interactive)
984 (stgit-capture-output "*StGit patch*"
985 (case (get-text-property (point) 'entry-type)
986 ('file
987 (let* ((patched-file (stgit-patched-file-at-point))
988 (patch-name (stgit-patch-name-at-point))
989 (patch-id (stgit-id patch-name))
990 (args (append (and (stgit-file-cr-from patched-file)
991 (list (stgit-find-copies-harder-diff-arg)))
992 (cond ((eq patch-id :index)
993 '("--cached"))
994 ((eq patch-id :work)
995 nil)
996 (t
997 (list (concat patch-id "^") patch-id)))
998 '("--")
999 (if (stgit-file-copy-or-rename patched-file)
1000 (list (stgit-file-cr-from patched-file)
1001 (stgit-file-cr-to patched-file))
1002 (list (stgit-file-file patched-file))))))
1003 (apply 'stgit-run-git "diff" args)))
1004 ('patch
1005 (let* ((patch-name (stgit-patch-name-at-point))
1006 (patch-id (stgit-id patch-name)))
1007 (if (or (eq patch-id :index) (eq patch-id :work))
1008 (apply 'stgit-run-git "diff"
1009 (stgit-find-copies-harder-diff-arg)
1010 (and (eq patch-id :index)
1011 '("--cached")))
1012 (stgit-run "show" "-O" "--patch-with-stat" "-O" "-M"
1013 (stgit-patch-name-at-point)))))
1014 (t
1015 (error "No patch or file at point")))
1016 (with-current-buffer standard-output
1017 (goto-char (point-min))
1018 (diff-mode))))
1019
1020 (defun stgit-move-change-to-index (file)
1021 "Copies the workspace state of FILE to index, using git add or git rm"
1022 (let ((op (if (or (file-exists-p file) (file-symlink-p file))
1023 '("add") '("rm" "-q"))))
1024 (stgit-capture-output "*git output*"
1025 (apply 'stgit-run-git (append op '("--") (list file))))))
1026
1027 (defun stgit-remove-change-from-index (file)
1028 "Unstages the change in FILE from the index"
1029 (stgit-capture-output "*git output*"
1030 (stgit-run-git "reset" "-q" "--" file)))
1031
1032 (defun stgit-file-toggle-index ()
1033 "Move modified file in or out of the index."
1034 (interactive)
1035 (let ((patched-file (stgit-patched-file-at-point)))
1036 (unless patched-file
1037 (error "No file on the current line"))
1038 (when (eq (stgit-file-status patched-file) 'unmerged)
1039 (error (substitute-command-keys "Use \\[stgit-resolve-file] to move an unmerged file to the index")))
1040 (let ((patch-name (stgit-patch-name-at-point)))
1041 (cond ((eq patch-name :work)
1042 (stgit-move-change-to-index (stgit-file-file patched-file)))
1043 ((eq patch-name :index)
1044 (stgit-remove-change-from-index (stgit-file-file patched-file)))
1045 (t
1046 (error "Can only move files in the working tree to index")))))
1047 (stgit-refresh-worktree)
1048 (stgit-refresh-index))
1049
1050 (defun stgit-edit ()
1051 "Edit the patch on the current line."
1052 (interactive)
1053 (let ((patchsym (stgit-patch-name-at-point t t))
1054 (edit-buf (get-buffer-create "*StGit edit*"))
1055 (dir default-directory))
1056 (log-edit 'stgit-confirm-edit t nil edit-buf)
1057 (set (make-local-variable 'stgit-edit-patchsym) patchsym)
1058 (setq default-directory dir)
1059 (let ((standard-output edit-buf))
1060 (stgit-run-silent "edit" "--save-template=-" patchsym))))
1061
1062 (defun stgit-confirm-edit ()
1063 (interactive)
1064 (let ((file (make-temp-file "stgit-edit-")))
1065 (write-region (point-min) (point-max) file)
1066 (stgit-capture-output nil
1067 (stgit-run "edit" "-f" file stgit-edit-patchsym))
1068 (with-current-buffer log-edit-parent-buffer
1069 (stgit-reload))))
1070
1071 (defun stgit-new (add-sign)
1072 "Create a new patch.
1073 With a prefix argument, include a \"Signed-off-by:\" line at the
1074 end of the patch."
1075 (interactive "P")
1076 (let ((edit-buf (get-buffer-create "*StGit edit*"))
1077 (dir default-directory))
1078 (log-edit 'stgit-confirm-new t nil edit-buf)
1079 (setq default-directory dir)
1080 (when add-sign
1081 (save-excursion
1082 (let ((standard-output (current-buffer)))
1083 (stgit-run-silent "new" "--sign" "--save-template=-"))))))
1084
1085 (defun stgit-confirm-new ()
1086 (interactive)
1087 (let ((file (make-temp-file "stgit-edit-")))
1088 (write-region (point-min) (point-max) file)
1089 (stgit-capture-output nil
1090 (stgit-run "new" "-f" file))
1091 (with-current-buffer log-edit-parent-buffer
1092 (stgit-reload))))
1093
1094 (defun stgit-create-patch-name (description)
1095 "Create a patch name from a long description"
1096 (let ((patch ""))
1097 (while (> (length description) 0)
1098 (cond ((string-match "\\`[a-zA-Z_-]+" description)
1099 (setq patch (downcase (concat patch
1100 (match-string 0 description))))
1101 (setq description (substring description (match-end 0))))
1102 ((string-match "\\` +" description)
1103 (setq patch (concat patch "-"))
1104 (setq description (substring description (match-end 0))))
1105 ((string-match "\\`[^a-zA-Z_-]+" description)
1106 (setq description (substring description (match-end 0))))))
1107 (cond ((= (length patch) 0)
1108 "patch")
1109 ((> (length patch) 20)
1110 (substring patch 0 20))
1111 (t patch))))
1112
1113 (defun stgit-delete (patchsyms &optional spill-p)
1114 "Delete the patches in PATCHSYMS.
1115 Interactively, delete the marked patches, or the patch at point.
1116
1117 With a prefix argument, or SPILL-P, spill the patch contents to
1118 the work tree and index."
1119 (interactive (list (stgit-patches-marked-or-at-point)
1120 current-prefix-arg))
1121 (unless patchsyms
1122 (error "No patches to delete"))
1123 (when (memq :index patchsyms)
1124 (error "Cannot delete the index"))
1125 (when (memq :work patchsyms)
1126 (error "Cannot delete the work tree"))
1127
1128 (let ((npatches (length patchsyms)))
1129 (when (yes-or-no-p (format "Really delete %d patch%s%s? "
1130 npatches
1131 (if (= 1 npatches) "" "es")
1132 (if spill-p
1133 " (spilling contents to index)"
1134 "")))
1135 (let ((args (if spill-p
1136 (cons "--spill" patchsyms)
1137 patchsyms)))
1138 (stgit-capture-output nil
1139 (apply 'stgit-run "delete" args))
1140 (stgit-reload)))))
1141
1142 (defun stgit-move-patches-target ()
1143 "Return the patchsym indicating a target patch for
1144 `stgit-move-patches'.
1145
1146 This is either the patch at point, or one of :top and :bottom, if
1147 the point is after or before the applied patches."
1148
1149 (let ((patchsym (stgit-patch-name-at-point)))
1150 (cond (patchsym patchsym)
1151 ((save-excursion (re-search-backward "^>" nil t)) :top)
1152 (t :bottom))))
1153
1154 (defun stgit-sort-patches (patchsyms)
1155 "Returns the list of patches in PATCHSYMS sorted according to
1156 their position in the patch series, bottommost first.
1157
1158 PATCHSYMS may not contain duplicate entries."
1159 (let (sorted-patchsyms
1160 (series (with-output-to-string
1161 (with-current-buffer standard-output
1162 (stgit-run-silent "series" "--noprefix"))))
1163 start)
1164 (while (string-match "^\\(.+\\)" series start)
1165 (let ((patchsym (intern (match-string 1 series))))
1166 (when (memq patchsym patchsyms)
1167 (setq sorted-patchsyms (cons patchsym sorted-patchsyms))))
1168 (setq start (match-end 0)))
1169 (setq sorted-patchsyms (nreverse sorted-patchsyms))
1170
1171 (unless (= (length patchsyms) (length sorted-patchsyms))
1172 (error "Internal error"))
1173
1174 sorted-patchsyms))
1175
1176 (defun stgit-move-patches (patchsyms target-patch)
1177 "Move the patches in PATCHSYMS to below TARGET-PATCH.
1178 If TARGET-PATCH is :bottom or :top, move the patches to the
1179 bottom or top of the stack, respectively.
1180
1181 Interactively, move the marked patches to where the point is."
1182 (interactive (list stgit-marked-patches
1183 (stgit-move-patches-target)))
1184 (unless patchsyms
1185 (error "Need at least one patch to move"))
1186
1187 (unless target-patch
1188 (error "Point not at a patch"))
1189
1190 (if (eq target-patch :top)
1191 (stgit-capture-output nil
1192 (apply 'stgit-run "float" patchsyms))
1193
1194 ;; need to have patchsyms sorted by position in the stack
1195 (let ((sorted-patchsyms (stgit-sort-patches patchsyms)))
1196 (while sorted-patchsyms
1197 (setq sorted-patchsyms
1198 (and (stgit-capture-output nil
1199 (if (eq target-patch :bottom)
1200 (stgit-run "sink" "--" (car sorted-patchsyms))
1201 (stgit-run "sink" "--to" target-patch "--"
1202 (car sorted-patchsyms))))
1203 (cdr sorted-patchsyms))))))
1204 (stgit-reload))
1205
1206 (defun stgit-squash (patchsyms)
1207 "Squash the patches in PATCHSYMS.
1208 Interactively, squash the marked patches.
1209
1210 Unless there are any conflicts, the patches will be merged into
1211 one patch, which will occupy the same spot in the series as the
1212 deepest patch had before the squash."
1213 (interactive (list stgit-marked-patches))
1214 (when (< (length patchsyms) 2)
1215 (error "Need at least two patches to squash"))
1216 (let ((stgit-buffer (current-buffer))
1217 (edit-buf (get-buffer-create "*StGit edit*"))
1218 (dir default-directory)
1219 (sorted-patchsyms (stgit-sort-patches patchsyms)))
1220 (log-edit 'stgit-confirm-squash t nil edit-buf)
1221 (set (make-local-variable 'stgit-patchsyms) sorted-patchsyms)
1222 (setq default-directory dir)
1223 (let ((result (let ((standard-output edit-buf))
1224 (apply 'stgit-run-silent "squash"
1225 "--save-template=-" sorted-patchsyms))))
1226
1227 ;; stg squash may have reordered the patches or caused conflicts
1228 (with-current-buffer stgit-buffer
1229 (stgit-reload))
1230
1231 (unless (eq 0 result)
1232 (fundamental-mode)
1233 (rename-buffer "*StGit error*")
1234 (resize-temp-buffer-window)
1235 (switch-to-buffer-other-window stgit-buffer)
1236 (error "stg squash failed")))))
1237
1238 (defun stgit-confirm-squash ()
1239 (interactive)
1240 (let ((file (make-temp-file "stgit-edit-")))
1241 (write-region (point-min) (point-max) file)
1242 (stgit-capture-output nil
1243 (apply 'stgit-run "squash" "-f" file stgit-patchsyms))
1244 (with-current-buffer log-edit-parent-buffer
1245 (stgit-clear-marks)
1246 ;; Go to first marked patch and stay there
1247 (goto-char (point-min))
1248 (re-search-forward (concat "^[>+-]\\*") nil t)
1249 (move-to-column goal-column)
1250 (let ((pos (point)))
1251 (stgit-reload)
1252 (goto-char pos)))))
1253
1254 (defun stgit-help ()
1255 "Display help for the StGit mode."
1256 (interactive)
1257 (describe-function 'stgit-mode))
1258
1259 (defun stgit-undo (&optional arg)
1260 "Run stg undo.
1261 With prefix argument, run it with the --hard flag."
1262 (interactive "P")
1263 (stgit-capture-output nil
1264 (if arg
1265 (stgit-run "undo" "--hard")
1266 (stgit-run "undo")))
1267 (stgit-reload))
1268
1269 (defun stgit-refresh (&optional arg)
1270 "Run stg refresh.
1271 If the index contains any changes, only refresh from index.
1272
1273 With prefix argument, refresh the marked patch or the patch under point."
1274 (interactive "P")
1275 (let ((patchargs (if arg
1276 (let ((patches (stgit-patches-marked-or-at-point)))
1277 (cond ((null patches)
1278 (error "No patch to update"))
1279 ((> (length patches) 1)
1280 (error "Too many patches selected"))
1281 (t
1282 (cons "-p" patches))))
1283 nil)))
1284 (unless (stgit-index-empty-p)
1285 (setq patchargs (cons "--index" patchargs)))
1286 (stgit-capture-output nil
1287 (apply 'stgit-run "refresh" patchargs))
1288 (stgit-refresh-git-status))
1289 (stgit-reload))
1290
1291 (defcustom stgit-show-worktree-mode 'center
1292 "This variable controls where the \"Index\" and \"Work tree\"
1293 will be shown on in the buffer.
1294
1295 It can be set to 'top (above all patches), 'center (show between
1296 applied and unapplied patches), and 'bottom (below all patches).
1297
1298 See also `stgit-show-worktree'."
1299 :type '(radio (const :tag "above all patches (top)" top)
1300 (const :tag "between applied and unapplied patches (center)"
1301 center)
1302 (const :tag "below all patches (bottom)" bottom))
1303 :group 'stgit)
1304
1305 (defcustom stgit-default-show-worktree
1306 nil
1307 "Set to non-nil to by default show the working tree in a new stgit buffer.
1308
1309 This value is used as the default value for `stgit-show-worktree'."
1310 :type 'boolean
1311 :group 'stgit)
1312
1313 (defvar stgit-show-worktree nil
1314 "If nil, inhibit showing work tree and index in the stgit buffer.
1315
1316 See also `stgit-show-worktree-mode'.")
1317
1318 (defun stgit-toggle-worktree (&optional arg)
1319 "Toggle the visibility of the work tree.
1320 With arg, show the work tree if arg is positive.
1321
1322 Its initial setting is controlled by `stgit-default-show-worktree'.
1323
1324 `stgit-show-worktree-mode' controls where on screen the index and
1325 work tree will show up."
1326 (interactive)
1327 (setq stgit-show-worktree
1328 (if (numberp arg)
1329 (> arg 0)
1330 (not stgit-show-worktree)))
1331 (stgit-reload))
1332
1333 (provide 'stgit)