;; To start: `M-x stgit'
(require 'git nil t)
+(require 'cl)
(defun stgit (dir)
"Manage StGit patches for the tree in DIR."
"Capture StGit output and, if there was any output, show it in a window
at the end.
Returns nil if there was no output."
+ (declare (debug ([&or stringp null] body))
+ (indent 1))
`(let ((output-buf (get-buffer-create ,(or name "*StGit output*")))
(stgit-dir default-directory)
(inhibit-read-only t))
(setq buffer-read-only t)
(if (< (point-min) (point-max))
(display-buffer output-buf t)))))
-(put 'stgit-capture-output 'lisp-indent-function 1)
(defun stgit-make-run-args (args)
"Return a copy of ARGS with its elements converted to strings."
(line-start (point))
status
change
- properties)
+ (properties '(entry-type file)))
(insert " ")
(if copy-or-rename
(let ((cr-score (match-string 5 result))
(cr-to-file (match-string 7 result)))
(setq status (stgit-file-status-code copy-or-rename
cr-score)
- properties (list 'stgit-old-file cr-from-file
- 'stgit-new-file cr-to-file)
+ properties (list* 'stgit-old-file cr-from-file
+ 'stgit-new-file cr-to-file
+ properties)
change (concat
cr-from-file
(propertize " -> "
'face 'stgit-description-face)
cr-to-file)))
(setq status (stgit-file-status-code (match-string 8 result))
- properties (list 'stgit-file (match-string 9 result))
+ properties (list* 'stgit-file (match-string 9 result)
+ properties)
change (match-string 9 result)))
(let ((mode-change (stgit-file-mode-change-string old-perm
(save-excursion
(replace-match "*" nil nil nil 3))
(setq marked (cons patchsym marked)))
- (put-text-property (match-beginning 0) (match-end 0)
- 'stgit-patchsym patchsym)
+ (add-text-properties (match-beginning 0) (match-end 0)
+ (list 'stgit-patchsym patchsym
+ 'entry-type 'patch))
(when (memq patchsym stgit-expanded-patches)
(stgit-expand-patch patchsym))
(when (equal "0" empty)
(error "File does not exist"))
(find-file filename))))
-(defun stgit-toggle-patch-file-list (curpath)
- (let ((inhibit-read-only t))
+(defun stgit-select-patch ()
+ (let ((inhibit-read-only t)
+ (curpatch (stgit-patch-at-point)))
(if (memq curpatch stgit-expanded-patches)
(save-excursion
(setq stgit-expanded-patches (delq curpatch stgit-expanded-patches))
(defun stgit-select ()
"Expand or collapse the current entry"
(interactive)
- (let ((curpatch (stgit-patch-at-point)))
- (if curpatch
- (stgit-toggle-patch-file-list curpatch)
- (stgit-select-file))))
-
+ (case (get-text-property (point) 'entry-type)
+ ('patch
+ (stgit-select-patch))
+ ('file
+ (stgit-select-file))
+ (t
+ (error "No patch or file on line"))))
(defun stgit-find-file-other-window ()
"Open file at point in other window"
(pop-to-buffer nil)
(git-status dir))))
-(defun stgit-next-line (&optional arg try-vscroll)
+(defun stgit-goal-column ()
+ "Return goal column for the current line"
+ (case (get-text-property (point) 'entry-type)
+ ('patch 2)
+ ('file 4)
+ (t 0)))
+
+(defun stgit-next-line (&optional arg)
"Move cursor vertically down ARG lines"
- (interactive "p\np")
- (next-line arg try-vscroll)
- (when (looking-at " \\S-")
- (forward-char 2)))
+ (interactive "p")
+ (next-line arg)
+ (move-to-column (stgit-goal-column)))
-(defun stgit-previous-line (&optional arg try-vscroll)
+(defun stgit-previous-line (&optional arg)
"Move cursor vertically up ARG lines"
- (interactive "p\np")
- (previous-line arg try-vscroll)
- (when (looking-at " \\S-")
- (forward-char 2)))
+ (interactive "p")
+ (previous-line arg)
+ (move-to-column (stgit-goal-column)))
(defun stgit-next-patch (&optional arg)
"Move cursor down ARG patches"
("u" . stgit-unmark-down)
("?" . stgit-help)
("h" . stgit-help)
- ("p" . stgit-previous-line)
- ("n" . stgit-next-line)
- ("\C-p" . stgit-previous-patch)
- ("\C-n" . stgit-next-patch)
+ ("\C-p" . stgit-previous-line)
+ ("\C-n" . stgit-next-line)
+ ([up] . stgit-previous-line)
+ ([down] . stgit-next-line)
+ ("p" . stgit-previous-patch)
+ ("n" . stgit-next-patch)
("\M-{" . stgit-previous-patch)
("\M-}" . stgit-next-patch)
("s" . stgit-git-status)
"Unmark all patches."
(setq stgit-marked-patches '()))
-(defun stgit-patch-at-point (&optional cause-error allow-file)
+(defun stgit-patch-at-point (&optional cause-error)
"Return the patch name on the current line as a symbol.
-If CAUSE-ERROR is not nil, signal an error if none found.
-If ALLOW-FILE is not nil, also handle when point is on a file of
-a patch."
- (or (get-text-property (point) 'stgit-patchsym)
- (and allow-file
- (get-text-property (point) 'stgit-file-patchsym))
- (when cause-error
- (error "No patch on this line"))))
+If CAUSE-ERROR is not nil, signal an error if none found."
+ (case (get-text-property (point) 'entry-type)
+ ('patch (get-text-property (point) 'stgit-patchsym))
+ (t (if cause-error
+ (error "No patch on this line")
+ nil))))
(defun stgit-patched-file-at-point (&optional both-files)
"Returns a cons of the patchsym and file name at point. For
(stgit-reload)))
(defun stgit-id (patchsym)
- "Return the git commit id for PATCHSYM."
- (let ((result (with-output-to-string
- (stgit-run-silent "id" patchsym))))
- (unless (string-match "^\\([0-9A-Fa-f]\\{40\\}\\)$" result)
- (error "Cannot find commit id for %s" patchsym))
- (match-string 1 result)))
+ "Return the git commit id for PATCHSYM.
+If PATCHSYM is a keyword, returns PATCHSYM unmodified."
+ (if (keywordp patchsym)
+ patchsym
+ (let ((result (with-output-to-string
+ (stgit-run-silent "id" patchsym))))
+ (unless (string-match "^\\([0-9A-Fa-f]\\{40\\}\\)$" result)
+ (error "Cannot find commit id for %s" patchsym))
+ (match-string 1 result))))
(defun stgit-show ()
"Show the patch on the current line."
(interactive)
(stgit-capture-output "*StGit patch*"
- (let ((patchsym (stgit-patch-at-point)))
- (if (not patchsym)
- (let ((patched-file (stgit-patched-file-at-point t)))
- (unless patched-file
- (error "No patch or file at point"))
- (let ((id (stgit-id (car patched-file))))
- (if (consp (cdr patched-file))
- ;; two files (copy or rename)
- (stgit-run-git "diff" "-C" "-C" (concat id "^") id "--"
- (cadr patched-file) (cddr patched-file))
- ;; just one file
- (stgit-run-git "diff" (concat id "^") id "--"
- (cdr patched-file)))))
- (stgit-run "show" "-O" "--patch-with-stat" "-O" "-M" patchsym))
- (with-current-buffer standard-output
- (goto-char (point-min))
- (diff-mode)))))
+ (case (get-text-property (point) 'entry-type)
+ ('file
+ (let ((patchsym (stgit-patch-at-point))
+ (patched-file (stgit-patched-file-at-point t)))
+ (let ((id (stgit-id (car patched-file))))
+ (if (consp (cdr patched-file))
+ ;; two files (copy or rename)
+ (stgit-run-git "diff" "-C" "-C" (concat id "^") id "--"
+ (cadr patched-file) (cddr patched-file))
+ ;; just one file
+ (stgit-run-git "diff" (concat id "^") id "--"
+ (cdr patched-file))))))
+ ('patch
+ (stgit-run "show" "-O" "--patch-with-stat" "-O" "-M" (stgit-patch-at-point)))
+ (t
+ (error "No patch or file at point")))
+ (with-current-buffer standard-output
+ (goto-char (point-min))
+ (diff-mode))))
(defun stgit-edit ()
"Edit the patch on the current line."
(interactive (list stgit-marked-patches))
(when (< (length patchsyms) 2)
(error "Need at least two patches to squash"))
- (let ((edit-buf (get-buffer-create "*StGit edit*"))
+ (let ((stgit-buffer (current-buffer))
+ (edit-buf (get-buffer-create "*StGit edit*"))
(dir default-directory)
(sorted-patchsyms (stgit-sort-patches patchsyms)))
(log-edit 'stgit-confirm-squash t nil edit-buf)
(set (make-local-variable 'stgit-patchsyms) sorted-patchsyms)
(setq default-directory dir)
- (let ((standard-output edit-buf))
- (apply 'stgit-run-silent "squash" "--save-template=-" sorted-patchsyms))))
+ (let ((result (let ((standard-output edit-buf))
+ (apply 'stgit-run-silent "squash"
+ "--save-template=-" sorted-patchsyms))))
+
+ ;; stg squash may have reordered the patches or caused conflicts
+ (with-current-buffer stgit-buffer
+ (stgit-reload))
+
+ (unless (eq 0 result)
+ (fundamental-mode)
+ (rename-buffer "*StGit error*")
+ (resize-temp-buffer-window)
+ (switch-to-buffer-other-window stgit-buffer)
+ (error "stg squash failed")))))
(defun stgit-confirm-squash ()
(interactive)