stg mail: Improve error message for unknown sender identity
[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
GH
12(require 'git nil t)
13
56d81fe5 14(defun stgit (dir)
a53347d9 15 "Manage StGit patches for the tree in DIR."
56d81fe5 16 (interactive "DDirectory: \n")
52144ce5 17 (switch-to-stgit-buffer (git-get-top-dir dir))
1f0bf00f 18 (stgit-reload))
56d81fe5 19
074a4fb0
GH
20(unless (fboundp 'git-get-top-dir)
21 (defun git-get-top-dir (dir)
22 "Retrieve the top-level directory of a git tree."
23 (let ((cdup (with-output-to-string
24 (with-current-buffer standard-output
25 (cd dir)
26 (unless (eq 0 (call-process "git" nil t nil
27 "rev-parse" "--show-cdup"))
28 (error "cannot find top-level git tree for %s." dir))))))
29 (expand-file-name (concat (file-name-as-directory dir)
30 (car (split-string cdup "\n")))))))
31
32(defun stgit-refresh-git-status (&optional dir)
33 "If it exists, refresh the `git-status' buffer belonging to
34directory DIR or `default-directory'"
35 (when (and (fboundp 'git-find-status-buffer)
36 (fboundp 'git-refresh-status))
37 (let* ((top-dir (git-get-top-dir (or dir default-directory)))
38 (git-status-buffer (and top-dir (git-find-status-buffer top-dir))))
39 (when git-status-buffer
40 (with-current-buffer git-status-buffer
41 (git-refresh-status))))))
52144ce5 42
56d81fe5 43(defun switch-to-stgit-buffer (dir)
a53347d9 44 "Switch to a (possibly new) buffer displaying StGit patches for DIR."
56d81fe5
DK
45 (setq dir (file-name-as-directory dir))
46 (let ((buffers (buffer-list)))
47 (while (and buffers
48 (not (with-current-buffer (car buffers)
49 (and (eq major-mode 'stgit-mode)
50 (string= default-directory dir)))))
51 (setq buffers (cdr buffers)))
52 (switch-to-buffer (if buffers
53 (car buffers)
54 (create-stgit-buffer dir)))))
55
56(defun create-stgit-buffer (dir)
57 "Create a buffer for showing StGit patches.
58Argument DIR is the repository path."
59 (let ((buf (create-file-buffer (concat dir "*stgit*")))
60 (inhibit-read-only t))
61 (with-current-buffer buf
62 (setq default-directory dir)
63 (stgit-mode)
64 (setq buffer-read-only t))
65 buf))
66
67(defmacro stgit-capture-output (name &rest body)
a53347d9 68 "Capture StGit output and show it in a window at the end."
34afb86c
DK
69 `(let ((output-buf (get-buffer-create ,(or name "*StGit output*")))
70 (stgit-dir default-directory)
71 (inhibit-read-only t))
56d81fe5 72 (with-current-buffer output-buf
34afb86c
DK
73 (erase-buffer)
74 (setq default-directory stgit-dir)
75 (setq buffer-read-only t))
56d81fe5
DK
76 (let ((standard-output output-buf))
77 ,@body)
34afb86c
DK
78 (with-current-buffer output-buf
79 (set-buffer-modified-p nil)
80 (setq buffer-read-only t)
81 (if (< (point-min) (point-max))
82 (display-buffer output-buf t)))))
56d81fe5
DK
83(put 'stgit-capture-output 'lisp-indent-function 1)
84
9aecd505 85(defun stgit-run-silent (&rest args)
56d81fe5
DK
86 (apply 'call-process "stg" nil standard-output nil args))
87
9aecd505
DK
88(defun stgit-run (&rest args)
89 (let ((msgcmd (mapconcat #'identity args " ")))
90 (message "Running stg %s..." msgcmd)
91 (apply 'call-process "stg" nil standard-output nil args)
92 (message "Running stg %s...done" msgcmd)))
93
378a003d
GH
94(defun stgit-run-git (&rest args)
95 (let ((msgcmd (mapconcat #'identity args " ")))
96 (message "Running git %s..." msgcmd)
97 (apply 'call-process "git" nil standard-output nil args)
98 (message "Running git %s...done" msgcmd)))
99
1f60181a
GH
100(defun stgit-run-git-silent (&rest args)
101 (apply 'call-process "git" nil standard-output nil args))
102
1f0bf00f 103(defun stgit-reload ()
a53347d9 104 "Update the contents of the StGit buffer."
56d81fe5
DK
105 (interactive)
106 (let ((inhibit-read-only t)
107 (curline (line-number-at-pos))
108 (curpatch (stgit-patch-at-point)))
109 (erase-buffer)
110 (insert "Branch: ")
9aecd505
DK
111 (stgit-run-silent "branch")
112 (stgit-run-silent "series" "--description")
6df83d42 113 (stgit-rescan)
56d81fe5
DK
114 (if curpatch
115 (stgit-goto-patch curpatch)
074a4fb0
GH
116 (goto-line curline)))
117 (stgit-refresh-git-status))
56d81fe5 118
8f40753a
GH
119(defgroup stgit nil
120 "A user interface for the StGit patch maintenance tool."
121 :group 'tools)
122
07f464e0
DK
123(defface stgit-description-face
124 '((((background dark)) (:foreground "tan"))
125 (((background light)) (:foreground "dark red")))
8f40753a
GH
126 "The face used for StGit descriptions"
127 :group 'stgit)
07f464e0
DK
128
129(defface stgit-top-patch-face
130 '((((background dark)) (:weight bold :foreground "yellow"))
131 (((background light)) (:weight bold :foreground "purple"))
132 (t (:weight bold)))
8f40753a
GH
133 "The face used for the top patch names"
134 :group 'stgit)
07f464e0
DK
135
136(defface stgit-applied-patch-face
137 '((((background dark)) (:foreground "light yellow"))
138 (((background light)) (:foreground "purple"))
139 (t ()))
8f40753a
GH
140 "The face used for applied patch names"
141 :group 'stgit)
07f464e0
DK
142
143(defface stgit-unapplied-patch-face
144 '((((background dark)) (:foreground "gray80"))
145 (((background light)) (:foreground "orchid"))
146 (t ()))
8f40753a
GH
147 "The face used for unapplied patch names"
148 :group 'stgit)
07f464e0 149
1f60181a
GH
150(defface stgit-modified-file-face
151 '((((class color) (background light)) (:foreground "purple"))
152 (((class color) (background dark)) (:foreground "salmon")))
153 "StGit mode face used for modified file status"
154 :group 'stgit)
155
156(defface stgit-unmerged-file-face
157 '((((class color) (background light)) (:foreground "red" :bold t))
158 (((class color) (background dark)) (:foreground "red" :bold t)))
159 "StGit mode face used for unmerged file status"
160 :group 'stgit)
161
162(defface stgit-unknown-file-face
163 '((((class color) (background light)) (:foreground "goldenrod" :bold t))
164 (((class color) (background dark)) (:foreground "goldenrod" :bold t)))
165 "StGit mode face used for unknown file status"
166 :group 'stgit)
167
168(defcustom stgit-expand-find-copies-harder
169 nil
170 "Try harder to find copied files when listing patches.
171
172When not nil, runs git diff-tree with the --find-copies-harder
173flag, which reduces performance."
174 :type 'boolean
175 :group 'stgit)
176
177(defconst stgit-file-status-code-strings
178 (mapcar (lambda (arg)
179 (cons (car arg)
180 (format "%-12s"
181 (propertize (cadr arg) 'face (car (cddr arg))))))
182 '((add "Added" stgit-modified-file-face)
183 (copy "Copied" stgit-modified-file-face)
184 (delete "Deleted" stgit-modified-file-face)
185 (modify "Modified" stgit-modified-file-face)
186 (rename "Renamed" stgit-modified-file-face)
187 (mode-change "Mode changed" stgit-modified-file-face)
188 (unmerged "Unmerged" stgit-unmerged-file-face)
189 (unknown "Unknown" stgit-unknown-file-face)))
190 "Alist of code symbols to description strings")
191
192(defun stgit-file-status-code-as-string (code)
193 "Return stgit status code as string"
194 (let ((str (assq code stgit-file-status-code-strings)))
195 (and str (cdr str))))
196
197(defun stgit-file-status-code (str)
198 "Return stgit status code from git status string"
199 (let ((code (assoc str '(("A" . add)
200 ("C" . copy)
201 ("D" . delete)
202 ("M" . modify)
203 ("R" . rename)
204 ("T" . mode-change)
205 ("U" . unmerged)
206 ("X" . unknown)))))
207 (if code (cdr code) 'unknown)))
208
378a003d
GH
209(defun stgit-expand-patch (patchsym)
210 (save-excursion
211 (forward-line)
1f60181a
GH
212 (let* ((start (point))
213 (result (with-output-to-string
214 (stgit-run-git "diff-tree" "-r" "-z"
215 (if stgit-expand-find-copies-harder
216 "--find-copies-harder"
217 "-C")
218 (stgit-id (symbol-name patchsym))))))
219 (let (mstart)
220 (while (string-match "\0:[0-7]+ [0-7]+ [0-9A-Fa-f]\\{40\\} [0-9A-Fa-f]\\{40\\} \\(\\([CR]\\)\\([0-9]*\\)\0\\([^\0]*\\)\0\\([^\0]*\\)\\|\\([ABD-QS-Z]\\)\0\\([^\0]*\\)\\)"
221 result mstart)
222 (let ((copy-or-rename (match-string 2 result))
223 (line-start (point))
224 properties)
225 (insert " ")
226 (if copy-or-rename
227 (let ((cr-score (match-string 3 result))
228 (cr-from-file (match-string 4 result))
229 (cr-to-file (match-string 5 result)))
230 (setq properties (list 'stgit-old-file cr-from-file
231 'stgit-new-file cr-to-file))
232 (insert (stgit-file-status-code-as-string
233 (if (equal "C" copy-or-rename) 'copy 'rename))
234 cr-from-file
235 (propertize " -> " 'face 'stgit-description-face)
236 cr-to-file))
237 (let ((status (stgit-file-status-code (match-string 6 result)))
238 (file (match-string 7 result)))
239 (setq properties (list 'stgit-file file))
240 (insert (stgit-file-status-code-as-string status) file)))
241 (insert ?\n)
242 (add-text-properties line-start (point) properties))
243 (setq mstart (match-end 0))))
244 (when (= start (point))
245 (insert " <no files>\n"))
246 (put-text-property start (point) 'stgit-patchsym patchsym))))
378a003d 247
6df83d42
DK
248(defun stgit-rescan ()
249 "Rescan the status buffer."
07f464e0 250 (save-excursion
6df83d42
DK
251 (let ((marked ()))
252 (goto-char (point-min))
253 (while (not (eobp))
254 (cond ((looking-at "Branch: \\(.*\\)")
255 (put-text-property (match-beginning 1) (match-end 1)
256 'face 'bold))
8ee1e4b4 257 ((looking-at "\\([>+-]\\)\\( \\)\\([^ ]+\\) *[|#] \\(.*\\)")
6df83d42
DK
258 (let ((state (match-string 1))
259 (patchsym (intern (match-string 3))))
260 (put-text-property
261 (match-beginning 3) (match-end 3) 'face
262 (cond ((string= state ">") 'stgit-top-patch-face)
263 ((string= state "+") 'stgit-applied-patch-face)
264 ((string= state "-") 'stgit-unapplied-patch-face)))
265 (put-text-property (match-beginning 4) (match-end 4)
266 'face 'stgit-description-face)
267 (when (memq patchsym stgit-marked-patches)
268 (replace-match "*" nil nil nil 2)
378a003d
GH
269 (setq marked (cons patchsym marked)))
270 (when (memq patchsym stgit-expanded-patches)
271 (stgit-expand-patch patchsym))
272 ))
ad80ce22
DK
273 ((or (looking-at "stg series: Branch \".*\" not initialised")
274 (looking-at "stg series: .*: branch not initialized"))
1c2426dc
DK
275 (forward-line 1)
276 (insert "Run M-x stgit-init to initialise")))
6df83d42
DK
277 (forward-line 1))
278 (setq stgit-marked-patches (nreverse marked)))))
07f464e0 279
378a003d
GH
280(defun stgit-select ()
281 "Expand or collapse the current entry"
282 (interactive)
283 (let ((curpatch (stgit-patch-at-point)))
284 (if (not curpatch)
285 (let ((patched-file (stgit-patched-file-at-point)))
286 (unless patched-file
287 (error "No patch or file on the current line"))
288 (let ((filename (expand-file-name (cdr patched-file))))
289 (unless (file-exists-p filename)
290 (error "File does not exist"))
291 (find-file filename)))
292 (setq curpatch (intern curpatch))
293 (setq stgit-expanded-patches
294 (if (memq curpatch stgit-expanded-patches)
295 (delq curpatch stgit-expanded-patches)
296 (cons curpatch stgit-expanded-patches)))
297 (stgit-reload))))
298
299(defun stgit-find-file-other-window ()
300 "Open file at point in other window"
301 (interactive)
302 (let ((patched-file (stgit-patched-file-at-point)))
303 (unless patched-file
304 (error "No file on the current line"))
305 (let ((filename (expand-file-name (cdr patched-file))))
306 (unless (file-exists-p filename)
307 (error "File does not exist"))
308 (find-file-other-window filename))))
309
83327d53 310(defun stgit-quit ()
a53347d9 311 "Hide the stgit buffer."
83327d53
GH
312 (interactive)
313 (bury-buffer))
314
0f076fe6 315(defun stgit-git-status ()
a53347d9 316 "Show status using `git-status'."
0f076fe6
GH
317 (interactive)
318 (unless (fboundp 'git-status)
319 (error "stgit-git-status requires git-status"))
320 (let ((dir default-directory))
321 (save-selected-window
322 (pop-to-buffer nil)
323 (git-status dir))))
324
378a003d
GH
325(defun stgit-next-line (&optional arg try-vscroll)
326 "Move cursor vertically down ARG lines"
327 (interactive "p\np")
328 (next-line arg try-vscroll)
329 (when (looking-at " \\S-")
330 (forward-char 2)))
331
332(defun stgit-previous-line (&optional arg try-vscroll)
333 "Move cursor vertically up ARG lines"
334 (interactive "p\np")
335 (previous-line arg try-vscroll)
336 (when (looking-at " \\S-")
337 (forward-char 2)))
338
339(defun stgit-next-patch (&optional arg)
340 "Move cursor down ARG patches"
341 (interactive "p")
342 (unless arg
343 (setq arg 1))
344 (if (< arg 0)
345 (stgit-previous-patch (- arg))
346 (while (not (zerop arg))
347 (setq arg (1- arg))
348 (while (progn (stgit-next-line)
349 (not (stgit-patch-at-point)))))))
350
351(defun stgit-previous-patch (&optional arg)
352 "Move cursor up ARG patches"
353 (interactive "p")
354 (unless arg
355 (setq arg 1))
356 (if (< arg 0)
357 (stgit-next-patch (- arg))
358 (while (not (zerop arg))
359 (setq arg (1- arg))
360 (while (progn (stgit-previous-line)
361 (not (stgit-patch-at-point)))))))
362
56d81fe5
DK
363(defvar stgit-mode-hook nil
364 "Run after `stgit-mode' is setup.")
365
366(defvar stgit-mode-map nil
367 "Keymap for StGit major mode.")
368
369(unless stgit-mode-map
370 (setq stgit-mode-map (make-keymap))
371 (suppress-keymap stgit-mode-map)
022a3664
GH
372 (mapc (lambda (arg) (define-key stgit-mode-map (car arg) (cdr arg)))
373 '((" " . stgit-mark)
3dccdc9b 374 ("m" . stgit-mark)
9b151b27
GH
375 ("\d" . stgit-unmark-up)
376 ("u" . stgit-unmark-down)
022a3664
GH
377 ("?" . stgit-help)
378 ("h" . stgit-help)
378a003d
GH
379 ("p" . stgit-previous-line)
380 ("n" . stgit-next-line)
381 ("\C-p" . stgit-previous-patch)
382 ("\C-n" . stgit-next-patch)
383 ("\M-{" . stgit-previous-patch)
384 ("\M-}" . stgit-next-patch)
0f076fe6 385 ("s" . stgit-git-status)
022a3664
GH
386 ("g" . stgit-reload)
387 ("r" . stgit-refresh)
388 ("\C-c\C-r" . stgit-rename)
389 ("e" . stgit-edit)
390 ("c" . stgit-coalesce)
391 ("N" . stgit-new)
392 ("R" . stgit-repair)
393 ("C" . stgit-commit)
394 ("U" . stgit-uncommit)
378a003d
GH
395 ("\r" . stgit-select)
396 ("o" . stgit-find-file-other-window)
022a3664
GH
397 (">" . stgit-push-next)
398 ("<" . stgit-pop-next)
399 ("P" . stgit-push-or-pop)
400 ("G" . stgit-goto)
401 ("=" . stgit-show)
402 ("D" . stgit-delete)
403 ([(control ?/)] . stgit-undo)
83327d53
GH
404 ("\C-_" . stgit-undo)
405 ("q" . stgit-quit))))
56d81fe5
DK
406
407(defun stgit-mode ()
408 "Major mode for interacting with StGit.
409Commands:
410\\{stgit-mode-map}"
411 (kill-all-local-variables)
412 (buffer-disable-undo)
413 (setq mode-name "StGit"
414 major-mode 'stgit-mode
415 goal-column 2)
416 (use-local-map stgit-mode-map)
417 (set (make-local-variable 'list-buffers-directory) default-directory)
6df83d42 418 (set (make-local-variable 'stgit-marked-patches) nil)
378a003d 419 (set (make-local-variable 'stgit-expanded-patches) nil)
2870f8b8 420 (set-variable 'truncate-lines 't)
56d81fe5
DK
421 (run-hooks 'stgit-mode-hook))
422
6df83d42
DK
423(defun stgit-add-mark (patch)
424 (let ((patchsym (intern patch)))
425 (setq stgit-marked-patches (cons patchsym stgit-marked-patches))))
426
427(defun stgit-remove-mark (patch)
428 (let ((patchsym (intern patch)))
429 (setq stgit-marked-patches (delq patchsym stgit-marked-patches))))
430
e6b1fdae
DK
431(defun stgit-clear-marks ()
432 (setq stgit-marked-patches '()))
433
6df83d42
DK
434(defun stgit-marked-patches ()
435 "Return the names of the marked patches."
436 (mapcar 'symbol-name stgit-marked-patches))
437
378a003d
GH
438(defun stgit-patch-at-point (&optional cause-error allow-file)
439 "Return the patch name on the current line.
440If CAUSE-ERROR is not nil, signal an error if none found.
441If ALLOW-FILE is not nil, also handle when point is on a file of
442a patch."
443 (or (and allow-file
444 (let ((patchsym (get-text-property (point) 'stgit-patchsym)))
445 (and patchsym
446 (symbol-name patchsym))))
447 (save-excursion
448 (beginning-of-line)
449 (and (looking-at "[>+-][ *]\\([^ ]*\\)")
450 (match-string-no-properties 1)))
451 (and cause-error
452 (error "No patch on this line"))))
453
1f60181a
GH
454(defun stgit-patched-file-at-point (&optional both-files)
455 "Returns a cons of the patchsym and file name at point. For
456copies and renames, return the new file if the patch is either
457applied. If BOTH-FILES is non-nil, return a cons of the old and
458the new file names instead of just one name."
459 (let ((patchsym (get-text-property (point) 'stgit-patchsym))
460 (file (get-text-property (point) 'stgit-file)))
461 (cond ((not patchsym) nil)
462 (file (cons patchsym file))
463 (both-files
464 (cons patchsym (cons (get-text-property (point) 'stgit-old-file)
465 (get-text-property (point) 'stgit-new-file))))
466 (t
467 (let ((file-sym (save-excursion
468 (stgit-previous-patch)
469 (unless (equal (stgit-patch-at-point)
470 (symbol-name patchsym))
471 (error "Cannot find the %s patch" patchsym))
472 (beginning-of-line)
473 (if (= (char-after) ?-)
474 'stgit-old-file
475 'stgit-new-file))))
476 (cons patchsym (get-text-property (point) file-sym)))))))
56d81fe5 477
7755d7f1
KH
478(defun stgit-patches-marked-or-at-point ()
479 "Return the names of the marked patches, or the patch on the current line."
480 (if stgit-marked-patches
481 (stgit-marked-patches)
482 (let ((patch (stgit-patch-at-point)))
483 (if patch
484 (list patch)
485 '()))))
486
56d81fe5 487(defun stgit-goto-patch (patch)
a53347d9 488 "Move point to the line containing PATCH."
56d81fe5
DK
489 (let ((p (point)))
490 (goto-char (point-min))
6df83d42 491 (if (re-search-forward (concat "^[>+-][ *]" (regexp-quote patch) " ") nil t)
56d81fe5
DK
492 (progn (move-to-column goal-column)
493 t)
494 (goto-char p)
495 nil)))
496
1c2426dc 497(defun stgit-init ()
a53347d9 498 "Run stg init."
1c2426dc
DK
499 (interactive)
500 (stgit-capture-output nil
b0424080 501 (stgit-run "init"))
1f0bf00f 502 (stgit-reload))
1c2426dc 503
6df83d42 504(defun stgit-mark ()
a53347d9 505 "Mark the patch under point."
6df83d42 506 (interactive)
018fa1ac 507 (let ((patch (stgit-patch-at-point t)))
6df83d42 508 (stgit-add-mark patch)
1f0bf00f 509 (stgit-reload))
378a003d 510 (stgit-next-patch))
6df83d42 511
9b151b27 512(defun stgit-unmark-up ()
a53347d9 513 "Remove mark from the patch on the previous line."
6df83d42 514 (interactive)
378a003d 515 (stgit-previous-patch)
018fa1ac 516 (stgit-remove-mark (stgit-patch-at-point t))
9b151b27
GH
517 (stgit-reload))
518
519(defun stgit-unmark-down ()
a53347d9 520 "Remove mark from the patch on the current line."
9b151b27 521 (interactive)
018fa1ac 522 (stgit-remove-mark (stgit-patch-at-point t))
378a003d 523 (stgit-next-patch)
9b151b27 524 (stgit-reload))
6df83d42 525
56d81fe5 526(defun stgit-rename (name)
018fa1ac
GH
527 "Rename the patch under point to NAME."
528 (interactive (list (read-string "Patch name: " (stgit-patch-at-point t))))
529 (let ((old-name (stgit-patch-at-point t)))
56d81fe5
DK
530 (stgit-capture-output nil
531 (stgit-run "rename" old-name name))
378a003d
GH
532 (let ((old-name-sym (intern old-name))
533 (name-sym (intern name)))
534 (when (memq old-name-sym stgit-expanded-patches)
535 (setq stgit-expanded-patches
536 (cons name-sym (delq old-name-sym stgit-expanded-patches))))
537 (when (memq old-name-sym stgit-marked-patches)
538 (setq stgit-marked-patches
539 (cons name-sym (delq old-name-sym stgit-marked-patches)))))
1f0bf00f 540 (stgit-reload)
56d81fe5
DK
541 (stgit-goto-patch name)))
542
26201d96 543(defun stgit-repair ()
a53347d9 544 "Run stg repair."
26201d96
DK
545 (interactive)
546 (stgit-capture-output nil
b0424080 547 (stgit-run "repair"))
1f0bf00f 548 (stgit-reload))
26201d96 549
c4aad9a7
DK
550(defun stgit-commit ()
551 "Run stg commit."
552 (interactive)
553 (stgit-capture-output nil (stgit-run "commit"))
1f0bf00f 554 (stgit-reload))
c4aad9a7
DK
555
556(defun stgit-uncommit (arg)
557 "Run stg uncommit. Numeric arg determines number of patches to uncommit."
558 (interactive "p")
559 (stgit-capture-output nil (stgit-run "uncommit" "-n" (number-to-string arg)))
1f0bf00f 560 (stgit-reload))
c4aad9a7 561
0b661144
DK
562(defun stgit-push-next (npatches)
563 "Push the first unapplied patch.
564With numeric prefix argument, push that many patches."
565 (interactive "p")
566 (stgit-capture-output nil (stgit-run "push" "-n"
567 (number-to-string npatches)))
074a4fb0
GH
568 (stgit-reload)
569 (stgit-refresh-git-status))
56d81fe5 570
0b661144
DK
571(defun stgit-pop-next (npatches)
572 "Pop the topmost applied patch.
573With numeric prefix argument, pop that many patches."
574 (interactive "p")
575 (stgit-capture-output nil (stgit-run "pop" "-n" (number-to-string npatches)))
074a4fb0
GH
576 (stgit-reload)
577 (stgit-refresh-git-status))
56d81fe5 578
f9182fca
KH
579(defun stgit-applied-at-point ()
580 "Is the patch on the current line applied?"
581 (save-excursion
582 (beginning-of-line)
583 (looking-at "[>+]")))
584
585(defun stgit-push-or-pop ()
a53347d9 586 "Push or pop the patch on the current line."
f9182fca 587 (interactive)
018fa1ac 588 (let ((patch (stgit-patch-at-point t))
f9182fca
KH
589 (applied (stgit-applied-at-point)))
590 (stgit-capture-output nil
b0424080 591 (stgit-run (if applied "pop" "push") patch))
1f0bf00f 592 (stgit-reload)))
f9182fca 593
c7adf5ef 594(defun stgit-goto ()
a53347d9 595 "Go to the patch on the current line."
c7adf5ef 596 (interactive)
018fa1ac 597 (let ((patch (stgit-patch-at-point t)))
c7adf5ef 598 (stgit-capture-output nil
b0424080 599 (stgit-run "goto" patch))
1f0bf00f 600 (stgit-reload)))
c7adf5ef 601
378a003d
GH
602(defun stgit-id (patch)
603 "Return the git commit id for PATCH"
604 (let ((result (with-output-to-string
605 (stgit-run-silent "id" patch))))
606 (unless (string-match "^\\([0-9A-Fa-f]\\{40\\}\\)$" result)
607 (error "Cannot find commit id for %s" patch))
608 (match-string 1 result)))
609
56d81fe5 610(defun stgit-show ()
a53347d9 611 "Show the patch on the current line."
56d81fe5
DK
612 (interactive)
613 (stgit-capture-output "*StGit patch*"
378a003d
GH
614 (let ((patch (stgit-patch-at-point)))
615 (if (not patch)
1f60181a 616 (let ((patched-file (stgit-patched-file-at-point t)))
378a003d
GH
617 (unless patched-file
618 (error "No patch or file at point"))
619 (let ((id (stgit-id (symbol-name (car patched-file)))))
620 (with-output-to-temp-buffer "*StGit diff*"
1f60181a
GH
621 (if (consp (cdr patched-file))
622 ;; two files (copy or rename)
623 (stgit-run-git "diff" "-C" "-C" (concat id "^") id "--"
624 (cadr patched-file) (cddr patched-file))
625 ;; just one file
626 (stgit-run-git "diff" (concat id "^") id "--"
627 (cdr patched-file)))
378a003d
GH
628 (with-current-buffer standard-output
629 (diff-mode)))))
630 (stgit-run "show" (stgit-patch-at-point))
631 (with-current-buffer standard-output
632 (goto-char (point-min))
633 (diff-mode))))))
0663524d 634
0bca35c8 635(defun stgit-edit ()
a53347d9 636 "Edit the patch on the current line."
0bca35c8 637 (interactive)
018fa1ac 638 (let ((patch (stgit-patch-at-point t))
0780be79 639 (edit-buf (get-buffer-create "*StGit edit*"))
0bca35c8
DK
640 (dir default-directory))
641 (log-edit 'stgit-confirm-edit t nil edit-buf)
642 (set (make-local-variable 'stgit-edit-patch) patch)
643 (setq default-directory dir)
644 (let ((standard-output edit-buf))
9aecd505 645 (stgit-run-silent "edit" "--save-template=-" patch))))
0bca35c8
DK
646
647(defun stgit-confirm-edit ()
648 (interactive)
649 (let ((file (make-temp-file "stgit-edit-")))
650 (write-region (point-min) (point-max) file)
651 (stgit-capture-output nil
652 (stgit-run "edit" "-f" file stgit-edit-patch))
653 (with-current-buffer log-edit-parent-buffer
1f0bf00f 654 (stgit-reload))))
0bca35c8 655
64c097a0 656(defun stgit-new ()
a53347d9 657 "Create a new patch."
64c097a0 658 (interactive)
c5d45b92
GH
659 (let ((edit-buf (get-buffer-create "*StGit edit*"))
660 (dir default-directory))
661 (log-edit 'stgit-confirm-new t nil edit-buf)
662 (setq default-directory dir)))
64c097a0
DK
663
664(defun stgit-confirm-new ()
665 (interactive)
27b0f9e4 666 (let ((file (make-temp-file "stgit-edit-")))
64c097a0
DK
667 (write-region (point-min) (point-max) file)
668 (stgit-capture-output nil
27b0f9e4 669 (stgit-run "new" "-f" file))
64c097a0 670 (with-current-buffer log-edit-parent-buffer
1f0bf00f 671 (stgit-reload))))
64c097a0
DK
672
673(defun stgit-create-patch-name (description)
674 "Create a patch name from a long description"
675 (let ((patch ""))
676 (while (> (length description) 0)
677 (cond ((string-match "\\`[a-zA-Z_-]+" description)
678 (setq patch (downcase (concat patch (match-string 0 description))))
679 (setq description (substring description (match-end 0))))
680 ((string-match "\\` +" description)
681 (setq patch (concat patch "-"))
682 (setq description (substring description (match-end 0))))
683 ((string-match "\\`[^a-zA-Z_-]+" description)
684 (setq description (substring description (match-end 0))))))
685 (cond ((= (length patch) 0)
686 "patch")
687 ((> (length patch) 20)
688 (substring patch 0 20))
689 (t patch))))
0bca35c8 690
7755d7f1 691(defun stgit-delete (patch-names)
a53347d9 692 "Delete the named patches."
7755d7f1
KH
693 (interactive (list (stgit-patches-marked-or-at-point)))
694 (if (zerop (length patch-names))
695 (error "No patches to delete")
696 (when (yes-or-no-p (format "Really delete %d patches? "
697 (length patch-names)))
698 (stgit-capture-output nil
699 (apply 'stgit-run "delete" patch-names))
1f0bf00f 700 (stgit-reload))))
7755d7f1 701
ea0def18 702(defun stgit-coalesce (patch-names)
a53347d9 703 "Run stg coalesce on the named patches."
ea0def18 704 (interactive (list (stgit-marked-patches)))
0780be79 705 (let ((edit-buf (get-buffer-create "*StGit edit*"))
ea0def18
DK
706 (dir default-directory))
707 (log-edit 'stgit-confirm-coalesce t nil edit-buf)
708 (set (make-local-variable 'stgit-patches) patch-names)
709 (setq default-directory dir)
710 (let ((standard-output edit-buf))
9aecd505 711 (apply 'stgit-run-silent "coalesce" "--save-template=-" patch-names))))
ea0def18
DK
712
713(defun stgit-confirm-coalesce ()
714 (interactive)
715 (let ((file (make-temp-file "stgit-edit-")))
716 (write-region (point-min) (point-max) file)
717 (stgit-capture-output nil
718 (apply 'stgit-run "coalesce" "-f" file stgit-patches))
719 (with-current-buffer log-edit-parent-buffer
e6b1fdae
DK
720 (stgit-clear-marks)
721 ;; Go to first marked patch and stay there
722 (goto-char (point-min))
723 (re-search-forward (concat "^[>+-]\\*") nil t)
724 (move-to-column goal-column)
725 (let ((pos (point)))
1f0bf00f 726 (stgit-reload)
e6b1fdae 727 (goto-char pos)))))
ea0def18 728
0663524d
KH
729(defun stgit-help ()
730 "Display help for the StGit mode."
731 (interactive)
732 (describe-function 'stgit-mode))
3a59f3db 733
83e51dbf
DK
734(defun stgit-undo (&optional arg)
735 "Run stg undo.
736With prefix argument, run it with the --hard flag."
737 (interactive "P")
738 (stgit-capture-output nil
739 (if arg
740 (stgit-run "undo" "--hard")
741 (stgit-run "undo")))
1f0bf00f 742 (stgit-reload))
83e51dbf 743
4d73c4d8
DK
744(defun stgit-refresh (&optional arg)
745 "Run stg refresh.
a53347d9 746With prefix argument, refresh the marked patch or the patch under point."
4d73c4d8
DK
747 (interactive "P")
748 (let ((patchargs (if arg
b0424080
GH
749 (let ((patches (stgit-patches-marked-or-at-point)))
750 (cond ((null patches)
751 (error "no patch to update"))
752 ((> (length patches) 1)
753 (error "too many patches selected"))
754 (t
755 (cons "-p" patches))))
756 nil)))
4d73c4d8 757 (stgit-capture-output nil
074a4fb0
GH
758 (apply 'stgit-run "refresh" patchargs))
759 (stgit-refresh-git-status))
4d73c4d8
DK
760 (stgit-reload))
761
3a59f3db 762(provide 'stgit)