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