stgit.el: Default to showing index and work tree
[stgit] / contrib / stgit.el
index 0d99467..ae74b46 100644 (file)
@@ -9,6 +9,9 @@
 ;;
 ;; To start: `M-x stgit'
 
+(when (< emacs-major-version 22)
+  (error "Emacs older than 22 is not supported by stgit.el"))
+
 (require 'git nil t)
 (require 'cl)
 (require 'ewoc)
@@ -79,7 +82,9 @@ directory DIR or `default-directory'"
         (insert (propertize (if (eq status 'index) "Index" "Work tree")
                             'face face))
       (insert (format "%-30s"
-                      (propertize (symbol-name name) 'face face))
+                      (propertize (symbol-name name)
+                                  'face face
+                                  'syntax-table (string-to-syntax "w")))
               "  "
               (if (stgit-patch-empty patch) "(empty) " "")
               (propertize (or (stgit-patch-desc patch) "")
@@ -337,7 +342,7 @@ Returns nil if there was no output."
   :group 'stgit)
 
 
-(defcustom stgit-expand-find-copies-harder
+(defcustom stgit-find-copies-harder
   nil
   "Try harder to find copied files when listing patches.
 
@@ -482,10 +487,8 @@ Cf. `stgit-file-type-change-string'."
 
 (defun stgit-find-copies-harder-diff-arg ()
   "Return the flag to use with `git-diff' depending on the
-`stgit-expand-find-copies-harder' flag."
-  (if stgit-expand-find-copies-harder
-      "--find-copies-harder"
-    "-C"))
+`stgit-find-copies-harder' flag."
+  (if stgit-find-copies-harder "--find-copies-harder" "-C"))
 
 (defun stgit-insert-ls-files (args file-flag)
   (let ((start (point)))
@@ -509,7 +512,7 @@ at point."
     (with-temp-buffer
       (apply 'stgit-run-git
              (cond ((eq patchsym :work)
-                    `("diff-files" ,@args))
+                    `("diff-files" "-0" ,@args))
                    ((eq patchsym :index)
                     `("diff-index" ,@args "--cached" "HEAD"))
                    (t
@@ -569,12 +572,16 @@ at point."
                              "\n"))))
     (goto-char end)))
 
-(defun stgit-select-file ()
-  (let ((filename (expand-file-name
-                   (stgit-file-file (stgit-patched-file-at-point)))))
+(defun stgit-find-file (&optional other-window)
+  (let* ((file (or (stgit-patched-file-at-point)
+                   (error "No file at point")))
+         (filename (expand-file-name (stgit-file-file file))))
     (unless (file-exists-p filename)
       (error "File does not exist"))
-    (find-file filename)))
+    (funcall (if other-window 'find-file-other-window 'find-file)
+             filename)
+    (when (eq (stgit-file-status file) 'unmerged)
+      (smerge-mode 1))))
 
 (defun stgit-select-patch ()
   (let ((patchname (stgit-patch-name-at-point)))
@@ -594,20 +601,20 @@ file for (applied) copies and renames."
     ('patch
      (stgit-select-patch))
     ('file
-     (stgit-select-file))
+     (stgit-find-file))
     (t
      (error "No patch or file on line"))))
 
 (defun stgit-find-file-other-window ()
   "Open file at point in other window"
   (interactive)
-  (let ((patched-file (stgit-patched-file-at-point)))
-    (unless patched-file
-      (error "No file on the current line"))
-    (let ((filename (expand-file-name (stgit-file-file patched-file))))
-      (unless (file-exists-p filename)
-        (error "File does not exist"))
-      (find-file-other-window filename))))
+  (stgit-find-file t))
+
+(defun stgit-find-file-merge ()
+  "Open file at point and merge it using `smerge-ediff'."
+  (interactive)
+  (stgit-find-file t)
+  (smerge-ediff))
 
 (defun stgit-quit ()
   "Hide the stgit buffer."
@@ -662,7 +669,15 @@ file for (applied) copies and renames."
   "Keymap for StGit major mode.")
 
 (unless stgit-mode-map
-  (let ((toggle-map (make-keymap)))
+  (let ((diff-map   (make-keymap))
+        (toggle-map (make-keymap)))
+    (suppress-keymap diff-map)
+    (mapc (lambda (arg) (define-key diff-map (car arg) (cdr arg)))
+          '(("b" .        stgit-diff-base)
+            ("c" .        stgit-diff-combined)
+            ("m" .        stgit-find-file-merge)
+            ("o" .        stgit-diff-ours)
+            ("t" .        stgit-diff-theirs)))
     (suppress-keymap toggle-map)
     (mapc (lambda (arg) (define-key toggle-map (car arg) (cdr arg)))
           '(("t" .        stgit-toggle-worktree)
@@ -704,12 +719,15 @@ file for (applied) copies and renames."
             ("<" .        stgit-pop-next)
             ("P" .        stgit-push-or-pop)
             ("G" .        stgit-goto)
-            ("=" .        stgit-show)
+            ("=" .        stgit-diff)
             ("D" .        stgit-delete)
-            ([(control ?/)] . stgit-undo)
+            ([?\C-/] .    stgit-undo)
             ("\C-_" .     stgit-undo)
+            ([?\C-c ?\C-/] . stgit-redo)
+            ("\C-c\C-_" . stgit-redo)
             ("B" .        stgit-branch)
             ("t" .        ,toggle-map)
+            ("d" .        ,diff-map)
             ("q" .        stgit-quit)))))
 
 (defun stgit-mode ()
@@ -728,6 +746,7 @@ Commands:
   (set (make-local-variable 'stgit-show-worktree) stgit-default-show-worktree)
   (set (make-local-variable 'stgit-index-node) nil)
   (set (make-local-variable 'stgit-worktree-node) nil)
+  (set (make-local-variable 'parse-sexp-lookup-properties) t)
   (set-variable 'truncate-lines 't)
   (add-hook 'after-save-hook 'stgit-update-saved-file)
   (run-hooks 'stgit-mode-hook))
@@ -903,10 +922,23 @@ was modified with git commands (`stgit-repair')."
 
 (defun stgit-commit (count)
   "Run stg commit on COUNT commits.
-Interactively, the prefix argument is used as COUNT."
+Interactively, the prefix argument is used as COUNT.
+A negative COUNT will uncommit instead."
   (interactive "p")
-  (stgit-capture-output nil (stgit-run "commit" "-n" count))
-  (stgit-reload))
+  (if (< count 0)
+      (stgit-uncommit (- count))
+    (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.
+A negative COUNT will commit instead."
+  (interactive "p")
+  (if (< count 0)
+      (stgit-commit (- count))
+    (stgit-capture-output nil (stgit-run "uncommit" "-n" count))
+    (stgit-reload)))
 
 (defun stgit-revert-file ()
   "Revert the file at point, which must be in the index or the
@@ -964,13 +996,6 @@ working tree."
 
     (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."
@@ -1021,44 +1046,89 @@ If PATCHSYM is a keyword, returns PATCHSYM unmodified."
        (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*"
-    (case (get-text-property (point) 'entry-type)
-      ('file
-       (let* ((patched-file (stgit-patched-file-at-point))
-              (patch-name (stgit-patch-name-at-point))
-              (patch-id (stgit-id patch-name))
-              (args (append (and (stgit-file-cr-from patched-file)
-                                 (list (stgit-find-copies-harder-diff-arg)))
-                            (cond ((eq patch-id :index)
-                                   '("--cached"))
-                                  ((eq patch-id :work)
-                                   nil)
-                                  (t
-                                   (list (concat patch-id "^") patch-id)))
-                            '("--")
+(defun stgit-show-patch (unmerged-stage ignore-whitespace)
+  "Show the patch on the current line.
+
+UNMERGED-STAGE is the argument to `git-diff' that that selects
+which stage to diff against in the case of unmerged files."
+  (let ((space-arg (when (numberp ignore-whitespace)
+                     (cond ((> ignore-whitespace 4)
+                            "--ignore-all-space")
+                           ((> ignore-whitespace 1)
+                            "--ignore-space-change"))))
+        (patch-name (stgit-patch-name-at-point t)))
+    (stgit-capture-output "*StGit patch*"
+      (case (get-text-property (point) 'entry-type)
+        ('file
+         (let* ((patched-file (stgit-patched-file-at-point))
+                (patch-id (let ((id (stgit-id patch-name)))
+                            (if (and (eq id :index)
+                                     (eq (stgit-file-status patched-file)
+                                         'unmerged))
+                                :work
+                              id)))
+                (args (append (and space-arg (list space-arg))
+                              (and (stgit-file-cr-from patched-file)
+                                   (list (stgit-find-copies-harder-diff-arg)))
+                              (cond ((eq patch-id :index)
+                                     '("--cached"))
+                                    ((eq patch-id :work)
+                                     (list unmerged-stage))
+                                    (t
+                                     (list (concat patch-id "^") patch-id)))
+                              '("--")
                               (if (stgit-file-copy-or-rename patched-file)
                                   (list (stgit-file-cr-from patched-file)
                                         (stgit-file-cr-to patched-file))
                                 (list (stgit-file-file patched-file))))))
-         (apply 'stgit-run-git "diff" args)))
-      ('patch
-       (let* ((patch-name (stgit-patch-name-at-point))
-              (patch-id (stgit-id patch-name)))
-         (if (or (eq patch-id :index) (eq patch-id :work))
-             (apply 'stgit-run-git "diff"
-                    (stgit-find-copies-harder-diff-arg)
-                    (and (eq patch-id :index)
-                         '("--cached")))
-           (stgit-run "show" "-O" "--patch-with-stat" "-O" "-M"
-                      (stgit-patch-name-at-point)))))
-      (t
-       (error "No patch or file at point")))
-    (with-current-buffer standard-output
-      (goto-char (point-min))
-      (diff-mode))))
+           (apply 'stgit-run-git "diff" args)))
+        ('patch
+         (let* ((patch-id (stgit-id patch-name)))
+           (if (or (eq patch-id :index) (eq patch-id :work))
+               (apply 'stgit-run-git "diff"
+                      (stgit-find-copies-harder-diff-arg)
+                      (append (and space-arg (list space-arg))
+                              (if (eq patch-id :index)
+                                  '("--cached")
+                                (list unmerged-stage))))
+             (let ((args (append '("show" "-O" "--patch-with-stat" "-O" "-M")
+                                 (and space-arg (list "-O" space-arg))
+                                 (list (stgit-patch-name-at-point)))))
+               (apply 'stgit-run args)))))
+         (t
+          (error "No patch or file at point")))
+      (with-current-buffer standard-output
+        (goto-char (point-min))
+        (diff-mode)))))
+
+(defmacro stgit-define-diff (name diff-arg &optional unmerged-action)
+  `(defun ,name (&optional ignore-whitespace)
+     ,(format "Show the patch on the current line.
+
+%sWith a prefix argument, ignore whitespace. With a prefix argument
+greater than four (e.g., \\[universal-argument] \
+\\[universal-argument] \\[%s]), ignore all whitespace."
+              (if unmerged-action
+                  (format "For unmerged files, %s.\n\n" unmerged-action)
+                "")
+              name)
+     (interactive "p")
+     (stgit-show-patch ,diff-arg ignore-whitespace)))
+
+(stgit-define-diff stgit-diff
+                   "--ours" nil)
+(stgit-define-diff stgit-diff-ours
+                   "--ours"
+                   "diff against our branch")
+(stgit-define-diff stgit-diff-theirs
+                   "--theirs"
+                   "diff against their branch")
+(stgit-define-diff stgit-diff-base
+                   "--base"
+                   "diff against the merge base")
+(stgit-define-diff stgit-diff-combined
+                   "--cc"
+                   "show a combined diff")
 
 (defun stgit-move-change-to-index (file)
   "Copies the workspace state of FILE to index, using git add or git rm"
@@ -1327,7 +1397,9 @@ deepest patch had before the squash."
 
 (defun stgit-undo (&optional arg)
   "Run stg undo.
-With prefix argument, run it with the --hard flag."
+With prefix argument, run it with the --hard flag.
+
+See also `stgit-redo'."
   (interactive "P")
   (stgit-capture-output nil
     (if arg
@@ -1335,6 +1407,18 @@ With prefix argument, run it with the --hard flag."
       (stgit-run "undo")))
   (stgit-reload))
 
+(defun stgit-redo (&optional arg)
+  "Run stg redo.
+With prefix argument, run it with the --hard flag.
+
+See also `stgit-undo'."
+  (interactive "P")
+  (stgit-capture-output nil
+    (if arg
+        (stgit-run "redo" "--hard")
+      (stgit-run "redo")))
+  (stgit-reload))
+
 (defun stgit-refresh (&optional arg)
   "Run stg refresh.
 If the index contains any changes, only refresh from index.
@@ -1372,7 +1456,7 @@ See also `stgit-show-worktree'."
   :group 'stgit)
 
 (defcustom stgit-default-show-worktree
-  nil
+  t
   "Set to non-nil to by default show the working tree in a new stgit buffer.
 
 This value is used as the default value for `stgit-show-worktree'."