contrib/vim: Add vim syntax highlighting for StGit commit messages
[stgit] / contrib / stgit.el
index dc09b1d..c71df4b 100644 (file)
@@ -34,13 +34,6 @@ reload all StGit buffers."
   :link '(function-link stgit)
   :link '(url-link "http://www.procode.org/stgit/"))
 
-(defcustom stgit-abbreviate-copies-and-renames t
-  "If non-nil, abbreviate copies and renames as \"dir/{old -> new}/file\"
-instead of \"dir/old/file -> dir/new/file\"."
-  :type 'boolean
-  :group 'stgit
-  :set 'stgit-set-default)
-
 (defcustom stgit-default-show-worktree t
   "Set to non-nil to by default show the working tree in a new stgit buffer.
 
@@ -68,6 +61,51 @@ setting in an already-started StGit buffer."
   :group 'stgit
   :link '(variable-link stgit-show-ignored))
 
+(defcustom stgit-default-show-patch-names t
+  "If non-nil, default to showing patch names in a new stgit buffer.
+
+Use \\<stgit-mode-map>\\[stgit-toggle-patch-names] \
+to toggle the this setting in an already-started StGit buffer."
+  :type 'boolean
+  :group 'stgit
+  :link '(variable-link stgit-show-patch-names))
+
+(defcustom stgit-default-show-committed nil
+  "Set to nil to inhibit showing of historical git commits by default.
+
+Use \\<stgit-mode-map>\\[stgit-toggle-committed] \
+to toggle this setting and to control how many commits are
+shown."
+  :type 'boolean
+  :group 'stgit
+  :link '(variable-link stgit-default-committed-count)
+  :link '(variable-link stgit-show-committed))
+
+(defcustom stgit-default-committed-count 5
+  "The number of historical commits to show when `stgit-show-committed'
+is enabled."
+  :type 'number
+  :group 'stgit
+  :link '(variable-link stgit-default-show-committed)
+  :link '(variable-link stgit-committed-count))
+
+(defcustom stgit-default-show-svn t
+  "Set to non-nil to by default show subversion information in a
+new stgit buffer.
+
+Use \\<stgit-mode-map>\\[stgit-toggle-svn] to toggle this \
+setting in an already-started StGit buffer."
+  :type 'boolean
+  :group 'stgit
+  :link '(variable-link stgit-show-worktree))
+
+(defcustom stgit-abbreviate-copies-and-renames t
+  "If non-nil, abbreviate copies and renames as \"dir/{old -> new}/file\"
+instead of \"dir/old/file -> dir/new/file\"."
+  :type 'boolean
+  :group 'stgit
+  :set 'stgit-set-default)
+
 (defcustom stgit-find-copies-harder nil
   "Try harder to find copied files when listing patches.
 
@@ -126,31 +164,6 @@ The alternate form is used when the patch name is hidden."
   :group 'stgit
   :set 'stgit-set-default)
 
-(defcustom stgit-default-show-committed nil
-  "Set to nil to inhibit showing of historical git commits by default.
-
-Use \\<stgit-mode-map>\\[stgit-toggle-committed] \
-to toggle this setting and to control how many commits are
-shown."
-  :type 'boolean
-  :group 'stgit
-  :link '(variable-link stgit-show-committed))
-
-(defcustom stgit-default-committed-count 5
-  "The number of historical commits to show when `stgit-show-committed'
-is enabled."
-  :type 'number
-  :link '(variable-link stgit-committed-count))
-
-(defcustom stgit-default-show-patch-names t
-  "If non-nil, default to showing patch names in a new stgit buffer.
-
-Use \\<stgit-mode-map>\\[stgit-toggle-patch-names] \
-to toggle the this setting in an already-started StGit buffer."
-  :type 'boolean
-  :group 'stgit
-  :link '(variable-link stgit-show-patch-names))
-
 (defcustom stgit-file-line-format "    %-11s %-2m %n   %c"
   "The format string used to format file lines.
 The format string is passed to `format-spec' and the following
@@ -167,62 +180,76 @@ format characters are recognized:
   :group 'stgit
   :set 'stgit-set-default)
 
+(defcustom stgit-git-program "git"
+  "The program used by `stgit-mode' to run git."
+  :type 'string
+  :group 'stgit)
+
+(defcustom stgit-stg-program "stg"
+  "The program used by `stgit-mode' to run StGit."
+  :type 'string
+  :group 'stgit)
+
+(defgroup stgit-faces nil
+  "Faces for `stgit-mode'."
+  :group 'stgit)
+
 (defface stgit-branch-name-face
   '((t :inherit bold))
   "The face used for the StGit branch name"
-  :group 'stgit)
+  :group 'stgit-faces)
 
 (defface stgit-top-patch-face
   '((((background dark)) (:weight bold :foreground "yellow"))
     (((background light)) (:weight bold :foreground "purple"))
     (t (:weight bold)))
   "The face used for the top patch names"
-  :group 'stgit)
+  :group 'stgit-faces)
 
 (defface stgit-applied-patch-face
   '((((background dark)) (:foreground "light yellow"))
     (((background light)) (:foreground "purple"))
     (t ()))
   "The face used for applied patch names"
-  :group 'stgit)
+  :group 'stgit-faces)
 
 (defface stgit-unapplied-patch-face
   '((((background dark)) (:foreground "gray80"))
     (((background light)) (:foreground "orchid"))
     (t ()))
   "The face used for unapplied patch names"
-  :group 'stgit)
+  :group 'stgit-faces)
 
 (defface stgit-committed-patch-face
   '((((background dark)) (:foreground "gray50"))
     (((background light)) (:foreground "gray50"))
     (t ()))
   "The face used for already committed patch names"
-  :group 'stgit)
+  :group 'stgit-faces)
 
 (defface stgit-description-face
   '((((background dark)) (:foreground "tan"))
     (((background light)) (:foreground "dark red")))
   "The face used for StGit descriptions"
-  :group 'stgit)
+  :group 'stgit-faces)
 
 (defface stgit-index-work-tree-title-face
   '((((supports :slant italic)) :slant italic)
     (t :inherit bold))
   "StGit mode face used for the \"Index\" and \"Work tree\" titles"
-  :group 'stgit)
+  :group 'stgit-faces)
 
 (defface stgit-unmerged-file-face
   '((((class color) (background light)) (:foreground "red" :bold t))
     (((class color) (background dark)) (:foreground "red" :bold t)))
   "StGit mode face used for unmerged file status"
-  :group 'stgit)
+  :group 'stgit-faces)
 
 (defface stgit-unknown-file-face
   '((((class color) (background light)) (:foreground "goldenrod" :bold t))
     (((class color) (background dark)) (:foreground "goldenrod" :bold t)))
   "StGit mode face used for unknown file status"
-  :group 'stgit)
+  :group 'stgit-faces)
 
 (defface stgit-ignored-file-face
   '((((class color) (background light)) (:foreground "grey60"))
@@ -233,13 +260,13 @@ format characters are recognized:
   '((((class color) (background light)) (:foreground "green" :bold t))
     (((class color) (background dark)) (:foreground "green" :bold t)))
   "StGit mode face used for permission changes."
-  :group 'stgit)
+  :group 'stgit-faces)
 
 (defface stgit-modified-file-face
   '((((class color) (background light)) (:foreground "purple"))
     (((class color) (background dark)) (:foreground "salmon")))
   "StGit mode face used for modified file status"
-  :group 'stgit)
+  :group 'stgit-faces)
 
 (defun stgit (dir)
   "Manage StGit patches for the tree in DIR.
@@ -259,7 +286,7 @@ See `stgit-mode' for commands available."
     (let ((cdup (with-output-to-string
                   (with-current-buffer standard-output
                     (cd dir)
-                    (unless (eq 0 (call-process "git" nil t nil
+                    (unless (eq 0 (call-process stgit-git-program nil t nil
                                                 "rev-parse" "--show-cdup"))
                       (error "Cannot find top-level git tree for %s" dir))))))
       (expand-file-name (concat (file-name-as-directory dir)
@@ -400,27 +427,37 @@ Returns nil if there was no output."
                    (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))
+(defvar stgit-inhibit-messages nil
+  "Set to non-nil to inhibit messages when running `stg' commands.
+See also `stgit-message'.")
+(defun stgit-message (format-spec &rest args)
+  "Call `message' on the arguments unless `stgit-inhibit-messages' is non-nil."
+  (unless stgit-inhibit-messages
+    (apply 'message format-spec 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)))
+  (let ((msgcmd (mapconcat #'identity (cons stgit-stg-program args) " ")))
+    (stgit-message "Running %s..." msgcmd)
+    (prog1
+        (apply 'call-process stgit-stg-program nil standard-output nil args)
+      (stgit-message "Running %s...done" msgcmd))))
+
+(defun stgit-run-silent (&rest args)
+  (let ((stgit-inhibit-messages t))
+    (apply 'stgit-run args)))
 
 (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)))
+  (let ((msgcmd (mapconcat #'identity (cons stgit-git-program args) " ")))
+    (stgit-message "Running %s..." msgcmd)
+    (prog1
+        (apply 'call-process stgit-git-program nil standard-output nil args)
+      (stgit-message "Running %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))
+  (let ((stgit-inhibit-messages t))
+    (apply 'stgit-run-git args)))
 
 (defun stgit-index-empty-p ()
   "Returns non-nil if the index contains no changes from HEAD."
@@ -430,9 +467,6 @@ Returns nil if there was no output."
   "Returns non-nil if the work tree contains no changes from index."
   (zerop (stgit-run-git-silent "diff-files" "--quiet")))
 
-(defvar stgit-index-node)
-(defvar stgit-worktree-node)
-
 (defvar stgit-did-advise nil
   "Set to non-nil if appropriate (non-stgit) git functions have
 been advised to update the stgit status when necessary.")
@@ -466,25 +500,41 @@ been advised to update the stgit status when necessary.")
                                                    :desc nil
                                                    :empty nil)))))
 
+(defun stgit-svn-find-rev (sha1 hash)
+  "Return the subversion revision corresponding to SHA1 as
+reported by git svn.
+
+Cached data is stored in HASH, which must have been created
+using (make-hash-table :test 'equal)."
+  (let ((result (gethash sha1 hash t)))
+    (when (eq result t)
+      (let ((svn-rev (with-output-to-string
+                       (stgit-run-git-silent "svn" "find-rev"
+                                             "--" sha1))))
+        (setq result (when (string-match "\\`[0-9]+" svn-rev)
+                       (string-to-number (match-string 0 svn-rev))))
+        (puthash sha1 result hash)))
+    result))
+
 (defun stgit-run-series (ewoc)
   (setq stgit-index-node nil
         stgit-worktree-node nil)
   (let (all-patchsyms)
     (when stgit-show-committed
-      (let* ((base (stgit-id "{base}"))
-             (range (format "%s~%d..%s" base stgit-committed-count base)))
+      (let* ((show-svn stgit-show-svn)
+             (svn-hash stgit-svn-find-rev-hash)
+             (nentries (format "-%s" stgit-committed-count))
+             (base (stgit-id "{base}")))
         (with-temp-buffer
           (let* ((standard-output (current-buffer))
                  (fmt (stgit-line-format))
                  (commit-abbrev (when (string-match "%-\\([0-9]+\\)n" fmt)
-                                  (list (format "--abbrev=%s"
-                                                (match-string 1 fmt)))))
-                 (exit-status (apply 'stgit-run-git-silent
-                                     "--no-pager"
-                                     "log" "--reverse" "--pretty=oneline"
-                                     "--abbrev-commit"
-                                     `(,@commit-abbrev
-                                       ,range))))
+                                  (string-to-number (match-string 1 fmt))))
+                 (exit-status (stgit-run-git-silent "--no-pager" "log"
+                                                    "--reverse"
+                                                    "--pretty=oneline"
+                                                    nentries
+                                                    base)))
             (goto-char (point-min))
             (if (not (zerop exit-status))
                 (message "Failed to run git log")
@@ -493,9 +543,21 @@ been advised to update the stgit status when necessary.")
                          "\\([0-9a-f]+\\)\\(\\.\\.\\.\\)? \\(.*\\)")
                   (error "Syntax error in output from git log"))
                 (let* ((state 'committed)
-                       (name (intern (match-string 1)))
+                       (name (match-string 1))
                        (desc (match-string 3))
                        (empty nil))
+
+                  (when show-svn
+                    (let ((svn-rev (stgit-svn-find-rev name svn-hash)))
+                      (when svn-rev
+                        (setq desc (format "(r%s) %s" svn-rev desc)))))
+
+                  (and commit-abbrev
+                       (< commit-abbrev (length name))
+                       (setq name (substring name 0 commit-abbrev)))
+
+                  (setq name (intern name))
+
                   (setq all-patchsyms (cons name all-patchsyms))
                   (ewoc-enter-last ewoc
                                    (make-stgit-patch
@@ -559,14 +621,20 @@ been advised to update the stgit status when necessary.")
                (stgit-run-silent "branch"))
              0 -1))
 
-(defun stgit-reload ()
-  "Update the contents of the StGit buffer."
+(defun stgit-reload (&optional description)
+  "Update the contents of the StGit buffer.
+
+If DESCRIPTION is non-nil, it is displayed as a status message
+during the operation."
   (interactive)
   (stgit-assert-mode)
+  (when description
+    (message "%s..." description))
   (let ((inhibit-read-only t)
         (curline (line-number-at-pos))
         (curpatch (stgit-patch-name-at-point))
-        (curfile (stgit-patched-file-at-point)))
+        (curfile (stgit-patched-file-at-point))
+        (stgit-inhibit-messages description))
     (ewoc-filter stgit-ewoc #'(lambda (x) nil))
     (ewoc-set-hf stgit-ewoc
                  (concat "Branch: "
@@ -580,10 +648,15 @@ been advised to update the stgit status when necessary.")
  shows the working tree\n")
                     'face 'stgit-description-face)))
     (stgit-run-series stgit-ewoc)
-    (if curpatch
-        (stgit-goto-patch curpatch (and curfile (stgit-file->file curfile)))
-      (goto-line curline)))
-  (stgit-refresh-git-status))
+    (unless (and curpatch
+                 (stgit-goto-patch curpatch
+                                   (and curfile (stgit-file->file curfile))))
+      (goto-char (point-min))
+      (forward-line (1- curline))
+      (move-to-column (stgit-goal-column)))
+    (stgit-refresh-git-status))
+  (when description
+    (message "%s...done" description)))
 
 (defconst stgit-file-status-code-strings
   (mapcar (lambda (arg)
@@ -1010,15 +1083,80 @@ file for (applied) copies and renames."
   "Move cursor down ARG patches."
   (interactive "p")
   (stgit-assert-mode)
-  (ewoc-goto-next stgit-ewoc (or arg 1))
-  (move-to-column goal-column))
+  (unless arg (setq arg 1))
+  (cond ((< arg 0)
+         (stgit-previous-patch (- arg)))
+        ((zerop arg)
+         (move-to-column (stgit-goal-column)))
+        (t
+         (when (stgit-at-header-p)
+           (ewoc-goto-node stgit-ewoc (ewoc-nth stgit-ewoc 0))
+           (setq arg (1- arg)))
+         (ewoc-goto-next stgit-ewoc arg)
+         (move-to-column goal-column))))
 
 (defun stgit-previous-patch (&optional arg)
   "Move cursor up ARG patches."
   (interactive "p")
   (stgit-assert-mode)
-  (ewoc-goto-prev stgit-ewoc (or arg 1))
-  (move-to-column goal-column))
+  (unless arg (setq arg 1))
+  (cond ((< arg 0)
+         (stgit-next-patch (- arg)))
+        ((zerop arg)
+         (move-to-column (stgit-goal-column)))
+        ((stgit-at-header-p)
+         (goto-char (point-min)))
+        (t
+         (let ((opatch (stgit-patch-at-point)))
+           (ewoc-goto-prev stgit-ewoc arg)
+           (unless (zerop arg)
+             (when (eq opatch (stgit-patch-at-point))
+               (goto-char (point-min)))))
+         (move-to-column (stgit-goal-column)))))
+
+(defun stgit-previous-patch-group (&optional arg)
+  "Move to the previous group of patches.
+
+If ARG is non-nil, do this ARG times. If ARG is negative, move
+-ARG groups forward instead; cf. `stgit-next-patch-group'."
+  (interactive "p")
+  (stgit-assert-mode)
+  (if (< arg 0)
+      (stgit-previous-patch-group (- arg))
+    (while (and (not (bobp))
+                (> arg 0))
+      (stgit-previous-patch 1)
+      (let* ((opoint (point))
+             (patch  (stgit-patch-at-point))
+             (status (and patch (stgit-patch->status patch))))
+        (while (and (not (bobp))
+                    (let* ((npatch (stgit-patch-at-point))
+                           (nstatus (and npatch (stgit-patch->status npatch))))
+                      (eq status nstatus)))
+          (setq opoint (point))
+          (stgit-previous-patch 1))
+        (goto-char opoint))
+      (setq arg (1- arg)))))
+
+(defun stgit-next-patch-group (&optional arg)
+  "Move to the next group of patches.
+
+If ARG is non-nil, do this ARG times. If ARG is negative, move
+-ARG groups backwards instead; cf. `stgit-previous-patch-group'."
+  (interactive "p")
+  (stgit-assert-mode)
+  (if (< arg 0)
+      (stgit-previous-patch-group (- arg))
+    (while (and (not (eobp))
+                (> arg 0))
+      (let* ((patch  (stgit-patch-at-point))
+             (status (and patch (stgit-patch->status patch))))
+        (while (and (not (eobp))
+                    (let* ((npatch (stgit-patch-at-point))
+                           (nstatus (and npatch (stgit-patch->status npatch))))
+                      (eq status nstatus)))
+          (stgit-next-patch 1)))
+      (setq arg (1- arg)))))
 
 (defvar stgit-mode-hook nil
   "Run after `stgit-mode' is setup.")
@@ -1041,7 +1179,8 @@ file for (applied) copies and renames."
             ("t" .        stgit-toggle-worktree)
             ("h" .        stgit-toggle-committed)
             ("i" .        stgit-toggle-ignored)
-            ("u" .        stgit-toggle-unknown)))
+            ("u" .        stgit-toggle-unknown)
+            ("s" .        stgit-toggle-svn)))
     (setq stgit-mode-map (make-keymap))
     (suppress-keymap stgit-mode-map)
     (mapc (lambda (arg) (define-key stgit-mode-map (car arg) (cdr arg)))
@@ -1057,8 +1196,10 @@ file for (applied) copies and renames."
             ([down] .     stgit-next-line)
             ("p" .        stgit-previous-patch)
             ("n" .        stgit-next-patch)
-            ("\M-{" .     stgit-previous-patch)
-            ("\M-}" .     stgit-next-patch)
+            ("\M-{" .     stgit-previous-patch-group)
+            ("\M-}" .     stgit-next-patch-group)
+            ([(control up)] .   stgit-previous-patch-group)
+            ([(control down)] . stgit-next-patch-group)
             ("s" .        stgit-git-status)
             ("g" .        stgit-reload-or-repair)
             ("r" .        stgit-refresh)
@@ -1207,11 +1348,15 @@ file for (applied) copies and renames."
          :selected stgit-show-patch-names]
         ["Show recent commits" stgit-toggle-committed :style toggle
          :selected stgit-show-committed]
+        ["Show subversion info" stgit-toggle-svn :style toggle
+         :selected stgit-show-svn]
         "-"
         ["Switch branches" stgit-branch t
          :help "Switch to or create another branch"]
         ["Rebase branch" stgit-rebase t
          :help "Rebase the current branch"]
+        "-"
+        ["Customize StGit" (customize-group 'stgit)]
         ))))
 
 ;; disable tool bar editing buttons
@@ -1243,6 +1388,8 @@ Movement commands:
 \\[stgit-next-line]    Move to next line
 \\[stgit-previous-patch]       Move to previous patch
 \\[stgit-next-patch]   Move to next patch
+\\[stgit-previous-patch-group] Move to previous patch group
+\\[stgit-next-patch-group]     Move to next patch group
 
 \\[stgit-mark-down]    Mark patch and move down
 \\[stgit-unmark-up]    Unmark patch and move up
@@ -1291,6 +1438,7 @@ Display commands:
 \\[stgit-toggle-unknown]       Toggle showing unknown files
 \\[stgit-toggle-ignored]       Toggle showing ignored files
 \\[stgit-toggle-committed]     Toggle showing recent commits
+\\[stgit-toggle-svn]   Toggle showing subversion information
 
 Commands for diffs:
 \\[stgit-diff] Show diff of patch or file
@@ -1319,6 +1467,7 @@ Customization variables:
 `stgit-default-show-unknown'
 `stgit-default-show-worktree'
 `stgit-default-show-committed'
+`stgit-default-show-svn'
 `stgit-default-committed-count'
 `stgit-find-copies-harder'
 `stgit-show-worktree-mode'
@@ -1337,10 +1486,12 @@ See also \\[customize-group] for the \"stgit\" group."
           (stgit-index-node             . nil)
           (stgit-worktree-node          . nil)
           (stgit-marked-patches         . nil)
+          (stgit-svn-find-rev-hash      . ,(make-hash-table :test 'equal))
           (stgit-committed-count        . ,stgit-default-committed-count)
           (stgit-show-committed         . ,stgit-default-show-committed)
           (stgit-show-ignored           . ,stgit-default-show-ignored)
           (stgit-show-patch-names       . ,stgit-default-show-patch-names)
+          (stgit-show-svn               . ,stgit-default-show-svn)
           (stgit-show-unknown           . ,stgit-default-show-unknown)
           (stgit-show-worktree          . ,stgit-default-show-worktree)))
   (set-variable 'truncate-lines 't)
@@ -1482,8 +1633,8 @@ allow historical commits; if nil, also allow work tree and index."
             (t nil)))))
 
 (defun stgit-goto-patch (patchsym &optional file)
-  "Move point to the line containing patch PATCHSYM.
-If that patch cannot be found, do nothing.
+  "Move point to the line containing patch PATCHSYM and return non-nil.
+If that patch cannot be found, do nothing and return nil.
 
 If the patch was found and FILE is not nil, instead move to that
 file's line. If FILE cannot be found, stay on the line of
@@ -1881,6 +2032,14 @@ If ONLY-PATCHES is not nil, exclude index and work tree."
              (stgit-sort-patches (if unapplied unapplied patchsyms)))))
   (stgit-reload))
 
+(defun stgit-at-header-p ()
+  "Return non-nil if point is in the header area above all patches."
+  (not (previous-single-property-change (point) 'patch-data)))
+
+(defun stgit-at-footer-p ()
+  "Return non-nil if point is in the footer area below all patches."
+  (not (next-single-property-change (point) 'patch-data)))
+
 (defun stgit-goto-target ()
   "Return the goto target at point: a patchsym, :top,
 or :bottom."
@@ -1890,9 +2049,9 @@ or :bottom."
              ((work index) nil)
              ((committed) :bottom)
              (t (stgit-patch->name patch))))
-          ((not (next-single-property-change (point) 'patch-data))
+          ((stgit-at-footer-p)
            :top)
-          ((not (previous-single-property-change (point) 'patch-data))
+          ((stgit-at-header-p)
            :bottom))))
 
 (defun stgit-goto ()
@@ -1954,6 +2113,8 @@ which stage to diff against in the case of unmerged files."
                                      (list unmerged-stage))
                                     (t
                                      (list (concat patch-id "^") patch-id)))
+                              (and (eq (stgit-file->status patched-file) 'copy)
+                                   '("--diff-filter=C"))
                               '("--")
                               (if (stgit-file->copy-or-rename patched-file)
                                   (list (stgit-file->cr-from patched-file)
@@ -1969,7 +2130,8 @@ which stage to diff against in the case of unmerged files."
                               (if (eq patch-id :index)
                                   '("--cached")
                                 (list unmerged-stage))))
-             (let ((args (append '("show" "-O" "--patch-with-stat" "-O" "-M")
+             (let ((args (append '("show" "-O" "--patch-with-stat")
+                                 `("-O" ,(stgit-find-copies-harder-diff-arg))
                                  (and space-arg (list "-O" space-arg))
                                  '("--")
                                  (list (stgit-patch-name-at-point)))))
@@ -2032,10 +2194,12 @@ greater than four (e.g., \\[universal-argument] \
       (error "Can only show diff range for applied patches"))
     (stgit-capture-output (format "*StGit diff %s..%s*"
                                   first-patch second-patch)
-      (apply 'stgit-run-git (append '("diff" "--patch-with-stat")
-                                    (and whitespace-arg (list whitespace-arg))
-                                    (list (format "%s^" (stgit-id first-patch))
-                                          (stgit-id second-patch))))
+      (apply 'stgit-run-git
+             "diff" "--patch-with-stat"
+             (stgit-find-copies-harder-diff-arg)
+             (append (and whitespace-arg (list whitespace-arg))
+                     (list (format "%s^" (stgit-id first-patch))
+                           (stgit-id second-patch))))
       (with-current-buffer standard-output
         (goto-char (point-min))
         (diff-mode)))))
@@ -2264,16 +2428,13 @@ their position in the patch series, bottommost first.
 
 PATCHSYMS must not contain duplicate entries, unless
 ALLOW-DUPLICATES is not nil."
-  (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)))
+  (let (sorted-patchsyms)
+    (ewoc-map #'(lambda (patch)
+                  (let ((name (stgit-patch->name patch)))
+                    (when (memq name patchsyms)
+                      (setq sorted-patchsyms (cons name sorted-patchsyms))))
+                  nil)
+              stgit-ewoc)
     (setq sorted-patchsyms (nreverse sorted-patchsyms))
 
     (unless allow-duplicates
@@ -2389,26 +2550,34 @@ deepest patch had before the squash."
       (unless at-pmark
         (goto-char old-point)))))
 
-(defun stgit-execute ()
+(defun stgit-execute (&optional git-mode)
   "Prompt for an stg command to execute in a shell.
 
 The names of any marked patches or the patch at point are
 inserted in the command to be executed.
 
+With a prefix argument, or if GIT-MODE is non-nil, insert SHA1
+sums of the marked patches instead, and prompt for a git command.
+
 If the command ends in an ampersand, run it asynchronously.
 
 When the command has finished, reload the stgit buffer."
-  (interactive)
+  (interactive "P")
   (stgit-assert-mode)
-  (let* ((patches (stgit-patches-marked-or-at-point nil 'allow-committed))
+  (let* ((patches (stgit-sort-patches
+                   (stgit-patches-marked-or-at-point nil 'allow-committed)))
          (patch-names (mapcar 'symbol-name patches))
          (hyphens (find-if (lambda (s) (string-match "^-" s)) patch-names))
+         (program (if git-mode stgit-git-program stgit-stg-program))
          (defaultcmd (if patches
-                         (concat "stg  "
+                         (concat program
+                                 "  "
                                  (and hyphens "-- ")
-                                 (mapconcat 'identity patch-names " "))
-                       "stg "))
-         (cmd (read-from-minibuffer "Shell command: " (cons defaultcmd 5)
+                                 (mapconcat (if git-mode 'stgit-id 'identity)
+                                            patch-names " "))
+                       (concat stgit-stg-program " ")))
+         (cmd (read-from-minibuffer "Shell command: "
+                                    (cons defaultcmd (+ (length program) 2))
                                     nil nil 'shell-command-history))
          (async (string-match "&[ \t]*\\'" cmd))
          (buffer (get-buffer-create
@@ -2474,7 +2643,7 @@ With prefix argument, refresh the marked patch or the patch under point."
   (interactive "P")
   (stgit-assert-mode)
   (let ((patchargs (if arg
-                       (let ((patches (stgit-patches-marked-or-at-point nil t)))
+                       (let ((patches (stgit-patches-marked-or-at-point t t)))
                          (when (> (length patches) 1)
                            (error "Too many patches marked"))
                          (cons "-p" patches))
@@ -2503,67 +2672,69 @@ See also `stgit-show-worktree-mode'.")
 (defvar stgit-show-committed nil
   "If nil, inhibit showing recent commits.")
 
+(defvar stgit-show-svn nil
+  "If nil, inhibit showing git svn information.")
+
 (defvar stgit-committed-count nil
   "The number of recent commits to show.")
 
-(defun stgit-toggle-worktree (&optional arg)
+(defmacro stgit-define-toggle-view (sym desc help)
+  (declare (indent 1))
+  (let* ((name (symbol-name sym))
+         (fun  (intern (concat "stgit-toggle-" name)))
+         (flag (intern (concat "stgit-show-" name))))
+    ;; make help-follow find the correct function
+    `(put (quote ,fun) 'definition-name 'stgit-define-toggle-view)
+    `(defun ,fun (&optional arg)
+       ,help
+       (interactive "P")
+       (stgit-assert-mode)
+       (setq ,flag (if arg
+                       (> (prefix-numeric-value arg) 0)
+                     (not ,flag)))
+       (stgit-reload (format "%s %s" (if ,flag "Showing" "Hiding") ,desc)))))
+
+(stgit-define-toggle-view worktree
+  "work tree and index"
   "Toggle the visibility of the work tree.
 With ARG, show the work tree if ARG is positive.
 
 Its initial setting is controlled by `stgit-default-show-worktree'.
 
 `stgit-show-worktree-mode' controls where on screen the index and
-work tree will show up."
-  (interactive)
-  (stgit-assert-mode)
-  (setq stgit-show-worktree
-        (if (numberp arg)
-            (> arg 0)
-          (not stgit-show-worktree)))
-  (stgit-reload))
+work tree will show up.")
 
-(defun stgit-toggle-ignored (&optional arg)
+(stgit-define-toggle-view ignored
+  "ignored files"
   "Toggle the visibility of files ignored by git in the work
 tree. With ARG, show these files if ARG is positive.
 
 Its initial setting is controlled by `stgit-default-show-ignored'.
 
-Use \\[stgit-toggle-worktree] to show the work tree."
-  (interactive)
-  (stgit-assert-mode)
-  (setq stgit-show-ignored
-        (if (numberp arg)
-            (> arg 0)
-          (not stgit-show-ignored)))
-  (stgit-reload))
+Use \\[stgit-toggle-worktree] to show the work tree.")
 
-(defun stgit-toggle-unknown (&optional arg)
+(stgit-define-toggle-view unknown
+  "unknown files"
   "Toggle the visibility of files not registered with git in the
 work tree. With ARG, show these files if ARG is positive.
 
 Its initial setting is controlled by `stgit-default-show-unknown'.
 
-Use \\[stgit-toggle-worktree] to show the work tree."
-  (interactive)
-  (stgit-assert-mode)
-  (setq stgit-show-unknown
-        (if (numberp arg)
-            (> arg 0)
-          (not stgit-show-unknown)))
-  (stgit-reload))
+Use \\[stgit-toggle-worktree] to show the work tree.")
 
-(defun stgit-toggle-patch-names (&optional arg)
+(stgit-define-toggle-view patch-names
+  "patch names"
   "Toggle the visibility of patch names. With ARG, show patch names
 if ARG is positive.
 
-The initial setting is controlled by `stgit-default-show-patch-names'."
-  (interactive)
-  (stgit-assert-mode)
-  (setq stgit-show-patch-names
-        (if (numberp arg)
-            (> arg 0)
-          (not stgit-show-patch-names)))
-  (stgit-reload))
+The initial setting is controlled by `stgit-default-show-patch-names'.")
+
+(stgit-define-toggle-view svn
+  "subversion revisions"
+  "Toggle showing subversion information from git svn. With ARG,
+show svn information if ARG is positive.
+
+The initial setting is controlled by `stgit-default-show-svn'.")
 
 (defun stgit-toggle-committed (&optional arg)
   "Toggle the visibility of historical git commits.
@@ -2578,6 +2749,10 @@ The initial setting is controlled by `stgit-default-show-committed'."
     (let ((n (prefix-numeric-value arg)))
       (setq stgit-show-committed (> n 0))
       (setq stgit-committed-count n)))
-  (stgit-reload))
+  (stgit-reload (format "%s historical commits"
+                        (if (and stgit-show-committed
+                                 (> stgit-committed-count 0))
+                            "Showing"
+                          "Hiding"))))
 
 (provide 'stgit)