X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=contrib%2Femacs%2Fgit.el;h=24629eb3e211505728c77db820618021203241ae;hb=28389d45fb2161d4dcdc1bbfabbcc2fb135914c6;hp=ebd00ef9c471f284d26fecd44e3fd7668903b50a;hpb=521a3f676794987bfd03703fe58fbec46ed69d49;p=git.git diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index ebd00ef9c..24629eb3e 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -49,24 +49,28 @@ (eval-when-compile (require 'cl)) (require 'ewoc) +(require 'log-edit) ;;;; Customizations ;;;; ------------------------------------------------------------ (defgroup git nil - "Git user interface") + "A user interface for the git versioning system." + :group 'tools) (defcustom git-committer-name nil "User name to use for commits. -The default is to fall back to the repository config, then to `add-log-full-name' and then to `user-full-name'." +The default is to fall back to the repository config, +then to `add-log-full-name' and then to `user-full-name'." :group 'git :type '(choice (const :tag "Default" nil) (string :tag "Name"))) (defcustom git-committer-email nil "Email address to use for commits. -The default is to fall back to the git repository config, then to `add-log-mailing-address' and then to `user-mail-address'." +The default is to fall back to the git repository config, +then to `add-log-mailing-address' and then to `user-mail-address'." :group 'git :type '(choice (const :tag "Default" nil) (string :tag "Email"))) @@ -81,11 +85,18 @@ The default is to fall back to the git repository config, then to `add-log-maili :group 'git :type 'boolean) +(defcustom git-reuse-status-buffer t + "Whether `git-status' should try to reuse an existing buffer +if there is already one that displays the same directory." + :group 'git + :type 'boolean) + (defcustom git-per-dir-ignore-file ".gitignore" "Name of the per-directory ignore file." :group 'git :type 'string) + (defface git-status-face '((((class color) (background light)) (:foreground "purple"))) "Git mode face used to highlight added and modified files." @@ -137,6 +148,13 @@ The default is to fall back to the git repository config, then to `add-log-maili (defconst git-log-msg-separator "--- log message follows this line ---") +(defvar git-log-edit-font-lock-keywords + `(("^\\(Author:\\|Date:\\|Parent:\\|Signed-off-by:\\)\\(.*\\)$" + (1 font-lock-keyword-face) + (2 font-lock-function-name-face)) + (,(concat "^\\(" (regexp-quote git-log-msg-separator) "\\)$") + (1 font-lock-comment-face)))) + (defun git-get-env-strings (env) "Build a list of NAME=VALUE strings from a list of environment strings." (mapcar (lambda (entry) (concat (car entry) "=" (cdr entry))) env)) @@ -149,7 +167,8 @@ The default is to fall back to the git repository config, then to `add-log-maili (apply #'call-process "git" nil buffer nil args))) (defun git-call-process-env-string (env &rest args) - "Wrapper for call-process that sets environment strings, and returns the process output as a string." + "Wrapper for call-process that sets environment strings, +and returns the process output as a string." (with-temp-buffer (and (eq 0 (apply #' git-call-process-env t env args)) (buffer-string)))) @@ -203,7 +222,7 @@ The default is to fall back to the git repository config, then to `add-log-maili "Return the name to use as GIT_COMMITTER_NAME." ; copied from log-edit (or git-committer-name - (git-repo-config "user.name") + (git-config "user.name") (and (boundp 'add-log-full-name) add-log-full-name) (and (fboundp 'user-full-name) (user-full-name)) (and (boundp 'user-full-name) user-full-name))) @@ -212,7 +231,7 @@ The default is to fall back to the git repository config, then to `add-log-maili "Return the email address to use as GIT_COMMITTER_EMAIL." ; copied from log-edit (or git-committer-email - (git-repo-config "user.email") + (git-config "user.email") (and (boundp 'add-log-mailing-address) add-log-mailing-address) (and (fboundp 'user-mail-address) (user-mail-address)) (and (boundp 'user-mail-address) user-mail-address))) @@ -254,13 +273,22 @@ The default is to fall back to the git repository config, then to `add-log-maili (set-buffer (find-file-noselect ignore-name)) (goto-char (point-max)) (unless (zerop (current-column)) (insert "\n")) - (insert name "\n") + (insert "/" name "\n") (sort-lines nil (point-min) (point-max)) (save-buffer)) (when created (git-run-command nil nil "update-index" "--info-only" "--add" "--" (file-relative-name ignore-name))) (git-add-status-file (if created 'added 'modified) (file-relative-name ignore-name)))) +; propertize definition for XEmacs, stolen from erc-compat +(eval-when-compile + (unless (fboundp 'propertize) + (defun propertize (string &rest props) + (let ((string (copy-sequence string))) + (while props + (put-text-property 0 (length string) (nth 0 props) (nth 1 props) string) + (setq props (cddr props))) + string)))) ;;;; Wrappers for basic git commands ;;;; ------------------------------------------------------------ @@ -270,9 +298,9 @@ The default is to fall back to the git repository config, then to `add-log-maili (git-get-string-sha1 (git-call-process-env-string nil "rev-parse" rev))) -(defun git-repo-config (key) +(defun git-config (key) "Retrieve the value associated to KEY in the git repository config file." - (let ((str (git-call-process-env-string nil "repo-config" key))) + (let ((str (git-call-process-env-string nil "config" key))) (and str (car (split-string str "\n"))))) (defun git-symbolic-ref (ref) @@ -411,8 +439,8 @@ The default is to fall back to the git repository config, then to `add-log-maili (propertize (concat " (" (if (eq state 'copy) "copied from " - (if (eq (git-fileinfo->state info) 'added) "renamed to " - "renamed from ")) + (if (eq (git-fileinfo->state info) 'added) "renamed from " + "renamed to ")) (git-escape-file-name (git-fileinfo->orig-name info)) ")") 'face 'git-status-face) ""))) @@ -429,11 +457,10 @@ The default is to fall back to the git repository config, then to `add-log-maili (defun git-fileinfo-prettyprint (info) "Pretty-printer for the git-fileinfo structure." - (insert (format " %s %s %s %s%s" - (if (git-fileinfo->marked info) (propertize "*" 'face 'git-mark-face) " ") - (git-status-code-as-string (git-fileinfo->state info)) - (git-permissions-as-string (git-fileinfo->old-perm info) (git-fileinfo->new-perm info)) - (git-escape-file-name (git-fileinfo->name info)) + (insert (concat " " (if (git-fileinfo->marked info) (propertize "*" 'face 'git-mark-face) " ") + " " (git-status-code-as-string (git-fileinfo->state info)) + " " (git-permissions-as-string (git-fileinfo->old-perm info) (git-fileinfo->new-perm info)) + " " (git-escape-file-name (git-fileinfo->name info)) (git-rename-as-string info)))) (defun git-parse-status (status) @@ -578,8 +605,11 @@ The default is to fall back to the git repository config, then to `add-log-maili (let ((commit (git-commit-tree buffer tree head))) (git-update-ref "HEAD" commit head) (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil)) + (condition-case nil (delete-file ".git/MERGE_MSG") (error nil)) (with-current-buffer buffer (erase-buffer)) (git-set-files-state files 'uptodate) + (when (file-directory-p ".git/rr-cache") + (git-run-command nil nil "rerere")) (git-refresh-files) (git-refresh-ewoc-hf git-status) (message "Committed %s." commit)) @@ -657,6 +687,32 @@ The default is to fall back to the git repository config, then to `add-log-maili (unless git-status (error "Not in git-status buffer.")) (ewoc-goto-prev git-status n)) +(defun git-next-unmerged-file (&optional n) + "Move the selection down N unmerged files." + (interactive "p") + (unless git-status (error "Not in git-status buffer.")) + (let* ((last (ewoc-locate git-status)) + (node (ewoc-next git-status last))) + (while (and node (> n 0)) + (when (eq 'unmerged (git-fileinfo->state (ewoc-data node))) + (setq n (1- n)) + (setq last node)) + (setq node (ewoc-next git-status node))) + (ewoc-goto-node git-status last))) + +(defun git-prev-unmerged-file (&optional n) + "Move the selection up N unmerged files." + (interactive "p") + (unless git-status (error "Not in git-status buffer.")) + (let* ((last (ewoc-locate git-status)) + (node (ewoc-prev git-status last))) + (while (and node (> n 0)) + (when (eq 'unmerged (git-fileinfo->state (ewoc-data node))) + (setq n (1- n)) + (setq last node)) + (setq node (ewoc-prev git-status node))) + (ewoc-goto-node git-status last))) + (defun git-add-file () "Add marked file(s) to the index cache." (interactive) @@ -737,7 +793,7 @@ The default is to fall back to the git repository config, then to `add-log-maili (interactive) (let ((files (git-marked-files-state 'unmerged))) (when files - (apply #'git-run-command nil nil "update-index" "--info-only" "--" (git-get-filenames files)) + (apply #'git-run-command nil nil "update-index" "--" (git-get-filenames files)) (git-set-files-state files 'modified) (git-refresh-files)))) @@ -849,18 +905,14 @@ The default is to fall back to the git repository config, then to `add-log-maili 'face 'git-header-face) (propertize git-log-msg-separator 'face 'git-separator-face) "\n") - (cond ((and merge-heads (file-readable-p ".git/MERGE_MSG")) + (cond ((file-readable-p ".git/MERGE_MSG") (insert-file-contents ".git/MERGE_MSG")) (sign-off (insert (format "\n\nSigned-off-by: %s <%s>\n" (git-get-committer-name) (git-get-committer-email))))))) - (let ((log-edit-font-lock-keywords - `(("^\\(Author:\\|Date:\\|Parent:\\|Signed-off-by:\\)\\(.*\\)" - (1 font-lock-keyword-face) - (2 font-lock-function-name-face)) - (,(concat "^\\(" (regexp-quote git-log-msg-separator) "\\)$") - (1 font-lock-comment-face))))) - (log-edit #'git-do-commit nil #'git-log-edit-files buffer)))) + (log-edit #'git-do-commit nil #'git-log-edit-files buffer) + (setq font-lock-keywords (font-lock-compile-keywords git-log-edit-font-lock-keywords)) + (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t))) (defun git-find-file () "Visit the current file in its own buffer." @@ -871,6 +923,15 @@ The default is to fall back to the git repository config, then to `add-log-maili (when (eq 'unmerged (git-fileinfo->state info)) (smerge-mode)))) +(defun git-find-file-other-window () + "Visit the current file in its own buffer in another window." + (interactive) + (unless git-status (error "Not in git-status buffer.")) + (let ((info (ewoc-data (ewoc-locate git-status)))) + (find-file-other-window (git-fileinfo->name info)) + (when (eq 'unmerged (git-fileinfo->state info)) + (smerge-mode)))) + (defun git-find-file-imerge () "Visit the current file in interactive merge mode." (interactive) @@ -939,6 +1000,8 @@ The default is to fall back to the git repository config, then to `add-log-maili (let ((map (make-keymap)) (diff-map (make-sparse-keymap))) (suppress-keymap map) + (define-key map "?" 'git-help) + (define-key map "h" 'git-help) (define-key map " " 'git-next-file) (define-key map "a" 'git-add-file) (define-key map "c" 'git-commit-file) @@ -952,7 +1015,10 @@ The default is to fall back to the git repository config, then to `add-log-maili (define-key map "m" 'git-mark-file) (define-key map "M" 'git-mark-all) (define-key map "n" 'git-next-file) + (define-key map "N" 'git-next-unmerged-file) + (define-key map "o" 'git-find-file-other-window) (define-key map "p" 'git-prev-file) + (define-key map "P" 'git-prev-unmerged-file) (define-key map "q" 'git-status-quit) (define-key map "r" 'git-remove-file) (define-key map "R" 'git-resolve-file) @@ -995,12 +1061,28 @@ Commands: (set (make-local-variable 'list-buffers-directory) default-directory) (run-hooks 'git-status-mode-hook))) +(defun git-find-status-buffer (dir) + "Find the git status buffer handling a specified directory." + (let ((list (buffer-list)) + (fulldir (expand-file-name dir)) + found) + (while (and list (not found)) + (let ((buffer (car list))) + (with-current-buffer buffer + (when (and list-buffers-directory + (string-equal fulldir (expand-file-name list-buffers-directory)) + (string-match "\\*git-status\\*$" (buffer-name buffer))) + (setq found buffer)))) + (setq list (cdr list))) + found)) + (defun git-status (dir) "Entry point into git-status mode." (interactive "DSelect directory: ") (setq dir (git-get-top-dir dir)) (if (file-directory-p (concat (file-name-as-directory dir) ".git")) - (let ((buffer (create-file-buffer (expand-file-name "*git-status*" dir)))) + (let ((buffer (or (and git-reuse-status-buffer (git-find-status-buffer dir)) + (create-file-buffer (expand-file-name "*git-status*" dir))))) (switch-to-buffer buffer) (cd dir) (git-status-mode) @@ -1008,5 +1090,10 @@ Commands: (goto-char (point-min))) (message "%s is not a git working tree." dir))) +(defun git-help () + "Display help for Git mode." + (interactive) + (describe-function 'git-status-mode)) + (provide 'git) ;;; git.el ends here