stgit.el: Repair ! for historical commits
[stgit] / contrib / stgit.el
index 275299b..b72ba98 100644 (file)
@@ -68,6 +68,16 @@ setting in an already-started StGit buffer."
   :group 'stgit
   :link '(variable-link stgit-show-ignored))
 
+(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-find-copies-harder nil
   "Try harder to find copied files when listing patches.
 
@@ -400,27 +410,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)))
+    (stgit-message "Running stg %s..." msgcmd)
+    (prog1
+        (apply 'call-process "stg" nil standard-output nil args)
+      (stgit-message "Running stg %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)))
+    (stgit-message "Running git %s..." msgcmd)
+    (prog1
+        (apply 'call-process "git" nil standard-output nil args)
+      (stgit-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))
+  (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 +450,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 +483,40 @@ 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}"))
+      (let* ((show-svn stgit-show-svn)
+             (svn-hash stgit-svn-find-rev-hash)
+             (base (stgit-id "{base}"))
              (range (format "%s~%d..%s" base stgit-committed-count 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"
+                                                    range)))
             (goto-char (point-min))
             (if (not (zerop exit-status))
                 (message "Failed to run git log")
@@ -493,9 +525,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 +603,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: "
@@ -583,8 +633,12 @@ been advised to update the stgit status when necessary.")
     (unless (and curpatch
                  (stgit-goto-patch curpatch
                                    (and curfile (stgit-file->file curfile))))
-      (goto-line curline)))
-  (stgit-refresh-git-status))
+      (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)
@@ -1042,7 +1096,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)))
@@ -1208,6 +1263,8 @@ 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"]
@@ -1292,6 +1349,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
@@ -1320,6 +1378,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'
@@ -1338,10 +1397,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)
@@ -1955,6 +2016,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)
@@ -1970,7 +2033,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)))))
@@ -2033,10 +2097,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)))))
@@ -2265,16 +2331,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
@@ -2390,24 +2453,29 @@ 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))
          (defaultcmd (if patches
-                         (concat "stg  "
+                         (concat (if git-mode "git" "stg") "  "
                                  (and hyphens "-- ")
-                                 (mapconcat 'identity patch-names " "))
+                                 (mapconcat (if git-mode 'stgit-id 'identity)
+                                            patch-names " "))
                        "stg "))
          (cmd (read-from-minibuffer "Shell command: " (cons defaultcmd 5)
                                     nil nil 'shell-command-history))
@@ -2504,67 +2572,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.
@@ -2579,6 +2649,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)