stgit.el: Fix some too wide lines
[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
a6d9a852
GH
168(defface stgit-file-permission-face
169 '((((class color) (background light)) (:foreground "green" :bold t))
170 (((class color) (background dark)) (:foreground "green" :bold t)))
171 "StGit mode face used for permission changes."
172 :group 'stgit)
173
1f60181a
GH
174(defcustom stgit-expand-find-copies-harder
175 nil
176 "Try harder to find copied files when listing patches.
177
178When not nil, runs git diff-tree with the --find-copies-harder
179flag, which reduces performance."
180 :type 'boolean
181 :group 'stgit)
182
183(defconst stgit-file-status-code-strings
184 (mapcar (lambda (arg)
185 (cons (car arg)
a6d9a852
GH
186 (propertize (cadr arg) 'face (car (cddr arg)))))
187 '((add "Added" stgit-modified-file-face)
188 (copy "Copied" stgit-modified-file-face)
189 (delete "Deleted" stgit-modified-file-face)
190 (modify "Modified" stgit-modified-file-face)
191 (rename "Renamed" stgit-modified-file-face)
192 (mode-change "Mode change" stgit-modified-file-face)
193 (unmerged "Unmerged" stgit-unmerged-file-face)
194 (unknown "Unknown" stgit-unknown-file-face)))
1f60181a
GH
195 "Alist of code symbols to description strings")
196
197(defun stgit-file-status-code-as-string (code)
198 "Return stgit status code as string"
a6d9a852
GH
199 (let ((str (assq (if (consp code) (car code) code)
200 stgit-file-status-code-strings)))
201 (when str
202 (format "%-11s "
203 (if (and str (consp code) (/= (cdr code) 100))
204 (format "%s %s" (cdr str)
205 (propertize (format "%d%%" (cdr code))
206 'face 'stgit-description-face))
207 (cdr str))))))
1f60181a 208
a6d9a852 209(defun stgit-file-status-code (str &optional score)
1f60181a
GH
210 "Return stgit status code from git status string"
211 (let ((code (assoc str '(("A" . add)
212 ("C" . copy)
213 ("D" . delete)
214 ("M" . modify)
215 ("R" . rename)
216 ("T" . mode-change)
217 ("U" . unmerged)
218 ("X" . unknown)))))
a6d9a852
GH
219 (setq code (if code (cdr code) 'unknown))
220 (when (stringp score)
221 (if (> (length score) 0)
222 (setq score (string-to-number score))
223 (setq score nil)))
224 (if score (cons code score) code)))
225
226(defconst stgit-file-type-strings
227 '((#o100 . "file")
228 (#o120 . "symlink")
229 (#o160 . "subproject"))
230 "Alist of names of file types")
231
232(defun stgit-file-type-string (type)
233 (let ((type-str (assoc type stgit-file-type-strings)))
234 (or (and type-str (cdr type-str))
235 (format "unknown type %o" type))))
236
237(defun stgit-file-type-change-string (old-perm new-perm)
238 (let ((old-type (lsh old-perm -9))
239 (new-type (lsh new-perm -9)))
240 (cond ((= old-type new-type) "")
241 ((zerop new-type) "")
242 ((zerop old-type)
243 (if (= new-type #o100)
244 ""
245 (format " (%s)" (stgit-file-type-string new-type))))
246 (t (format " (%s -> %s)"
247 (stgit-file-type-string old-type)
248 (stgit-file-type-string new-type))))))
249
250(defun stgit-file-mode-change-string (old-perm new-perm)
251 (setq old-perm (logand old-perm #o777)
252 new-perm (logand new-perm #o777))
253 (if (or (= old-perm new-perm)
254 (zerop old-perm)
255 (zerop new-perm))
256 ""
257 (let* ((modified (logxor old-perm new-perm))
258 (not-x-modified (logand (logxor old-perm new-perm) #o666)))
259 (cond ((zerop modified) "")
260 ((and (zerop not-x-modified)
261 (or (and (eq #o111 (logand old-perm #o111))
262 (propertize "-x" 'face 'stgit-file-permission-face))
263 (and (eq #o111 (logand new-perm #o111))
264 (propertize "+x" 'face
265 'stgit-file-permission-face)))))
266 (t (concat (propertize (format "%o" old-perm)
267 'face 'stgit-file-permission-face)
268 (propertize " -> "
269 'face 'stgit-description-face)
270 (propertize (format "%o" new-perm)
271 'face 'stgit-file-permission-face)))))))
1f60181a 272
378a003d
GH
273(defun stgit-expand-patch (patchsym)
274 (save-excursion
275 (forward-line)
1f60181a
GH
276 (let* ((start (point))
277 (result (with-output-to-string
278 (stgit-run-git "diff-tree" "-r" "-z"
279 (if stgit-expand-find-copies-harder
280 "--find-copies-harder"
281 "-C")
282 (stgit-id (symbol-name patchsym))))))
283 (let (mstart)
a6d9a852 284 (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]*\\)\\)"
1f60181a 285 result mstart)
a6d9a852
GH
286 (let ((copy-or-rename (match-string 4 result))
287 (old-perm (read (format "#o%s" (match-string 1 result))))
288 (new-perm (read (format "#o%s" (match-string 2 result))))
1f60181a 289 (line-start (point))
a6d9a852
GH
290 status
291 change
1f60181a
GH
292 properties)
293 (insert " ")
294 (if copy-or-rename
a6d9a852
GH
295 (let ((cr-score (match-string 5 result))
296 (cr-from-file (match-string 6 result))
297 (cr-to-file (match-string 7 result)))
298 (setq status (stgit-file-status-code copy-or-rename
299 cr-score)
300 properties (list 'stgit-old-file cr-from-file
301 'stgit-new-file cr-to-file)
302 change (concat
303 cr-from-file
304 (propertize " -> "
305 'face 'stgit-description-face)
306 cr-to-file)))
307 (setq status (stgit-file-status-code (match-string 8 result))
308 properties (list 'stgit-file (match-string 9 result))
309 change (match-string 9 result)))
310
311 (let ((mode-change (stgit-file-mode-change-string old-perm
312 new-perm)))
313 (insert (format "%-12s" (stgit-file-status-code-as-string
314 status))
315 mode-change
316 (if (> (length mode-change) 0) " " "")
317 change
318 (propertize (stgit-file-type-change-string old-perm
319 new-perm)
320 'face 'stgit-description-face)
321 ?\n))
1f60181a
GH
322 (add-text-properties line-start (point) properties))
323 (setq mstart (match-end 0))))
324 (when (= start (point))
325 (insert " <no files>\n"))
326 (put-text-property start (point) 'stgit-patchsym patchsym))))
378a003d 327
6df83d42
DK
328(defun stgit-rescan ()
329 "Rescan the status buffer."
07f464e0 330 (save-excursion
6df83d42
DK
331 (let ((marked ()))
332 (goto-char (point-min))
333 (while (not (eobp))
334 (cond ((looking-at "Branch: \\(.*\\)")
335 (put-text-property (match-beginning 1) (match-end 1)
336 'face 'bold))
8ee1e4b4 337 ((looking-at "\\([>+-]\\)\\( \\)\\([^ ]+\\) *[|#] \\(.*\\)")
6df83d42
DK
338 (let ((state (match-string 1))
339 (patchsym (intern (match-string 3))))
340 (put-text-property
341 (match-beginning 3) (match-end 3) 'face
342 (cond ((string= state ">") 'stgit-top-patch-face)
343 ((string= state "+") 'stgit-applied-patch-face)
344 ((string= state "-") 'stgit-unapplied-patch-face)))
345 (put-text-property (match-beginning 4) (match-end 4)
346 'face 'stgit-description-face)
347 (when (memq patchsym stgit-marked-patches)
348 (replace-match "*" nil nil nil 2)
378a003d
GH
349 (setq marked (cons patchsym marked)))
350 (when (memq patchsym stgit-expanded-patches)
351 (stgit-expand-patch patchsym))
352 ))
ad80ce22
DK
353 ((or (looking-at "stg series: Branch \".*\" not initialised")
354 (looking-at "stg series: .*: branch not initialized"))
1c2426dc
DK
355 (forward-line 1)
356 (insert "Run M-x stgit-init to initialise")))
6df83d42
DK
357 (forward-line 1))
358 (setq stgit-marked-patches (nreverse marked)))))
07f464e0 359
378a003d
GH
360(defun stgit-select ()
361 "Expand or collapse the current entry"
362 (interactive)
363 (let ((curpatch (stgit-patch-at-point)))
364 (if (not curpatch)
365 (let ((patched-file (stgit-patched-file-at-point)))
366 (unless patched-file
367 (error "No patch or file on the current line"))
368 (let ((filename (expand-file-name (cdr patched-file))))
369 (unless (file-exists-p filename)
370 (error "File does not exist"))
371 (find-file filename)))
372 (setq curpatch (intern curpatch))
373 (setq stgit-expanded-patches
374 (if (memq curpatch stgit-expanded-patches)
375 (delq curpatch stgit-expanded-patches)
376 (cons curpatch stgit-expanded-patches)))
377 (stgit-reload))))
378
379(defun stgit-find-file-other-window ()
380 "Open file at point in other window"
381 (interactive)
382 (let ((patched-file (stgit-patched-file-at-point)))
383 (unless patched-file
384 (error "No file on the current line"))
385 (let ((filename (expand-file-name (cdr patched-file))))
386 (unless (file-exists-p filename)
387 (error "File does not exist"))
388 (find-file-other-window filename))))
389
83327d53 390(defun stgit-quit ()
a53347d9 391 "Hide the stgit buffer."
83327d53
GH
392 (interactive)
393 (bury-buffer))
394
0f076fe6 395(defun stgit-git-status ()
a53347d9 396 "Show status using `git-status'."
0f076fe6
GH
397 (interactive)
398 (unless (fboundp 'git-status)
399 (error "stgit-git-status requires git-status"))
400 (let ((dir default-directory))
401 (save-selected-window
402 (pop-to-buffer nil)
403 (git-status dir))))
404
378a003d
GH
405(defun stgit-next-line (&optional arg try-vscroll)
406 "Move cursor vertically down ARG lines"
407 (interactive "p\np")
408 (next-line arg try-vscroll)
409 (when (looking-at " \\S-")
410 (forward-char 2)))
411
412(defun stgit-previous-line (&optional arg try-vscroll)
413 "Move cursor vertically up ARG lines"
414 (interactive "p\np")
415 (previous-line arg try-vscroll)
416 (when (looking-at " \\S-")
417 (forward-char 2)))
418
419(defun stgit-next-patch (&optional arg)
420 "Move cursor down ARG patches"
421 (interactive "p")
422 (unless arg
423 (setq arg 1))
424 (if (< arg 0)
425 (stgit-previous-patch (- arg))
426 (while (not (zerop arg))
427 (setq arg (1- arg))
428 (while (progn (stgit-next-line)
429 (not (stgit-patch-at-point)))))))
430
431(defun stgit-previous-patch (&optional arg)
432 "Move cursor up ARG patches"
433 (interactive "p")
434 (unless arg
435 (setq arg 1))
436 (if (< arg 0)
437 (stgit-next-patch (- arg))
438 (while (not (zerop arg))
439 (setq arg (1- arg))
440 (while (progn (stgit-previous-line)
441 (not (stgit-patch-at-point)))))))
442
56d81fe5
DK
443(defvar stgit-mode-hook nil
444 "Run after `stgit-mode' is setup.")
445
446(defvar stgit-mode-map nil
447 "Keymap for StGit major mode.")
448
449(unless stgit-mode-map
450 (setq stgit-mode-map (make-keymap))
451 (suppress-keymap stgit-mode-map)
022a3664
GH
452 (mapc (lambda (arg) (define-key stgit-mode-map (car arg) (cdr arg)))
453 '((" " . stgit-mark)
3dccdc9b 454 ("m" . stgit-mark)
9b151b27
GH
455 ("\d" . stgit-unmark-up)
456 ("u" . stgit-unmark-down)
022a3664
GH
457 ("?" . stgit-help)
458 ("h" . stgit-help)
378a003d
GH
459 ("p" . stgit-previous-line)
460 ("n" . stgit-next-line)
461 ("\C-p" . stgit-previous-patch)
462 ("\C-n" . stgit-next-patch)
463 ("\M-{" . stgit-previous-patch)
464 ("\M-}" . stgit-next-patch)
0f076fe6 465 ("s" . stgit-git-status)
022a3664
GH
466 ("g" . stgit-reload)
467 ("r" . stgit-refresh)
468 ("\C-c\C-r" . stgit-rename)
469 ("e" . stgit-edit)
470 ("c" . stgit-coalesce)
471 ("N" . stgit-new)
472 ("R" . stgit-repair)
473 ("C" . stgit-commit)
474 ("U" . stgit-uncommit)
378a003d
GH
475 ("\r" . stgit-select)
476 ("o" . stgit-find-file-other-window)
022a3664
GH
477 (">" . stgit-push-next)
478 ("<" . stgit-pop-next)
479 ("P" . stgit-push-or-pop)
480 ("G" . stgit-goto)
481 ("=" . stgit-show)
482 ("D" . stgit-delete)
483 ([(control ?/)] . stgit-undo)
83327d53
GH
484 ("\C-_" . stgit-undo)
485 ("q" . stgit-quit))))
56d81fe5
DK
486
487(defun stgit-mode ()
488 "Major mode for interacting with StGit.
489Commands:
490\\{stgit-mode-map}"
491 (kill-all-local-variables)
492 (buffer-disable-undo)
493 (setq mode-name "StGit"
494 major-mode 'stgit-mode
495 goal-column 2)
496 (use-local-map stgit-mode-map)
497 (set (make-local-variable 'list-buffers-directory) default-directory)
6df83d42 498 (set (make-local-variable 'stgit-marked-patches) nil)
378a003d 499 (set (make-local-variable 'stgit-expanded-patches) nil)
2870f8b8 500 (set-variable 'truncate-lines 't)
56d81fe5
DK
501 (run-hooks 'stgit-mode-hook))
502
6df83d42
DK
503(defun stgit-add-mark (patch)
504 (let ((patchsym (intern patch)))
505 (setq stgit-marked-patches (cons patchsym stgit-marked-patches))))
506
507(defun stgit-remove-mark (patch)
508 (let ((patchsym (intern patch)))
509 (setq stgit-marked-patches (delq patchsym stgit-marked-patches))))
510
e6b1fdae
DK
511(defun stgit-clear-marks ()
512 (setq stgit-marked-patches '()))
513
6df83d42
DK
514(defun stgit-marked-patches ()
515 "Return the names of the marked patches."
516 (mapcar 'symbol-name stgit-marked-patches))
517
378a003d
GH
518(defun stgit-patch-at-point (&optional cause-error allow-file)
519 "Return the patch name on the current line.
520If CAUSE-ERROR is not nil, signal an error if none found.
521If ALLOW-FILE is not nil, also handle when point is on a file of
522a patch."
523 (or (and allow-file
524 (let ((patchsym (get-text-property (point) 'stgit-patchsym)))
525 (and patchsym
526 (symbol-name patchsym))))
527 (save-excursion
528 (beginning-of-line)
529 (and (looking-at "[>+-][ *]\\([^ ]*\\)")
530 (match-string-no-properties 1)))
531 (and cause-error
532 (error "No patch on this line"))))
533
1f60181a
GH
534(defun stgit-patched-file-at-point (&optional both-files)
535 "Returns a cons of the patchsym and file name at point. For
536copies and renames, return the new file if the patch is either
537applied. If BOTH-FILES is non-nil, return a cons of the old and
538the new file names instead of just one name."
539 (let ((patchsym (get-text-property (point) 'stgit-patchsym))
540 (file (get-text-property (point) 'stgit-file)))
541 (cond ((not patchsym) nil)
542 (file (cons patchsym file))
543 (both-files
544 (cons patchsym (cons (get-text-property (point) 'stgit-old-file)
545 (get-text-property (point) 'stgit-new-file))))
546 (t
547 (let ((file-sym (save-excursion
548 (stgit-previous-patch)
549 (unless (equal (stgit-patch-at-point)
550 (symbol-name patchsym))
551 (error "Cannot find the %s patch" patchsym))
552 (beginning-of-line)
553 (if (= (char-after) ?-)
554 'stgit-old-file
555 'stgit-new-file))))
556 (cons patchsym (get-text-property (point) file-sym)))))))
56d81fe5 557
7755d7f1
KH
558(defun stgit-patches-marked-or-at-point ()
559 "Return the names of the marked patches, or the patch on the current line."
560 (if stgit-marked-patches
561 (stgit-marked-patches)
562 (let ((patch (stgit-patch-at-point)))
563 (if patch
564 (list patch)
565 '()))))
566
56d81fe5 567(defun stgit-goto-patch (patch)
a53347d9 568 "Move point to the line containing PATCH."
56d81fe5
DK
569 (let ((p (point)))
570 (goto-char (point-min))
8439f657
GH
571 (if (re-search-forward (concat "^[>+-][ *]" (regexp-quote patch) " ")
572 nil t)
56d81fe5
DK
573 (progn (move-to-column goal-column)
574 t)
575 (goto-char p)
576 nil)))
577
1c2426dc 578(defun stgit-init ()
a53347d9 579 "Run stg init."
1c2426dc
DK
580 (interactive)
581 (stgit-capture-output nil
b0424080 582 (stgit-run "init"))
1f0bf00f 583 (stgit-reload))
1c2426dc 584
6df83d42 585(defun stgit-mark ()
a53347d9 586 "Mark the patch under point."
6df83d42 587 (interactive)
018fa1ac 588 (let ((patch (stgit-patch-at-point t)))
6df83d42 589 (stgit-add-mark patch)
1f0bf00f 590 (stgit-reload))
378a003d 591 (stgit-next-patch))
6df83d42 592
9b151b27 593(defun stgit-unmark-up ()
a53347d9 594 "Remove mark from the patch on the previous line."
6df83d42 595 (interactive)
378a003d 596 (stgit-previous-patch)
018fa1ac 597 (stgit-remove-mark (stgit-patch-at-point t))
9b151b27
GH
598 (stgit-reload))
599
600(defun stgit-unmark-down ()
a53347d9 601 "Remove mark from the patch on the current line."
9b151b27 602 (interactive)
018fa1ac 603 (stgit-remove-mark (stgit-patch-at-point t))
1288eda2
GH
604 (stgit-reload)
605 (stgit-next-patch))
6df83d42 606
56d81fe5 607(defun stgit-rename (name)
018fa1ac
GH
608 "Rename the patch under point to NAME."
609 (interactive (list (read-string "Patch name: " (stgit-patch-at-point t))))
610 (let ((old-name (stgit-patch-at-point t)))
56d81fe5
DK
611 (stgit-capture-output nil
612 (stgit-run "rename" old-name name))
378a003d
GH
613 (let ((old-name-sym (intern old-name))
614 (name-sym (intern name)))
615 (when (memq old-name-sym stgit-expanded-patches)
616 (setq stgit-expanded-patches
617 (cons name-sym (delq old-name-sym stgit-expanded-patches))))
618 (when (memq old-name-sym stgit-marked-patches)
619 (setq stgit-marked-patches
620 (cons name-sym (delq old-name-sym stgit-marked-patches)))))
1f0bf00f 621 (stgit-reload)
56d81fe5
DK
622 (stgit-goto-patch name)))
623
26201d96 624(defun stgit-repair ()
a53347d9 625 "Run stg repair."
26201d96
DK
626 (interactive)
627 (stgit-capture-output nil
b0424080 628 (stgit-run "repair"))
1f0bf00f 629 (stgit-reload))
26201d96 630
c4aad9a7
DK
631(defun stgit-commit ()
632 "Run stg commit."
633 (interactive)
634 (stgit-capture-output nil (stgit-run "commit"))
1f0bf00f 635 (stgit-reload))
c4aad9a7
DK
636
637(defun stgit-uncommit (arg)
638 "Run stg uncommit. Numeric arg determines number of patches to uncommit."
639 (interactive "p")
640 (stgit-capture-output nil (stgit-run "uncommit" "-n" (number-to-string arg)))
1f0bf00f 641 (stgit-reload))
c4aad9a7 642
0b661144
DK
643(defun stgit-push-next (npatches)
644 "Push the first unapplied patch.
645With numeric prefix argument, push that many patches."
646 (interactive "p")
647 (stgit-capture-output nil (stgit-run "push" "-n"
648 (number-to-string npatches)))
074a4fb0
GH
649 (stgit-reload)
650 (stgit-refresh-git-status))
56d81fe5 651
0b661144
DK
652(defun stgit-pop-next (npatches)
653 "Pop the topmost applied patch.
654With numeric prefix argument, pop that many patches."
655 (interactive "p")
656 (stgit-capture-output nil (stgit-run "pop" "-n" (number-to-string npatches)))
074a4fb0
GH
657 (stgit-reload)
658 (stgit-refresh-git-status))
56d81fe5 659
f9182fca
KH
660(defun stgit-applied-at-point ()
661 "Is the patch on the current line applied?"
662 (save-excursion
663 (beginning-of-line)
664 (looking-at "[>+]")))
665
666(defun stgit-push-or-pop ()
a53347d9 667 "Push or pop the patch on the current line."
f9182fca 668 (interactive)
018fa1ac 669 (let ((patch (stgit-patch-at-point t))
f9182fca
KH
670 (applied (stgit-applied-at-point)))
671 (stgit-capture-output nil
b0424080 672 (stgit-run (if applied "pop" "push") patch))
1f0bf00f 673 (stgit-reload)))
f9182fca 674
c7adf5ef 675(defun stgit-goto ()
a53347d9 676 "Go to the patch on the current line."
c7adf5ef 677 (interactive)
018fa1ac 678 (let ((patch (stgit-patch-at-point t)))
c7adf5ef 679 (stgit-capture-output nil
b0424080 680 (stgit-run "goto" patch))
1f0bf00f 681 (stgit-reload)))
c7adf5ef 682
378a003d
GH
683(defun stgit-id (patch)
684 "Return the git commit id for PATCH"
685 (let ((result (with-output-to-string
686 (stgit-run-silent "id" patch))))
687 (unless (string-match "^\\([0-9A-Fa-f]\\{40\\}\\)$" result)
688 (error "Cannot find commit id for %s" patch))
689 (match-string 1 result)))
690
56d81fe5 691(defun stgit-show ()
a53347d9 692 "Show the patch on the current line."
56d81fe5
DK
693 (interactive)
694 (stgit-capture-output "*StGit patch*"
378a003d
GH
695 (let ((patch (stgit-patch-at-point)))
696 (if (not patch)
1f60181a 697 (let ((patched-file (stgit-patched-file-at-point t)))
378a003d
GH
698 (unless patched-file
699 (error "No patch or file at point"))
700 (let ((id (stgit-id (symbol-name (car patched-file)))))
701 (with-output-to-temp-buffer "*StGit diff*"
1f60181a
GH
702 (if (consp (cdr patched-file))
703 ;; two files (copy or rename)
704 (stgit-run-git "diff" "-C" "-C" (concat id "^") id "--"
705 (cadr patched-file) (cddr patched-file))
706 ;; just one file
707 (stgit-run-git "diff" (concat id "^") id "--"
708 (cdr patched-file)))
378a003d
GH
709 (with-current-buffer standard-output
710 (diff-mode)))))
711 (stgit-run "show" (stgit-patch-at-point))
712 (with-current-buffer standard-output
713 (goto-char (point-min))
714 (diff-mode))))))
0663524d 715
0bca35c8 716(defun stgit-edit ()
a53347d9 717 "Edit the patch on the current line."
0bca35c8 718 (interactive)
018fa1ac 719 (let ((patch (stgit-patch-at-point t))
0780be79 720 (edit-buf (get-buffer-create "*StGit edit*"))
0bca35c8
DK
721 (dir default-directory))
722 (log-edit 'stgit-confirm-edit t nil edit-buf)
723 (set (make-local-variable 'stgit-edit-patch) patch)
724 (setq default-directory dir)
725 (let ((standard-output edit-buf))
9aecd505 726 (stgit-run-silent "edit" "--save-template=-" patch))))
0bca35c8
DK
727
728(defun stgit-confirm-edit ()
729 (interactive)
730 (let ((file (make-temp-file "stgit-edit-")))
731 (write-region (point-min) (point-max) file)
732 (stgit-capture-output nil
733 (stgit-run "edit" "-f" file stgit-edit-patch))
734 (with-current-buffer log-edit-parent-buffer
1f0bf00f 735 (stgit-reload))))
0bca35c8 736
aa04f831
GH
737(defun stgit-new (add-sign)
738 "Create a new patch.
739With a prefix argument, include a \"Signed-off-by:\" line at the
740end of the patch."
741 (interactive "P")
c5d45b92
GH
742 (let ((edit-buf (get-buffer-create "*StGit edit*"))
743 (dir default-directory))
744 (log-edit 'stgit-confirm-new t nil edit-buf)
aa04f831
GH
745 (setq default-directory dir)
746 (when add-sign
747 (save-excursion
748 (let ((standard-output (current-buffer)))
749 (stgit-run-silent "new" "--sign" "--save-template=-"))))))
64c097a0
DK
750
751(defun stgit-confirm-new ()
752 (interactive)
27b0f9e4 753 (let ((file (make-temp-file "stgit-edit-")))
64c097a0
DK
754 (write-region (point-min) (point-max) file)
755 (stgit-capture-output nil
27b0f9e4 756 (stgit-run "new" "-f" file))
64c097a0 757 (with-current-buffer log-edit-parent-buffer
1f0bf00f 758 (stgit-reload))))
64c097a0
DK
759
760(defun stgit-create-patch-name (description)
761 "Create a patch name from a long description"
762 (let ((patch ""))
763 (while (> (length description) 0)
764 (cond ((string-match "\\`[a-zA-Z_-]+" description)
8439f657
GH
765 (setq patch (downcase (concat patch
766 (match-string 0 description))))
64c097a0
DK
767 (setq description (substring description (match-end 0))))
768 ((string-match "\\` +" description)
769 (setq patch (concat patch "-"))
770 (setq description (substring description (match-end 0))))
771 ((string-match "\\`[^a-zA-Z_-]+" description)
772 (setq description (substring description (match-end 0))))))
773 (cond ((= (length patch) 0)
774 "patch")
775 ((> (length patch) 20)
776 (substring patch 0 20))
777 (t patch))))
0bca35c8 778
7755d7f1 779(defun stgit-delete (patch-names)
a53347d9 780 "Delete the named patches."
7755d7f1
KH
781 (interactive (list (stgit-patches-marked-or-at-point)))
782 (if (zerop (length patch-names))
783 (error "No patches to delete")
784 (when (yes-or-no-p (format "Really delete %d patches? "
785 (length patch-names)))
786 (stgit-capture-output nil
787 (apply 'stgit-run "delete" patch-names))
1f0bf00f 788 (stgit-reload))))
7755d7f1 789
ea0def18 790(defun stgit-coalesce (patch-names)
a53347d9 791 "Run stg coalesce on the named patches."
ea0def18 792 (interactive (list (stgit-marked-patches)))
0780be79 793 (let ((edit-buf (get-buffer-create "*StGit edit*"))
ea0def18
DK
794 (dir default-directory))
795 (log-edit 'stgit-confirm-coalesce t nil edit-buf)
796 (set (make-local-variable 'stgit-patches) patch-names)
797 (setq default-directory dir)
798 (let ((standard-output edit-buf))
9aecd505 799 (apply 'stgit-run-silent "coalesce" "--save-template=-" patch-names))))
ea0def18
DK
800
801(defun stgit-confirm-coalesce ()
802 (interactive)
803 (let ((file (make-temp-file "stgit-edit-")))
804 (write-region (point-min) (point-max) file)
805 (stgit-capture-output nil
806 (apply 'stgit-run "coalesce" "-f" file stgit-patches))
807 (with-current-buffer log-edit-parent-buffer
e6b1fdae
DK
808 (stgit-clear-marks)
809 ;; Go to first marked patch and stay there
810 (goto-char (point-min))
811 (re-search-forward (concat "^[>+-]\\*") nil t)
812 (move-to-column goal-column)
813 (let ((pos (point)))
1f0bf00f 814 (stgit-reload)
e6b1fdae 815 (goto-char pos)))))
ea0def18 816
0663524d
KH
817(defun stgit-help ()
818 "Display help for the StGit mode."
819 (interactive)
820 (describe-function 'stgit-mode))
3a59f3db 821
83e51dbf
DK
822(defun stgit-undo (&optional arg)
823 "Run stg undo.
824With prefix argument, run it with the --hard flag."
825 (interactive "P")
826 (stgit-capture-output nil
827 (if arg
828 (stgit-run "undo" "--hard")
829 (stgit-run "undo")))
1f0bf00f 830 (stgit-reload))
83e51dbf 831
4d73c4d8
DK
832(defun stgit-refresh (&optional arg)
833 "Run stg refresh.
a53347d9 834With prefix argument, refresh the marked patch or the patch under point."
4d73c4d8
DK
835 (interactive "P")
836 (let ((patchargs (if arg
b0424080
GH
837 (let ((patches (stgit-patches-marked-or-at-point)))
838 (cond ((null patches)
839 (error "no patch to update"))
840 ((> (length patches) 1)
841 (error "too many patches selected"))
842 (t
843 (cons "-p" patches))))
844 nil)))
4d73c4d8 845 (stgit-capture-output nil
074a4fb0
GH
846 (apply 'stgit-run "refresh" patchargs))
847 (stgit-refresh-git-status))
4d73c4d8
DK
848 (stgit-reload))
849
3a59f3db 850(provide 'stgit)