(cd dir)
(unless (eq 0 (call-process "git" nil t nil
"rev-parse" "--show-cdup"))
- (error "cannot find top-level git tree for %s." dir))))))
+ (error "Cannot find top-level git tree for %s" dir))))))
(expand-file-name (concat (file-name-as-directory dir)
(car (split-string cdup "\n")))))))
buf))
(defmacro stgit-capture-output (name &rest body)
- "Capture StGit output and show it in a window at the end."
+ "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."
+ (mapcar (lambda (x)
+ ;; don't use (format "%s" ...) to limit type errors
+ (cond ((stringp x) x)
+ ((integerp x) (number-to-string x))
+ ((symbolp x) (symbol-name x))
+ (t
+ (error "Bad element in stgit-make-run-args args: %S" x))))
+ args))
(defun stgit-run-silent (&rest args)
+ (setq args (stgit-make-run-args args))
(apply 'call-process "stg" nil standard-output nil args))
(defun stgit-run (&rest args)
+ (setq args (stgit-make-run-args args))
(let ((msgcmd (mapconcat #'identity args " ")))
(message "Running stg %s..." msgcmd)
(apply 'call-process "stg" nil standard-output nil args)
(message "Running stg %s...done" msgcmd)))
(defun stgit-run-git (&rest args)
+ (setq args (stgit-make-run-args args))
(let ((msgcmd (mapconcat #'identity args " ")))
(message "Running git %s..." msgcmd)
(apply 'call-process "git" nil standard-output nil args)
(message "Running git %s...done" msgcmd)))
(defun stgit-run-git-silent (&rest args)
+ (setq args (stgit-make-run-args args))
(apply 'call-process "git" nil standard-output nil args))
(defun stgit-reload ()
(erase-buffer)
(insert "Branch: ")
(stgit-run-silent "branch")
- (stgit-run-silent "series" "--description")
+ (stgit-run-silent "series" "--description" "--empty")
(stgit-rescan)
(if curpatch
(stgit-goto-patch curpatch)
(if stgit-expand-find-copies-harder
"--find-copies-harder"
"-C")
- (stgit-id (symbol-name patchsym))))))
+ (stgit-id patchsym)))))
(let (mstart)
(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]*\\)\\)"
result mstart)
(setq mstart (match-end 0))))
(when (= start (point))
(insert " <no files>\n"))
- (put-text-property start (point) 'stgit-patchsym patchsym))))
+ (put-text-property start (point) 'stgit-file-patchsym patchsym))))
+
+(defun stgit-collapse-patch (patchsym)
+ "Collapse the patch with name PATCHSYM after the line at point."
+ (save-excursion
+ (forward-line)
+ (let ((start (point)))
+ (while (eq (get-text-property (point) 'stgit-file-patchsym) patchsym)
+ (forward-line))
+ (delete-region start (point)))))
(defun stgit-rescan ()
"Rescan the status buffer."
(save-excursion
- (let ((marked ()))
+ (let ((marked ())
+ found-any)
(goto-char (point-min))
(while (not (eobp))
(cond ((looking-at "Branch: \\(.*\\)")
(put-text-property (match-beginning 1) (match-end 1)
'face 'bold))
- ((looking-at "\\([>+-]\\)\\( \\)\\([^ ]+\\) *[|#] \\(.*\\)")
- (let ((state (match-string 1))
- (patchsym (intern (match-string 3))))
+ ((looking-at "\\([0 ]\\)\\([>+-]\\)\\( \\)\\([^ ]+\\) *[|#] \\(.*\\)")
+ (setq found-any t)
+ (let ((empty (match-string 1))
+ (state (match-string 2))
+ (patchsym (intern (match-string 4))))
(put-text-property
- (match-beginning 3) (match-end 3) 'face
+ (match-beginning 4) (match-end 4) 'face
(cond ((string= state ">") 'stgit-top-patch-face)
((string= state "+") 'stgit-applied-patch-face)
((string= state "-") 'stgit-unapplied-patch-face)))
- (put-text-property (match-beginning 4) (match-end 4)
+ (put-text-property (match-beginning 5) (match-end 5)
'face 'stgit-description-face)
(when (memq patchsym stgit-marked-patches)
- (replace-match "*" nil nil nil 2)
+ (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)
(when (memq patchsym stgit-expanded-patches)
(stgit-expand-patch patchsym))
+ (when (equal "0" empty)
+ (save-excursion
+ (goto-char (match-beginning 5))
+ (insert "(empty) ")))
+ (delete-char 1)
))
((or (looking-at "stg series: Branch \".*\" not initialised")
(looking-at "stg series: .*: branch not initialized"))
+ (setq found-any t)
(forward-line 1)
(insert "Run M-x stgit-init to initialise")))
(forward-line 1))
- (setq stgit-marked-patches (nreverse marked)))))
+ (setq stgit-marked-patches (nreverse marked))
+ (unless found-any
+ (insert "\n "
+ (propertize "no patches in series"
+ 'face 'stgit-description-face))))))
+
+(defun stgit-select-file ()
+ (let ((patched-file (stgit-patched-file-at-point)))
+ (unless patched-file
+ (error "No patch or file on the current line"))
+ (let ((filename (expand-file-name (cdr patched-file))))
+ (unless (file-exists-p filename)
+ (error "File does not exist"))
+ (find-file filename))))
+
+(defun stgit-select-patch (curpath)
+ (let ((inhibit-read-only t))
+ (if (memq curpatch stgit-expanded-patches)
+ (save-excursion
+ (setq stgit-expanded-patches (delq curpatch stgit-expanded-patches))
+ (stgit-collapse-patch curpatch))
+ (progn
+ (setq stgit-expanded-patches (cons curpatch stgit-expanded-patches))
+ (stgit-expand-patch curpatch)))))
(defun stgit-select ()
"Expand or collapse the current entry"
(interactive)
(let ((curpatch (stgit-patch-at-point)))
- (if (not curpatch)
- (let ((patched-file (stgit-patched-file-at-point)))
- (unless patched-file
- (error "No patch or file on the current line"))
- (let ((filename (expand-file-name (cdr patched-file))))
- (unless (file-exists-p filename)
- (error "File does not exist"))
- (find-file filename)))
- (setq curpatch (intern curpatch))
- (setq stgit-expanded-patches
- (if (memq curpatch stgit-expanded-patches)
- (delq curpatch stgit-expanded-patches)
- (cons curpatch stgit-expanded-patches)))
- (stgit-reload))))
+ (if curpatch
+ (stgit-select-patch curpatch)
+ (stgit-select-file))))
+
(defun stgit-find-file-other-window ()
"Open file at point in other window"
"Show status using `git-status'."
(interactive)
(unless (fboundp 'git-status)
- (error "stgit-git-status requires git-status"))
+ (error "The stgit-git-status command requires git-status"))
(let ((dir default-directory))
(save-selected-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"
+ (cond ((get-text-property (point) 'stgit-file-patchsym) 4)
+ ((get-text-property (point) 'stgit-patchsym) 2)
+ (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)
("r" . stgit-refresh)
("\C-c\C-r" . stgit-rename)
("e" . stgit-edit)
- ("c" . stgit-coalesce)
+ ("M" . stgit-move-patches)
+ ("S" . stgit-squash)
("N" . stgit-new)
("R" . stgit-repair)
("C" . stgit-commit)
("D" . stgit-delete)
([(control ?/)] . stgit-undo)
("\C-_" . stgit-undo)
- ("q" . stgit-quit))))
+ ("B" . stgit-branch)
+ ("q" . stgit-quit))))
(defun stgit-mode ()
"Major mode for interacting with StGit.
(set-variable 'truncate-lines 't)
(run-hooks 'stgit-mode-hook))
-(defun stgit-add-mark (patch)
- "Mark the patch named PATCH."
- (let ((patchsym (intern patch)))
- (setq stgit-marked-patches (cons patchsym stgit-marked-patches))))
-
-(defun stgit-remove-mark (patch)
- "Unmark the patch named PATCH."
- (let ((patchsym (intern patch)))
- (setq stgit-marked-patches (delq patchsym stgit-marked-patches))))
+(defun stgit-add-mark (patchsym)
+ "Mark the patch PATCHSYM."
+ (setq stgit-marked-patches (cons patchsym stgit-marked-patches))
+ (save-excursion
+ (when (stgit-goto-patch patchsym)
+ (move-to-column 1)
+ (let ((inhibit-read-only t))
+ (insert-and-inherit ?*)
+ (delete-char 1)))))
+
+(defun stgit-remove-mark (patchsym)
+ "Unmark the patch PATCHSYM."
+ (setq stgit-marked-patches (delq patchsym stgit-marked-patches))
+ (save-excursion
+ (when (stgit-goto-patch patchsym)
+ (move-to-column 1)
+ (let ((inhibit-read-only t))
+ (insert-and-inherit ? )
+ (delete-char 1)))))
(defun stgit-clear-marks ()
"Unmark all patches."
(setq stgit-marked-patches '()))
-(defun stgit-marked-patches ()
- "Return the names of the marked patches."
- (mapcar 'symbol-name stgit-marked-patches))
-
-(defun stgit-patch-at-point (&optional cause-error allow-file)
- "Return the patch name on the current line.
-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 (and allow-file
- (let ((patchsym (get-text-property (point) 'stgit-patchsym)))
- (and patchsym
- (symbol-name patchsym))))
- (save-excursion
- (beginning-of-line)
- (and (looking-at "[>+-][ *]\\([^ ]*\\)")
- (match-string-no-properties 1)))
- (and cause-error
- (error "No patch on this line"))))
+(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."
+ (or (get-text-property (point) 'stgit-patchsym)
+ (when cause-error
+ (error "No patch on this line"))))
(defun stgit-patched-file-at-point (&optional both-files)
"Returns a cons of the patchsym and file name at point. For
copies and renames, return the new file if the patch is either
applied. If BOTH-FILES is non-nil, return a cons of the old and
the new file names instead of just one name."
- (let ((patchsym (get-text-property (point) 'stgit-patchsym))
+ (let ((patchsym (get-text-property (point) 'stgit-file-patchsym))
(file (get-text-property (point) 'stgit-file)))
(cond ((not patchsym) nil)
(file (cons patchsym file))
(t
(let ((file-sym (save-excursion
(stgit-previous-patch)
- (unless (equal (stgit-patch-at-point)
- (symbol-name patchsym))
+ (unless (eq (stgit-patch-at-point)
+ patchsym)
(error "Cannot find the %s patch" patchsym))
(beginning-of-line)
(if (= (char-after) ?-)
(cons patchsym (get-text-property (point) file-sym)))))))
(defun stgit-patches-marked-or-at-point ()
- "Return the names of the marked patches, or the patch on the current line."
+ "Return the symbols of the marked patches, or the patch on the current line."
(if stgit-marked-patches
- (stgit-marked-patches)
+ stgit-marked-patches
(let ((patch (stgit-patch-at-point)))
(if patch
(list patch)
'()))))
-(defun stgit-goto-patch (patch)
- "Move point to the line containing PATCH."
- (let ((p (point)))
- (goto-char (point-min))
- (if (re-search-forward (concat "^[>+-][ *]" (regexp-quote patch) " ")
- nil t)
- (progn (move-to-column goal-column)
- t)
+(defun stgit-goto-patch (patchsym)
+ "Move point to the line containing patch PATCHSYM.
+If that patch cannot be found, return nil."
+ (let ((p (text-property-any (point-min) (point-max)
+ 'stgit-patchsym patchsym)))
+ (when p
(goto-char p)
- nil)))
+ (move-to-column goal-column))))
(defun stgit-init ()
"Run stg init."
"Mark the patch under point."
(interactive)
(let ((patch (stgit-patch-at-point t)))
- (stgit-add-mark patch)
- (stgit-reload))
+ (stgit-add-mark patch))
(stgit-next-patch))
(defun stgit-unmark-up ()
"Remove mark from the patch on the previous line."
(interactive)
(stgit-previous-patch)
- (stgit-remove-mark (stgit-patch-at-point t))
- (stgit-reload))
+ (stgit-remove-mark (stgit-patch-at-point t)))
(defun stgit-unmark-down ()
"Remove mark from the patch on the current line."
(interactive)
(stgit-remove-mark (stgit-patch-at-point t))
- (stgit-reload)
(stgit-next-patch))
(defun stgit-rename (name)
"Rename the patch under point to NAME."
- (interactive (list (read-string "Patch name: " (stgit-patch-at-point t))))
- (let ((old-name (stgit-patch-at-point t)))
+ (interactive (list (read-string "Patch name: "
+ (symbol-name (stgit-patch-at-point t)))))
+ (let ((old-patchsym (stgit-patch-at-point t)))
(stgit-capture-output nil
- (stgit-run "rename" old-name name))
- (let ((old-name-sym (intern old-name))
- (name-sym (intern name)))
- (when (memq old-name-sym stgit-expanded-patches)
+ (stgit-run "rename" old-patchsym name))
+ (let ((name-sym (intern name)))
+ (when (memq old-patchsym stgit-expanded-patches)
(setq stgit-expanded-patches
- (cons name-sym (delq old-name-sym stgit-expanded-patches))))
- (when (memq old-name-sym stgit-marked-patches)
+ (cons name-sym (delq old-patchsym stgit-expanded-patches))))
+ (when (memq old-patchsym stgit-marked-patches)
(setq stgit-marked-patches
- (cons name-sym (delq old-name-sym stgit-marked-patches)))))
- (stgit-reload)
- (stgit-goto-patch name)))
+ (cons name-sym (delq old-patchsym stgit-marked-patches))))
+ (stgit-reload)
+ (stgit-goto-patch name-sym))))
(defun stgit-repair ()
"Run stg repair."
(stgit-run "repair"))
(stgit-reload))
-(defun stgit-commit ()
- "Run stg commit."
- (interactive)
- (stgit-capture-output nil (stgit-run "commit"))
+(defun stgit-available-branches ()
+ "Returns a list of the available stg branches"
+ (let ((output (with-output-to-string
+ (stgit-run "branch" "--list")))
+ (start 0)
+ result)
+ (while (string-match "^>?\\s-+s\\s-+\\(\\S-+\\)" output start)
+ (setq result (cons (match-string 1 output) result))
+ (setq start (match-end 0)))
+ result))
+
+(defun stgit-branch (branch)
+ "Switch to branch BRANCH."
+ (interactive (list (completing-read "Switch to branch: "
+ (stgit-available-branches))))
+ (stgit-capture-output nil (stgit-run "branch" "--" branch))
(stgit-reload))
-(defun stgit-uncommit (arg)
- "Run stg uncommit. Numeric arg determines number of patches to uncommit."
+(defun stgit-commit (count)
+ "Run stg commit on COUNT commits.
+Interactively, the prefix argument is used as COUNT."
(interactive "p")
- (stgit-capture-output nil (stgit-run "uncommit" "-n" (number-to-string arg)))
+ (stgit-capture-output nil (stgit-run "commit" "-n" count))
+ (stgit-reload))
+
+(defun stgit-uncommit (count)
+ "Run stg uncommit on COUNT commits.
+Interactively, the prefix argument is used as COUNT."
+ (interactive "p")
+ (stgit-capture-output nil (stgit-run "uncommit" "-n" count))
(stgit-reload))
(defun stgit-push-next (npatches)
"Push the first unapplied patch.
With numeric prefix argument, push that many patches."
(interactive "p")
- (stgit-capture-output nil (stgit-run "push" "-n"
- (number-to-string npatches)))
+ (stgit-capture-output nil (stgit-run "push" "-n" npatches))
(stgit-reload)
(stgit-refresh-git-status))
"Pop the topmost applied patch.
With numeric prefix argument, pop that many patches."
(interactive "p")
- (stgit-capture-output nil (stgit-run "pop" "-n" (number-to-string npatches)))
+ (stgit-capture-output nil (stgit-run "pop" "-n" npatches))
(stgit-reload)
(stgit-refresh-git-status))
(defun stgit-push-or-pop ()
"Push or pop the patch on the current line."
(interactive)
- (let ((patch (stgit-patch-at-point t))
+ (let ((patchsym (stgit-patch-at-point t))
(applied (stgit-applied-at-point)))
(stgit-capture-output nil
- (stgit-run (if applied "pop" "push") patch))
+ (stgit-run (if applied "pop" "push") patchsym))
(stgit-reload)))
(defun stgit-goto ()
"Go to the patch on the current line."
(interactive)
- (let ((patch (stgit-patch-at-point t)))
+ (let ((patchsym (stgit-patch-at-point t)))
(stgit-capture-output nil
- (stgit-run "goto" patch))
+ (stgit-run "goto" patchsym))
(stgit-reload)))
-(defun stgit-id (patch)
- "Return the git commit id for PATCH"
+(defun stgit-id (patchsym)
+ "Return the git commit id for PATCHSYM."
(let ((result (with-output-to-string
- (stgit-run-silent "id" patch))))
+ (stgit-run-silent "id" patchsym))))
(unless (string-match "^\\([0-9A-Fa-f]\\{40\\}\\)$" result)
- (error "Cannot find commit id for %s" patch))
+ (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 ((patch (stgit-patch-at-point)))
- (if (not 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 (symbol-name (car patched-file)))))
- (with-output-to-temp-buffer "*StGit diff*"
- (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)))
- (with-current-buffer standard-output
- (diff-mode)))))
- (stgit-run "show" (stgit-patch-at-point))
- (with-current-buffer standard-output
- (goto-char (point-min))
- (diff-mode))))))
+ (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)))))
(defun stgit-edit ()
"Edit the patch on the current line."
(interactive)
- (let ((patch (stgit-patch-at-point t))
+ (let ((patchsym (stgit-patch-at-point t))
(edit-buf (get-buffer-create "*StGit edit*"))
(dir default-directory))
(log-edit 'stgit-confirm-edit t nil edit-buf)
- (set (make-local-variable 'stgit-edit-patch) patch)
+ (set (make-local-variable 'stgit-edit-patchsym) patchsym)
(setq default-directory dir)
(let ((standard-output edit-buf))
- (stgit-run-silent "edit" "--save-template=-" patch))))
+ (stgit-run-silent "edit" "--save-template=-" patchsym))))
(defun stgit-confirm-edit ()
(interactive)
(let ((file (make-temp-file "stgit-edit-")))
(write-region (point-min) (point-max) file)
(stgit-capture-output nil
- (stgit-run "edit" "-f" file stgit-edit-patch))
+ (stgit-run "edit" "-f" file stgit-edit-patchsym))
(with-current-buffer log-edit-parent-buffer
(stgit-reload))))
(substring patch 0 20))
(t patch))))
-(defun stgit-delete (patch-names)
- "Delete the named patches."
- (interactive (list (stgit-patches-marked-or-at-point)))
- (if (zerop (length patch-names))
- (error "No patches to delete")
- (when (yes-or-no-p (format "Really delete %d patches? "
- (length patch-names)))
+(defun stgit-delete (patchsyms &optional spill-p)
+ "Delete the patches in PATCHSYMS.
+Interactively, delete the marked patches, or the patch at point.
+
+With a prefix argument, or SPILL-P, spill the patch contents to
+the work tree and index."
+ (interactive (list (stgit-patches-marked-or-at-point)
+ current-prefix-arg))
+ (unless patchsyms
+ (error "No patches to delete"))
+ (let ((npatches (length patchsyms)))
+ (when (yes-or-no-p (format "Really delete %d patch%s%s? "
+ npatches
+ (if (= 1 npatches) "" "es")
+ (if spill-p
+ " (spilling contents to index)"
+ "")))
+ (let ((args (if spill-p
+ (cons "--spill" patchsyms)
+ patchsyms)))
+ (stgit-capture-output nil
+ (apply 'stgit-run "delete" args))
+ (stgit-reload)))))
+
+(defun stgit-move-patches-target ()
+ "Return the patchsym indicating a target patch for
+`stgit-move-patches'.
+
+This is either the patch at point, or one of :top and :bottom, if
+the point is after or before the applied patches."
+
+ (let ((patchsym (stgit-patch-at-point)))
+ (cond (patchsym patchsym)
+ ((save-excursion (re-search-backward "^>" nil t)) :top)
+ (t :bottom))))
+
+(defun stgit-sort-patches (patchsyms)
+ "Returns the list of patches in PATCHSYMS sorted according to
+their position in the patch series, bottommost first.
+
+PATCHSYMS may not contain duplicate entries."
+ (let (sorted-patchsyms
+ (series (with-output-to-string
+ (with-current-buffer standard-output
+ (stgit-run-silent "series" "--noprefix"))))
+ start)
+ (while (string-match "^\\(.+\\)" series start)
+ (let ((patchsym (intern (match-string 1 series))))
+ (when (memq patchsym patchsyms)
+ (setq sorted-patchsyms (cons patchsym sorted-patchsyms))))
+ (setq start (match-end 0)))
+ (setq sorted-patchsyms (nreverse sorted-patchsyms))
+
+ (unless (= (length patchsyms) (length sorted-patchsyms))
+ (error "Internal error"))
+
+ sorted-patchsyms))
+
+(defun stgit-move-patches (patchsyms target-patch)
+ "Move the patches in PATCHSYMS to below TARGET-PATCH.
+If TARGET-PATCH is :bottom or :top, move the patches to the
+bottom or top of the stack, respectively.
+
+Interactively, move the marked patches to where the point is."
+ (interactive (list stgit-marked-patches
+ (stgit-move-patches-target)))
+ (unless patchsyms
+ (error "Need at least one patch to move"))
+
+ (unless target-patch
+ (error "Point not at a patch"))
+
+ (if (eq target-patch :top)
(stgit-capture-output nil
- (apply 'stgit-run "delete" patch-names))
- (stgit-reload))))
+ (apply 'stgit-run "float" patchsyms))
+
+ ;; need to have patchsyms sorted by position in the stack
+ (let ((sorted-patchsyms (stgit-sort-patches patchsyms)))
+ (while sorted-patchsyms
+ (setq sorted-patchsyms
+ (and (stgit-capture-output nil
+ (if (eq target-patch :bottom)
+ (stgit-run "sink" "--" (car sorted-patchsyms))
+ (stgit-run "sink" "--to" target-patch "--"
+ (car sorted-patchsyms))))
+ (cdr sorted-patchsyms))))))
+ (stgit-reload))
-(defun stgit-coalesce (patch-names)
- "Run stg coalesce on the named patches."
- (interactive (list (stgit-marked-patches)))
- (let ((edit-buf (get-buffer-create "*StGit edit*"))
- (dir default-directory))
- (log-edit 'stgit-confirm-coalesce t nil edit-buf)
- (set (make-local-variable 'stgit-patches) patch-names)
+(defun stgit-squash (patchsyms)
+ "Squash the patches in PATCHSYMS.
+Interactively, squash the marked patches.
+
+Unless there are any conflicts, the patches will be merged into
+one patch, which will occupy the same spot in the series as the
+deepest patch had before the squash."
+ (interactive (list stgit-marked-patches))
+ (when (< (length patchsyms) 2)
+ (error "Need at least two patches to squash"))
+ (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 "coalesce" "--save-template=-" patch-names))))
-
-(defun stgit-confirm-coalesce ()
+ (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)
(let ((file (make-temp-file "stgit-edit-")))
(write-region (point-min) (point-max) file)
(stgit-capture-output nil
- (apply 'stgit-run "coalesce" "-f" file stgit-patches))
+ (apply 'stgit-run "squash" "-f" file stgit-patchsyms))
(with-current-buffer log-edit-parent-buffer
(stgit-clear-marks)
;; Go to first marked patch and stay there
(let ((patchargs (if arg
(let ((patches (stgit-patches-marked-or-at-point)))
(cond ((null patches)
- (error "no patch to update"))
+ (error "No patch to update"))
((> (length patches) 1)
- (error "too many patches selected"))
+ (error "Too many patches selected"))
(t
(cons "-p" patches))))
nil)))