diff options
Diffstat (limited to 'lisp/vc-git.el')
-rw-r--r-- | lisp/vc-git.el | 291 |
1 files changed, 202 insertions, 89 deletions
diff --git a/lisp/vc-git.el b/lisp/vc-git.el index 0d35afa739e..fe7b95cb43a 100644 --- a/lisp/vc-git.el +++ b/lisp/vc-git.el @@ -43,64 +43,64 @@ ;; beginning of vc.el. The current status is: ;; ("??" means: "figure out what to do about it") ;; -;; FUNCTION NAME STATUS +;; FUNCTION NAME STATUS ;; BACKEND PROPERTIES -;; * revision-granularity OK +;; * revision-granularity OK ;; STATE-QUERYING FUNCTIONS -;; * registered (file) OK -;; * state (file) OK -;; - state-heuristic (file) NOT NEEDED -;; * working-revision (file) OK -;; - latest-on-branch-p (file) NOT NEEDED -;; * checkout-model (files) OK -;; - workfile-unchanged-p (file) OK -;; - mode-line-string (file) OK +;; * registered (file) OK +;; * state (file) OK +;; - state-heuristic (file) NOT NEEDED +;; * working-revision (file) OK +;; - latest-on-branch-p (file) NOT NEEDED +;; * checkout-model (files) OK +;; - workfile-unchanged-p (file) OK +;; - mode-line-string (file) OK ;; STATE-CHANGING FUNCTIONS -;; * create-repo () OK -;; * register (files &optional rev comment) OK -;; - init-revision (file) NOT NEEDED -;; - responsible-p (file) OK -;; - could-register (file) NOT NEEDED, DEFAULT IS GOOD -;; - receive-file (file rev) NOT NEEDED -;; - unregister (file) OK -;; * checkin (files rev comment) OK -;; * find-revision (file rev buffer) OK -;; * checkout (file &optional editable rev) OK -;; * revert (file &optional contents-done) OK -;; - rollback (files) COULD BE SUPPORTED +;; * create-repo () OK +;; * register (files &optional rev comment) OK +;; - init-revision (file) NOT NEEDED +;; - responsible-p (file) OK +;; - could-register (file) NOT NEEDED, DEFAULT IS GOOD +;; - receive-file (file rev) NOT NEEDED +;; - unregister (file) OK +;; * checkin (files rev comment) OK +;; * find-revision (file rev buffer) OK +;; * checkout (file &optional editable rev) OK +;; * revert (file &optional contents-done) OK +;; - rollback (files) COULD BE SUPPORTED ;; - merge (file rev1 rev2) It would be possible to merge ;; changes into a single file, but when ;; committing they wouldn't ;; be identified as a merge ;; by git, so it's probably ;; not a good idea. -;; - merge-news (file) see `merge' -;; - steal-lock (file &optional revision) NOT NEEDED +;; - merge-news (file) see `merge' +;; - steal-lock (file &optional revision) NOT NEEDED ;; HISTORY FUNCTIONS -;; * print-log (files &optional buffer) OK -;; - log-view-mode () OK -;; - show-log-entry (revision) OK -;; - comment-history (file) ?? -;; - update-changelog (files) COULD BE SUPPORTED -;; * diff (file &optional rev1 rev2 buffer) OK -;; - revision-completion-table (files) OK -;; - annotate-command (file buf &optional rev) OK -;; - annotate-time () OK -;; - annotate-current-time () NOT NEEDED -;; - annotate-extract-revision-at-line () OK +;; * print-log (files &optional buffer shortlog) OK +;; - log-view-mode () OK +;; - show-log-entry (revision) OK +;; - comment-history (file) ?? +;; - update-changelog (files) COULD BE SUPPORTED +;; * diff (file &optional rev1 rev2 buffer) OK +;; - revision-completion-table (files) OK +;; - annotate-command (file buf &optional rev) OK +;; - annotate-time () OK +;; - annotate-current-time () NOT NEEDED +;; - annotate-extract-revision-at-line () OK ;; TAG SYSTEM -;; - create-tag (dir name branchp) OK -;; - retrieve-tag (dir name update) OK, needs to update buffers +;; - create-tag (dir name branchp) OK +;; - retrieve-tag (dir name update) OK ;; MISCELLANEOUS -;; - make-version-backups-p (file) NOT NEEDED -;; - repository-hostname (dirname) NOT NEEDED -;; - previous-revision (file rev) OK -;; - next-revision (file rev) OK -;; - check-headers () COULD BE SUPPORTED -;; - clear-headers () NOT NEEDED -;; - delete-file (file) OK -;; - rename-file (old new) OK -;; - find-file-hook () NOT NEEDED +;; - make-version-backups-p (file) NOT NEEDED +;; - repository-hostname (dirname) NOT NEEDED +;; - previous-revision (file rev) OK +;; - next-revision (file rev) OK +;; - check-headers () COULD BE SUPPORTED +;; - clear-headers () NOT NEEDED +;; - delete-file (file) OK +;; - rename-file (old new) OK +;; - find-file-hook () NOT NEEDED (eval-when-compile (require 'cl) @@ -146,7 +146,8 @@ If nil, use the value of `vc-diff-switches'. If t, use no switches." "Check whether FILE is registered with git." (when (vc-git-root file) (with-temp-buffer - (let* ((dir (file-name-directory file)) + (let* (process-file-side-effects + (dir (file-name-directory file)) (name (file-relative-name file dir)) (str (ignore-errors (when dir (cd dir)) @@ -183,9 +184,10 @@ If nil, use the value of `vc-diff-switches'. If t, use no switches." (defun vc-git-working-revision (file) "Git-specific version of `vc-working-revision'." - (let ((str (with-output-to-string - (with-current-buffer standard-output - (vc-git--out-ok "symbolic-ref" "HEAD"))))) + (let* (process-file-side-effects + (str (with-output-to-string + (with-current-buffer standard-output + (vc-git--out-ok "symbolic-ref" "HEAD"))))) (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str) (match-string 2 str) str))) @@ -260,7 +262,7 @@ If nil, use the value of `vc-diff-switches'. If t, use no switches." (defun vc-git-rename-as-string (state extra) "Return a string describing the copy or rename associated with INFO, or an empty string if none." - (let ((rename-state (when extra + (let ((rename-state (when extra (vc-git-extra-fileinfo->rename-state extra)))) (if rename-state (propertize @@ -401,21 +403,51 @@ If nil, use the value of `vc-diff-switches'. If t, use no switches." (let ((str (with-output-to-string (with-current-buffer standard-output (vc-git--out-ok "symbolic-ref" "HEAD")))) - (stash (vc-git-stash-list))) + (stash (vc-git-stash-list)) + branch remote remote-url) + (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str) + (progn + (setq branch (match-string 2 str)) + (setq remote + (with-output-to-string + (with-current-buffer standard-output + (vc-git--out-ok "config" (concat "branch." branch ".remote"))))) + (when (string-match "\\([^\n]+\\)" remote) + (setq remote (match-string 1 remote))) + (when remote + (setq remote-url + (with-output-to-string + (with-current-buffer standard-output + (vc-git--out-ok "config" (concat "remote." remote ".url")))))) + (when (string-match "\\([^\n]+\\)" remote-url) + (setq remote-url (match-string 1 remote-url)))) + "not (detached HEAD)") ;; FIXME: maybe use a different face when nothing is stashed. - (when (string= stash "") (setq stash "Nothing stashed")) (concat (propertize "Branch : " 'face 'font-lock-type-face) - (propertize - (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str) - (match-string 2 str) - "not (detached HEAD)") - 'face 'font-lock-variable-name-face) + (propertize branch + 'face 'font-lock-variable-name-face) + (when remote + (concat + "\n" + (propertize "Remote : " 'face 'font-lock-type-face) + (propertize remote-url + 'face 'font-lock-variable-name-face))) "\n" - (propertize "Stash : " 'face 'font-lock-type-face) - (propertize - stash - 'face 'font-lock-variable-name-face)))) + (if stash + (concat + (propertize "Stash :\n" 'face 'font-lock-type-face) + (mapconcat + (lambda (x) + (propertize x + 'face 'font-lock-variable-name-face + 'mouse-face 'highlight + 'keymap vc-git-stash-map)) + stash "\n")) + (concat + (propertize "Stash : " 'face 'font-lock-type-face) + (propertize "Nothing stashed" + 'face 'font-lock-variable-name-face)))))) ;;; STATE-CHANGING FUNCTIONS @@ -424,8 +456,16 @@ If nil, use the value of `vc-diff-switches'. If t, use no switches." (vc-git-command nil 0 nil "init")) (defun vc-git-register (files &optional rev comment) - "Register FILE into the git version-control system." - (vc-git-command nil 0 files "update-index" "--add" "--")) + "Register FILES into the git version-control system." + (let (flist dlist) + (dolist (crt files) + (if (file-directory-p crt) + (push crt dlist) + (push crt flist))) + (when flist + (vc-git-command nil 0 flist "update-index" "--add" "--")) + (when dlist + (vc-git-command nil 0 dlist "add")))) (defalias 'vc-git-responsible-p 'vc-git-root) @@ -439,12 +479,13 @@ If nil, use the value of `vc-diff-switches'. If t, use no switches." (if vc-git-add-signoff "-s") "-m" comment "--only" "--"))) (defun vc-git-find-revision (file rev buffer) - (let ((coding-system-for-read 'binary) - (coding-system-for-write 'binary) - (fullname (substring - (vc-git--run-command-string - file "ls-files" "-z" "--full-name" "--") - 0 -1))) + (let* (process-file-side-effects + (coding-system-for-read 'binary) + (coding-system-for-write 'binary) + (fullname (substring + (vc-git--run-command-string + file "ls-files" "-z" "--full-name" "--") + 0 -1))) (vc-git-command buffer 0 (concat (if rev rev "HEAD") ":" fullname) "cat-file" "blob"))) @@ -460,7 +501,7 @@ If nil, use the value of `vc-diff-switches'. If t, use no switches." ;;; HISTORY FUNCTIONS -(defun vc-git-print-log (files &optional buffer) +(defun vc-git-print-log (files &optional buffer shortlog) "Get change log associated with FILES." (let ((coding-system-for-read git-commits-coding-system) ;; Support both the old print-log interface that passes a @@ -474,22 +515,38 @@ If nil, use the value of `vc-diff-switches'. If t, use no switches." (let ((inhibit-read-only t)) (with-current-buffer buffer + (if shortlog (vc-git-command buffer 'async files - "rev-list" "--pretty" "HEAD" "--"))))) + "log" ;; "--graph" + "--date=short" "--pretty=format:%h %ad %s" "--abbrev-commit" + "--") + (vc-git-command buffer 'async files + "rev-list" ;; "--graph" + "--pretty" "HEAD" "--")))))) (defvar log-view-message-re) (defvar log-view-file-re) (defvar log-view-font-lock-keywords) (defvar log-view-per-file-logs) +;; Dynamically bound. +(defvar vc-short-log) + (define-derived-mode vc-git-log-view-mode log-view-mode "Git-Log-View" (require 'add-log) ;; we need the faces add-log ;; Don't have file markers, so use impossible regexp. (set (make-local-variable 'log-view-file-re) "\\`a\\`") (set (make-local-variable 'log-view-per-file-logs) nil) (set (make-local-variable 'log-view-message-re) - "^commit *\\([0-9a-z]+\\)") + (if vc-short-log + "^\\(?:[*/\\| ]+ \\)?\\([0-9a-z]+\\) \\([-a-z0-9]+\\) \\(.*\\)" + "^commit *\\([0-9a-z]+\\)")) (set (make-local-variable 'log-view-font-lock-keywords) + (if vc-short-log + (append + `((,log-view-message-re + (1 'change-log-acknowledgement) + (2 'change-log-date)))) (append `((,log-view-message-re (1 'change-log-acknowledgement))) ;; Handle the case: @@ -510,7 +567,8 @@ If nil, use the value of `vc-diff-switches'. If t, use no switches." (1 'change-log-acknowledgement) (2 'change-log-acknowledgement)) ("^Date: \\(.+\\)" (1 'change-log-date)) - ("^summary:[ \t]+\\(.+\\)" (1 'log-view-message)))))) + ("^summary:[ \t]+\\(.+\\)" (1 'log-view-message))))))) + (defun vc-git-show-log-entry (revision) "Move to the log entry for REVISION. @@ -528,15 +586,17 @@ or BRANCH^ (where \"^\" can be repeated)." (defun vc-git-diff (files &optional rev1 rev2 buffer) "Get a difference report using Git between two revisions of FILES." - (apply #'vc-git-command (or buffer "*vc-diff*") 1 files - (if (and rev1 rev2) "diff-tree" "diff-index") - "--exit-code" - (append (vc-switches 'git 'diff) - (list "-p" (or rev1 "HEAD") rev2 "--")))) + (let (process-file-side-effects) + (apply #'vc-git-command (or buffer "*vc-diff*") 1 files + (if (and rev1 rev2) "diff-tree" "diff-index") + "--exit-code" + (append (vc-switches 'git 'diff) + (list "-p" (or rev1 "HEAD") rev2 "--"))))) (defun vc-git-revision-table (files) ;; What about `files'?!? --Stef - (let ((table (list "HEAD"))) + (let (process-file-side-effects + (table (list "HEAD"))) (with-temp-buffer (vc-git-command t nil nil "for-each-ref" "--format=%(refname)") (goto-char (point-min)) @@ -553,7 +613,7 @@ or BRANCH^ (where \"^\" can be repeated)." (defun vc-git-annotate-command (file buf &optional rev) (let ((name (file-relative-name file))) - (vc-git-command buf 'async name "blame" "--date=iso" rev))) + (vc-git-command buf 'async name "blame" "--date=iso" rev "--"))) (declare-function vc-annotate-convert-time "vc-annotate" (time)) @@ -649,6 +709,12 @@ or BRANCH^ (where \"^\" can be repeated)." (define-key map [git-grep] '(menu-item "Git grep..." vc-git-grep :help "Run the `git grep' command")) + (define-key map [git-st] + '(menu-item "Stash..." vc-git-stash + :help "Stash away changes")) + (define-key map [git-ss] + '(menu-item "Show Stash..." vc-git-stash-show + :help "Show stash contents")) (define-key map [git-sig] '(menu-item "Add Signed-off-by on commit" vc-git-toggle-signoff :help "Add Add Signed-off-by when commiting (i.e. add the -s flag)" @@ -659,6 +725,9 @@ or BRANCH^ (where \"^\" can be repeated)." (defun vc-git-extra-status-menu () vc-git-extra-menu-map) +(defun vc-git-root (file) + (vc-find-root file ".git")) + (defun vc-git-toggle-signoff () (interactive) (setq vc-git-add-signoff (not vc-git-add-signoff))) @@ -717,18 +786,61 @@ This command shares argument histories with \\[rgrep] and \\[grep]." (if (eq next-error-last-buffer (current-buffer)) (setq default-directory dir)))))) +(defun vc-git-stash (name) + "Create a stash." + (interactive "sStash name: ") + (let ((root (vc-git-root default-directory))) + (when root + (vc-git--call nil "stash" "save" name) + (vc-resynch-buffer root t t)))) + +(defun vc-git-stash-show (name) + "Show the contents of stash NAME." + (interactive "sStash name: ") + (vc-setup-buffer "*vc-git-stash*") + (vc-git-command "*vc-git-stash*" 'async nil "stash" "show" "-p" name) + (set-buffer "*vc-git-stash*") + (diff-mode) + (setq buffer-read-only t) + (pop-to-buffer (current-buffer))) + (defun vc-git-stash-list () - (replace-regexp-in-string - "\n" "\n " - (replace-regexp-in-string - "^stash@" "" (vc-git--run-command-string nil "stash" "list")))) + (delete + "" + (split-string + (replace-regexp-in-string + "^stash@" " " (vc-git--run-command-string nil "stash" "list")) + "\n"))) + +(defun vc-git-stash-get-at-point (point) + (save-excursion + (goto-char point) + (beginning-of-line) + (if (looking-at "^ +\\({[0-9]+}\\):") + (match-string 1) + (error "Cannot find stash at point")))) + +(defun vc-git-stash-delete-at-point () + (interactive) + (let ((stash (vc-git-stash-get-at-point (point)))) + (when (y-or-n-p (format "Remove stash %s ?" stash)) + (vc-git--run-command-string nil "stash" "drop" (format "stash@%s" stash)) + (vc-dir-refresh)))) + +(defun vc-git-stash-show-at-point () + (interactive) + (vc-git-stash-show (format "stash@%s" (vc-git-stash-get-at-point (point))))) + +(defvar vc-git-stash-map + (let ((map (make-sparse-keymap))) + (define-key map "\C-k" 'vc-git-stash-delete-at-point) + (define-key map "=" 'vc-git-stash-show-at-point) + (define-key map "\C-m" 'vc-git-stash-show-at-point) + map)) ;;; Internal commands -(defun vc-git-root (file) - (vc-find-root file ".git")) - (defun vc-git-command (buffer okstatus file-or-list &rest flags) "A wrapper around `vc-do-command' for use in vc-git.el. The difference to vc-do-command is that this function always invokes `git'." @@ -736,7 +848,8 @@ The difference to vc-do-command is that this function always invokes `git'." (defun vc-git--empty-db-p () "Check if the git db is empty (no commit done yet)." - (not (eq 0 (vc-git--call nil "rev-parse" "--verify" "HEAD")))) + (let (process-file-side-effects) + (not (eq 0 (vc-git--call nil "rev-parse" "--verify" "HEAD"))))) (defun vc-git--call (buffer command &rest args) ;; We don't need to care the arguments. If there is a file name, it |