X-Git-Url: https://git.distorted.org.uk/~mdw/stgit/blobdiff_plain/072e96c5f887e56e2dbd39df6ff586b62c1a6a1a..bf7e391cbf0673575fe73915fd71c0920d0f217a:/contrib/stgit.el diff --git a/contrib/stgit.el b/contrib/stgit.el index 56de515..dc09b1d 100644 --- a/contrib/stgit.el +++ b/contrib/stgit.el @@ -14,6 +14,7 @@ (require 'git nil t) (require 'cl) +(require 'comint) (require 'ewoc) (require 'easymenu) (require 'format-spec) @@ -43,11 +44,30 @@ instead of \"dir/old/file -> dir/new/file\"." (defcustom stgit-default-show-worktree t "Set to non-nil to by default show the working tree in a new stgit buffer. -Use \\\\[stgit-toggle-worktree] to toggle the this setting in an already-started StGit buffer." +Use \\\\[stgit-toggle-worktree] to toggle this \ +setting in an already-started StGit buffer." :type 'boolean :group 'stgit :link '(variable-link stgit-show-worktree)) +(defcustom stgit-default-show-unknown nil + "Set to non-nil to by default show unknown files a new stgit buffer. + +Use \\\\[stgit-toggle-unknown] to toggle this \ +setting in an already-started StGit buffer." + :type 'boolean + :group 'stgit + :link '(variable-link stgit-show-unknown)) + +(defcustom stgit-default-show-ignored nil + "Set to non-nil to by default show ignored files a new stgit buffer. + +Use \\\\[stgit-toggle-ignored] to toggle this \ +setting in an already-started StGit buffer." + :type 'boolean + :group 'stgit + :link '(variable-link stgit-show-ignored)) + (defcustom stgit-find-copies-harder nil "Try harder to find copied files when listing patches. @@ -98,19 +118,35 @@ variable is used instead." (defcustom stgit-noname-patch-line-format "%s%m%e%D" "The alternate format string used to format patch lines. It has the same semantics as `stgit-patch-line-format', and the -display can be toggled between the two formats using -\\>\\[stgit-toggle-patch-names]. +display can be toggled between the two formats using \ +\\\\[stgit-toggle-patch-names]. The alternate form is used when the patch name is hidden." :type 'string :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-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-toggle-patch-names] to toggle the -this setting in an already-started StGit buffer." +Use \\\\[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)) @@ -157,6 +193,13 @@ format characters are recognized: "The face used for unapplied patch names" :group 'stgit) +(defface stgit-committed-patch-face + '((((background dark)) (:foreground "gray50")) + (((background light)) (:foreground "gray50")) + (t ())) + "The face used for already committed patch names" + :group 'stgit) + (defface stgit-description-face '((((background dark)) (:foreground "tan")) (((background light)) (:foreground "dark red"))) @@ -269,14 +312,19 @@ A newline is appended." (error)) (insert (match-string 1 text) ?\n)) +(defun stgit-line-format () + "Return the current line format; one of +`stgit-patch-line-format' and `stgit-noname-patch-line-format'" + (if stgit-show-patch-names + stgit-patch-line-format + stgit-noname-patch-line-format)) + (defun stgit-patch-pp (patch) (let* ((status (stgit-patch->status patch)) (start (point)) (name (stgit-patch->name patch)) (face (cdr (assq status stgit-patch-status-face-alist))) - (fmt (if stgit-show-patch-names - stgit-patch-line-format - stgit-noname-patch-line-format)) + (fmt (stgit-line-format)) (spec (format-spec-make ?s (case status ('applied "+") @@ -385,6 +433,10 @@ Returns nil if there was no output." (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.") + (defconst stgit-allowed-branch-name-re ;; Disallow control characters, space, del, and "/:@^{}~" in ;; "/"-separated parts; parts may not start with a period (.) @@ -417,55 +469,89 @@ Returns nil if there was no output." (defun stgit-run-series (ewoc) (setq stgit-index-node nil stgit-worktree-node nil) - (let ((inserted-index (not stgit-show-worktree)) - index-node - worktree-node - all-patchsyms) - (with-temp-buffer - (let* ((standard-output (current-buffer)) - (exit-status (stgit-run-silent "series" - "--description" "--empty"))) - (goto-char (point-min)) - (if (not (zerop exit-status)) - (cond ((looking-at "stg series: \\(.*\\)") - (setq inserted-index t) - (ewoc-set-hf ewoc (car (ewoc-get-hf ewoc)) - (substitute-command-keys - "-- not initialized; run \\[stgit-init]"))) - ((looking-at ".*") - (error "Error running stg: %s" - (match-string 0)))) - (while (not (eobp)) - (unless (looking-at - "\\([0 ]\\)\\([>+-]\\)\\( \\)\\([^ ]+\\) *[|#] \\(.*\\)") - (error "Syntax error in output from stg series")) - (let* ((state-str (match-string 2)) - (state (cond ((string= state-str ">") 'top) - ((string= state-str "+") 'applied) - ((string= state-str "-") 'unapplied))) - (name (intern (match-string 4))) - (desc (match-string 5)) - (empty (string= (match-string 1) "0"))) - (unless inserted-index - (when (or (eq stgit-show-worktree-mode 'top) - (and (eq stgit-show-worktree-mode 'center) - (eq state 'unapplied))) - (setq inserted-index t) - (stgit-run-series-insert-index ewoc))) - (setq all-patchsyms (cons name all-patchsyms)) - (ewoc-enter-last ewoc - (make-stgit-patch - :status state - :name name - :desc desc - :empty empty))) - (forward-line 1)))) + (let (all-patchsyms) + (when stgit-show-committed + (let* ((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)))) + (goto-char (point-min)) + (if (not (zerop exit-status)) + (message "Failed to run git log") + (while (not (eobp)) + (unless (looking-at + "\\([0-9a-f]+\\)\\(\\.\\.\\.\\)? \\(.*\\)") + (error "Syntax error in output from git log")) + (let* ((state 'committed) + (name (intern (match-string 1))) + (desc (match-string 3)) + (empty nil)) + (setq all-patchsyms (cons name all-patchsyms)) + (ewoc-enter-last ewoc + (make-stgit-patch + :status state + :name name + :desc desc + :empty empty))) + (forward-line 1))))))) + (let ((inserted-index (not stgit-show-worktree)) + index-node + worktree-node) + (with-temp-buffer + (let* ((standard-output (current-buffer)) + (exit-status (stgit-run-silent "series" + "--description" "--empty"))) + (goto-char (point-min)) + (if (not (zerop exit-status)) + (cond ((looking-at "stg series: \\(.*\\)") + (setq inserted-index t) + (ewoc-set-hf ewoc (car (ewoc-get-hf ewoc)) + (substitute-command-keys + "-- not initialized; run \\[stgit-init]"))) + ((looking-at ".*") + (error "Error running stg: %s" + (match-string 0)))) + (while (not (eobp)) + (unless (looking-at + "\\([0 ]\\)\\([>+-]\\)\\( \\)\\([^ ]+\\) *[|#] \\(.*\\)") + (error "Syntax error in output from stg series")) + (let* ((state-str (match-string 2)) + (state (cond ((string= state-str ">") 'top) + ((string= state-str "+") 'applied) + ((string= state-str "-") 'unapplied))) + (name (intern (match-string 4))) + (desc (match-string 5)) + (empty (string= (match-string 1) "0"))) + (unless inserted-index + (when (or (eq stgit-show-worktree-mode 'top) + (and (eq stgit-show-worktree-mode 'center) + (eq state 'unapplied))) + (setq inserted-index t) + (stgit-run-series-insert-index ewoc))) + (setq all-patchsyms (cons name all-patchsyms)) + (ewoc-enter-last ewoc + (make-stgit-patch + :status state + :name name + :desc desc + :empty empty))) + (forward-line 1))))) (unless inserted-index - (stgit-run-series-insert-index ewoc))) - (setq stgit-index-node index-node - stgit-worktree-node worktree-node - stgit-marked-patches (intersection stgit-marked-patches - all-patchsyms)))) + (stgit-run-series-insert-index ewoc)) + (setq stgit-index-node index-node + stgit-worktree-node worktree-node + stgit-marked-patches (intersection stgit-marked-patches + all-patchsyms))))) (defun stgit-current-branch () "Return the name of the current branch." @@ -518,6 +604,7 @@ Returns nil if there was no output." '((applied . stgit-applied-patch-face) (top . stgit-top-patch-face) (unapplied . stgit-unapplied-patch-face) + (committed . stgit-committed-patch-face) (index . stgit-index-work-tree-title-face) (work . stgit-index-work-tree-title-face)) "Alist of face to use for a given patch status") @@ -728,16 +815,20 @@ Cf. `stgit-file-type-change-string'." (defun stgit-insert-patch-files (patch) "Expand (show modification of) the patch PATCH after the line at point." - (let* ((patchsym (stgit-patch->name patch)) - (end (point-marker)) - (args (list "-z" (stgit-find-copies-harder-diff-arg))) - (ewoc (ewoc-create #'stgit-file-pp nil nil t))) + (let* ((patchsym (stgit-patch->name patch)) + (end (point-marker)) + (args (list "-z" (stgit-find-copies-harder-diff-arg))) + (ewoc (ewoc-create #'stgit-file-pp nil nil t)) + (show-ignored stgit-show-ignored) + (show-unknown stgit-show-unknown)) (set-marker-insertion-type end t) (setf (stgit-patch->files-ewoc patch) ewoc) (with-temp-buffer (let ((standard-output (current-buffer))) (apply 'stgit-run-git (cond ((eq patchsym :work) + (let (standard-output) + (stgit-run-git "update-index" "--refresh")) `("diff-files" "-0" ,@args)) ((eq patchsym :index) `("diff-index" ,@args "--cached" "HEAD")) @@ -745,9 +836,9 @@ at point." `("diff-tree" ,@args "-r" ,(stgit-id patchsym))))) (when (and (eq patchsym :work)) - (when stgit-show-ignored + (when show-ignored (stgit-insert-ls-files '("--ignored" "--others") "I")) - (when stgit-show-unknown + (when show-unknown (stgit-insert-ls-files '("--directory" "--no-empty-directory" "--others") "X")) @@ -836,6 +927,8 @@ See also `stgit-expand'." (stgit-process-files (lambda (f) (setq node (ewoc-enter-after ewoc node f)))))) + (move-to-column (stgit-goal-column)) + (let ((inhibit-read-only t)) (put-text-property start end 'patch-data patch)))) @@ -936,7 +1029,6 @@ file for (applied) copies and renames." (unless stgit-mode-map (let ((diff-map (make-sparse-keymap)) (toggle-map (make-sparse-keymap))) - (suppress-keymap diff-map) (mapc (lambda (arg) (define-key diff-map (car arg) (cdr arg))) '(("b" . stgit-diff-base) ("c" . stgit-diff-combined) @@ -944,10 +1036,10 @@ file for (applied) copies and renames." ("o" . stgit-diff-ours) ("r" . stgit-diff-range) ("t" . stgit-diff-theirs))) - (suppress-keymap toggle-map) (mapc (lambda (arg) (define-key toggle-map (car arg) (cdr arg))) '(("n" . stgit-toggle-patch-names) ("t" . stgit-toggle-worktree) + ("h" . stgit-toggle-committed) ("i" . stgit-toggle-ignored) ("u" . stgit-toggle-unknown))) (setq stgit-mode-map (make-keymap)) @@ -999,7 +1091,8 @@ file for (applied) copies and renames." ("\C-c\C-b" . stgit-rebase) ("t" . ,toggle-map) ("d" . ,diff-map) - ("q" . stgit-quit)))) + ("q" . stgit-quit) + ("!" . stgit-execute)))) (let ((at-unmerged-file '(let ((file (stgit-patched-file-at-point))) (and file (eq (stgit-file->status file) @@ -1112,6 +1205,8 @@ file for (applied) copies and renames." :selected stgit-show-ignored :active stgit-show-worktree] ["Show patch names" stgit-toggle-patch-names :style toggle :selected stgit-show-patch-names] + ["Show recent commits" stgit-toggle-committed :style toggle + :selected stgit-show-committed] "-" ["Switch branches" stgit-branch t :help "Switch to or create another branch"] @@ -1141,6 +1236,8 @@ Basic commands: \\[stgit-git-status] Run `git-status' (if available) +\\[stgit-execute] Run an stg shell command + Movement commands: \\[stgit-previous-line] Move to previous line \\[stgit-next-line] Move to next line @@ -1193,6 +1290,7 @@ Display commands: \\[stgit-toggle-worktree] Toggle showing index and work tree \\[stgit-toggle-unknown] Toggle showing unknown files \\[stgit-toggle-ignored] Toggle showing ignored files +\\[stgit-toggle-committed] Toggle showing recent commits Commands for diffs: \\[stgit-diff] Show diff of patch or file @@ -1216,8 +1314,12 @@ Commands for branches: Customization variables: `stgit-abbreviate-copies-and-renames' +`stgit-default-show-ignored' `stgit-default-show-patch-names' +`stgit-default-show-unknown' `stgit-default-show-worktree' +`stgit-default-show-committed' +`stgit-default-committed-count' `stgit-find-copies-harder' `stgit-show-worktree-mode' @@ -1228,28 +1330,103 @@ See also \\[customize-group] for the \"stgit\" group." major-mode 'stgit-mode goal-column 2) (use-local-map stgit-mode-map) - (set (make-local-variable 'list-buffers-directory) default-directory) - (set (make-local-variable 'stgit-marked-patches) nil) - (set (make-local-variable 'stgit-expanded-patches) (list :work :index)) - (set (make-local-variable 'stgit-show-patch-names) - stgit-default-show-patch-names) - (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) + (mapc (lambda (x) (set (make-local-variable (car x)) (cdr x))) + `((list-buffers-directory . ,default-directory) + (parse-sexp-lookup-properties . t) + (stgit-expanded-patches . (:work :index)) + (stgit-index-node . nil) + (stgit-worktree-node . nil) + (stgit-marked-patches . nil) + (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-unknown . ,stgit-default-show-unknown) + (stgit-show-worktree . ,stgit-default-show-worktree))) (set-variable 'truncate-lines 't) - (add-hook 'after-save-hook 'stgit-update-saved-file) + (add-hook 'after-save-hook 'stgit-update-stgit-for-buffer) + (unless stgit-did-advise + (stgit-advise) + (setq stgit-did-advise t)) (run-hooks 'stgit-mode-hook)) -(defun stgit-update-saved-file () - (let* ((file (expand-file-name buffer-file-name)) - (dir (file-name-directory file)) - (gitdir (condition-case nil (git-get-top-dir dir) - (error nil))) +(defun stgit-advise-funlist (funlist) + "Add advice to the functions in FUNLIST so we can refresh the +stgit buffers as the git status of files change." + (mapc (lambda (sym) + (when (fboundp sym) + (eval `(defadvice ,sym (after stgit-update-stgit-for-buffer) + (stgit-update-stgit-for-buffer t))) + (ad-activate sym))) + funlist)) + +(defun stgit-advise () + "Add advice to appropriate (non-stgit) git functions so we can +refresh the stgit buffers as the git status of files change." + (mapc (lambda (arg) + (let ((feature (car arg)) + (funlist (cdr arg))) + (if (featurep feature) + (stgit-advise-funlist funlist) + (add-to-list 'after-load-alist + `(,feature (stgit-advise-funlist + (quote ,funlist))))))) + ;; lists of ( ...) to be advised + '((vc-git vc-git-rename-file vc-git-revert vc-git-register) + (git git-add-file git-checkout git-revert-file git-remove-file) + (dired dired-delete-file)))) + +(defvar stgit-pending-refresh-buffers nil + "Alist of (cons `buffer' `refresh-index') of buffers that need +to be refreshed. `refresh-index' is non-nil if both work tree +and index need to be refreshed.") + +(defun stgit-run-pending-refreshs () + "Run all pending stgit buffer updates as posted by `stgit-post-refresh'." + (let ((buffers stgit-pending-refresh-buffers) + (stgit-inhibit-messages t)) + (setq stgit-pending-refresh-buffers nil) + (while buffers + (let* ((elem (car buffers)) + (buffer (car elem)) + (refresh-index (cdr elem))) + (when (buffer-name buffer) + (with-current-buffer buffer + (stgit-refresh-worktree) + (when refresh-index (stgit-refresh-index))))) + (setq buffers (cdr buffers))))) + +(defun stgit-post-refresh (buffer refresh-index) + "Update worktree status in BUFFER when Emacs becomes idle. If +REFRESH-INDEX is non-nil, also update the index." + (unless stgit-pending-refresh-buffers + (run-with-idle-timer 0.1 nil 'stgit-run-pending-refreshs)) + (let ((elem (assq buffer stgit-pending-refresh-buffers))) + (if elem + ;; if buffer is already present, set its refresh-index flag if + ;; necessary + (when refresh-index + (setcdr elem t)) + ;; new entry + (setq stgit-pending-refresh-buffers + (cons (cons buffer refresh-index) + stgit-pending-refresh-buffers))))) + +(defun stgit-update-stgit-for-buffer (&optional refresh-index) + "When Emacs becomes idle, refresh worktree status in any +`stgit-mode' buffer that shows the status of the current buffer. + +If REFRESH-INDEX is non-nil, also update the index." + (let* ((dir (cond ((derived-mode-p 'stgit-status-mode 'dired-mode) + default-directory) + (buffer-file-name + (file-name-directory + (expand-file-name buffer-file-name))))) + (gitdir (and dir (condition-case nil (git-get-top-dir dir) + (error nil)))) (buffer (and gitdir (stgit-find-buffer gitdir)))) (when buffer - (with-current-buffer buffer - (stgit-refresh-worktree))))) + (stgit-post-refresh buffer refresh-index)))) (defun stgit-add-mark (patchsym) "Mark the patch PATCHSYM." @@ -1266,15 +1443,21 @@ See also \\[customize-group] for the \"stgit\" group." (defun stgit-patch-at-point (&optional cause-error) (get-text-property (point) 'patch-data)) -(defun stgit-patch-name-at-point (&optional cause-error only-patches) +(defun stgit-patch-name-at-point (&optional cause-error types) "Return the patch name on the current line as a symbol. If CAUSE-ERROR is not nil, signal an error if none found. -If ONLY-PATCHES is not nil, only allow real patches, and not -index or work tree." + +TYPES controls which types of commits and patches can be returned. +If it is t, only allow stgit patches; if 'allow-committed, also +allow historical commits; if nil, also allow work tree and index." (let ((patch (stgit-patch-at-point))) (and patch - only-patches - (memq (stgit-patch->status patch) '(work index)) + (memq (stgit-patch->status patch) + (case types + ((nil) nil) + ((allow-committed) '(work index)) + ((t) '(work index committed)) + (t (error "Bad value")))) (setq patch nil)) (cond (patch (stgit-patch->name patch)) @@ -1284,13 +1467,16 @@ index or work tree." (defun stgit-patched-file-at-point () (get-text-property (point) 'file-data)) -(defun stgit-patches-marked-or-at-point (&optional cause-error only-patches) +(defun stgit-patches-marked-or-at-point (&optional cause-error types) "Return the symbols of the marked patches, or the patch on the current line. If CAUSE-ERRROR is not nil, signal an error if none found. -If ONLY-PATCHES is not nil, do not include index or work tree." + +TYPES controls which types of commits and patches can be returned. +If it is t, only allow stgit patches; if 'allow-committed, also +allow historical commits; if nil, also allow work tree and index." (if stgit-marked-patches stgit-marked-patches - (let ((patch (stgit-patch-name-at-point nil only-patches))) + (let ((patch (stgit-patch-name-at-point nil types))) (cond (patch (list patch)) (cause-error (error "No patches marked or at this line")) (t nil))))) @@ -1309,7 +1495,9 @@ PATCHSYM." (when (and node file) (let* ((file-ewoc (stgit-patch->files-ewoc (ewoc-data node))) (file-node (ewoc-nth file-ewoc 0))) - (while (and file-node (not (equal (stgit-file->file (ewoc-data file-node)) file))) + (while (and file-node + (not (equal (stgit-file->file (ewoc-data file-node)) + file))) (setq file-node (ewoc-next file-ewoc file-node))) (when file-node (ewoc-goto-node file-ewoc file-node) @@ -1340,12 +1528,11 @@ PATCHSYM." (interactive) (stgit-assert-mode) (let* ((node (ewoc-locate stgit-ewoc)) - (patch (ewoc-data node)) - (name (stgit-patch->name patch))) - (when (eq name :work) - (error "Cannot mark the work tree")) - (when (eq name :index) - (error "Cannot mark the index")) + (patch (ewoc-data node))) + (case (stgit-patch->status patch) + (work (error "Cannot mark the work tree")) + (index (error "Cannot mark the index")) + (committed (error "Cannot mark a committed patch"))) (stgit-add-mark (stgit-patch->name patch)) (let ((column (current-column))) (ewoc-invalidate stgit-ewoc node) @@ -1390,7 +1577,7 @@ PATCHSYM." (stgit-assert-mode) (let ((old-patchsym (stgit-patch-name-at-point t t))) (stgit-capture-output nil - (stgit-run "rename" old-patchsym name)) + (stgit-run "rename" "--" old-patchsym name)) (let ((name-sym (intern name))) (when (memq old-patchsym stgit-expanded-patches) (setq stgit-expanded-patches @@ -1448,9 +1635,16 @@ If ALL is not nil, also return non-stgit branches." ((not (string-match stgit-allowed-branch-name-re branch)) (error "Invalid branch name")) ((yes-or-no-p (format "Create branch \"%s\"? " branch)) - (stgit-capture-output nil (stgit-run "branch" "--create" "--" - branch)) - t)) + (let ((branch-point (completing-read + "Branch from (default current branch): " + (stgit-available-branches)))) + (stgit-capture-output nil + (apply 'stgit-run + `("branch" "--create" "--" + ,branch + ,@(unless (zerop (length branch-point)) + (list branch-point))))) + t))) (stgit-reload))) (defun stgit-available-refs (&optional omit-stgit) @@ -1491,7 +1685,7 @@ what git-config branch..stgit.parentbranch is set to." nil nil (stgit-parent-branch)))) (stgit-assert-mode) - (stgit-capture-output nil (stgit-run "rebase" new-base)) + (stgit-capture-output nil (stgit-run "rebase" "--" new-base)) (stgit-reload)) (defun stgit-commit (count) @@ -1660,8 +1854,10 @@ If ONLY-PATCHES is not nil, exclude index and work tree." '(applied top) '(applied top index work))) result) - (ewoc-map (lambda (patch) (when (memq (stgit-patch->status patch) states) - (setq result (cons patch result)))) + (ewoc-map (lambda (patch) + (when (memq (stgit-patch->status patch) states) + (setq result (cons patch result))) + nil) stgit-ewoc) result)) @@ -1686,11 +1882,14 @@ If ONLY-PATCHES is not nil, exclude index and work tree." (stgit-reload)) (defun stgit-goto-target () - "Return the goto target a point; either a patchsym, :top, + "Return the goto target at point: a patchsym, :top, or :bottom." - (let ((patchsym (stgit-patch-name-at-point))) - (cond ((memq patchsym '(:work :index)) nil) - (patchsym) + (let ((patch (stgit-patch-at-point))) + (cond (patch + (case (stgit-patch->status patch) + ((work index) nil) + ((committed) :bottom) + (t (stgit-patch->name patch)))) ((not (next-single-property-change (point) 'patch-data)) :top) ((not (previous-single-property-change (point) 'patch-data)) @@ -1710,7 +1909,7 @@ patches if used on a line after or before all patches." (:top (stgit-push-or-pop-patches t t)) (:bottom (stgit-push-or-pop-patches nil t)) (t (stgit-capture-output nil - (stgit-run "goto" patchsym)) + (stgit-run "goto" "--" patchsym)) (stgit-reload))))) (defun stgit-id (patchsym) @@ -1719,7 +1918,7 @@ If PATCHSYM is a keyword, returns PATCHSYM unmodified." (if (keywordp patchsym) patchsym (let ((result (with-output-to-string - (stgit-run-silent "id" patchsym)))) + (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)))) @@ -1772,6 +1971,7 @@ which stage to diff against in the case of unmerged files." (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 @@ -1820,9 +2020,10 @@ greater than four (e.g., \\[universal-argument] \ (stgit-assert-mode) (unless (= (length stgit-marked-patches) 1) (error "Need exactly one patch marked")) - (let* ((patches (stgit-sort-patches (cons (stgit-patch-name-at-point t t) - stgit-marked-patches) - t)) + (let* ((patches (stgit-sort-patches + (cons (stgit-patch-name-at-point t 'allow-committed) + stgit-marked-patches) + t)) (first-patch (car patches)) (second-patch (if (cdr patches) (cadr patches) first-patch)) (whitespace-arg (stgit-whitespace-diff-arg ignore-whitespace)) @@ -1938,14 +2139,14 @@ file ended up. You can then jump to the file with \ (setq default-directory dir) (let ((standard-output edit-buf)) (save-excursion - (stgit-run-silent "edit" "--save-template=-" patchsym))))) + (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-patchsym)) + (stgit-run "edit" "-f" file "--" stgit-edit-patchsym)) (with-current-buffer log-edit-parent-buffer (stgit-reload)))) @@ -2030,9 +2231,9 @@ the work tree and index." (if spill-p " (spilling contents to index)" ""))) - (let ((args (if spill-p - (cons "--spill" patchsyms) - patchsyms))) + (let ((args (append (when spill-p '("--spill")) + '("--") + patchsyms))) (stgit-capture-output nil (apply 'stgit-run "delete" args)) (stgit-reload))))) @@ -2100,7 +2301,7 @@ Interactively, move the marked patches to where the point is." (let ((sorted-patchsyms (stgit-sort-patches patchsyms))) (stgit-capture-output nil (if (eq target-patch :top) - (apply 'stgit-run "float" sorted-patchsyms) + (apply 'stgit-run "float" "--" sorted-patchsyms) (apply 'stgit-run "sink" (append (unless (eq target-patch :bottom) @@ -2130,7 +2331,7 @@ deepest patch had before the squash." (let ((result (let ((standard-output edit-buf)) (save-excursion (apply 'stgit-run-silent "squash" - "--save-template=-" sorted-patchsyms))))) + "--save-template=-" "--" sorted-patchsyms))))) ;; stg squash may have reordered the patches or caused conflicts (with-current-buffer stgit-buffer @@ -2148,7 +2349,7 @@ deepest patch had before the squash." (let ((file (make-temp-file "stgit-edit-"))) (write-region (point-min) (point-max) file) (stgit-capture-output nil - (apply 'stgit-run "squash" "-f" file stgit-patchsyms)) + (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 @@ -2164,6 +2365,75 @@ deepest patch had before the squash." (interactive) (describe-function 'stgit-mode)) +(defun stgit-execute-process-sentinel (process sentinel) + (let (old-sentinel stgit-buf) + (with-current-buffer (process-buffer process) + (setq old-sentinel old-process-sentinel + stgit-buf stgit-buffer)) + (and (memq (process-status process) '(exit signal)) + (buffer-live-p stgit-buf) + (with-current-buffer stgit-buf + (stgit-reload))) + (funcall old-sentinel process sentinel))) + +(defun stgit-execute-process-filter (process output) + (with-current-buffer (process-buffer process) + (let* ((old-point (point)) + (pmark (process-mark process)) + (insert-at (marker-position pmark)) + (at-pmark (= insert-at old-point))) + (goto-char insert-at) + (insert-before-markers output) + (comint-carriage-motion insert-at (point)) + (set-marker pmark (point)) + (unless at-pmark + (goto-char old-point))))) + +(defun stgit-execute () + "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. + +If the command ends in an ampersand, run it asynchronously. + +When the command has finished, reload the stgit buffer." + (interactive) + (stgit-assert-mode) + (let* ((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 " + (and hyphens "-- ") + (mapconcat 'identity patch-names " ")) + "stg ")) + (cmd (read-from-minibuffer "Shell command: " (cons defaultcmd 5) + nil nil 'shell-command-history)) + (async (string-match "&[ \t]*\\'" cmd)) + (buffer (get-buffer-create + (if async + "*Async Shell Command*" + "*Shell Command Output*")))) + ;; cannot use minibuffer as stgit-reload would overwrite it; if we + ;; show the buffer, shell-command will not use the minibuffer + (display-buffer buffer) + (shell-command cmd) + (if async + (let ((old-buffer (current-buffer))) + (with-current-buffer buffer + (let ((process (get-buffer-process buffer))) + (set (make-local-variable 'old-process-sentinel) + (process-sentinel process)) + (set (make-local-variable 'stgit-buffer) + old-buffer) + (set-process-filter process 'stgit-execute-process-filter) + (set-process-sentinel process 'stgit-execute-process-sentinel)))) + (with-current-buffer buffer + (comint-carriage-motion (point-min) (point-max))) + (shrink-window-if-larger-than-buffer (get-buffer-window buffer)) + (stgit-reload)))) + (defun stgit-undo-or-redo (redo hard) "Run stg undo or, if REDO is non-nil, stg redo. @@ -2230,6 +2500,12 @@ See also `stgit-show-worktree-mode'.") (defvar stgit-show-patch-names t "If nil, inhibit showing patch names.") +(defvar stgit-show-committed nil + "If nil, inhibit showing recent commits.") + +(defvar stgit-committed-count nil + "The number of recent commits to show.") + (defun stgit-toggle-worktree (&optional arg) "Toggle the visibility of the work tree. With ARG, show the work tree if ARG is positive. @@ -2250,6 +2526,8 @@ work tree will show up." "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) @@ -2263,6 +2541,8 @@ Use \\[stgit-toggle-worktree] to show the work tree." "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) @@ -2285,4 +2565,19 @@ The initial setting is controlled by `stgit-default-show-patch-names'." (not stgit-show-patch-names))) (stgit-reload)) +(defun stgit-toggle-committed (&optional arg) + "Toggle the visibility of historical git commits. +With ARG, set the number of commits to show to ARG, and disable +them if ARG is zero. + +The initial setting is controlled by `stgit-default-show-committed'." + (interactive "P") + (stgit-assert-mode) + (if (null arg) + (setq stgit-show-committed (not stgit-show-committed)) + (let ((n (prefix-numeric-value arg))) + (setq stgit-show-committed (> n 0)) + (setq stgit-committed-count n))) + (stgit-reload)) + (provide 'stgit)