Merge branch 'master' into next
* master: AsciiDoc fix for tutorial git.el: Added customize support for all parameters. git.el: Added support for Signed-off-by. git.el: Automatically update .gitignore status. git.el: Set default directory before running the status mode setup hooks. git.el: Portability fixes for XEmacs and Emacs CVS. contrib/emacs: Add an Emacs VC backend.
This commit is contained in:
commit
b463ceb885
@ -309,7 +309,7 @@ git diff HEAD^^ HEAD^
|
||||
-------------------------------------
|
||||
|
||||
shows the difference between that previous state and the state two
|
||||
commits ago. Also, HEAD~5 can be used as a shorthand for HEAD^^^^^,
|
||||
commits ago. Also, HEAD~5 can be used as a shorthand for HEAD{caret}{caret}{caret}{caret}{caret},
|
||||
and more generally HEAD~n can refer to the nth previous commit.
|
||||
Commits representing merges have more than one parent, and you can
|
||||
specify which parent to follow in that case; see
|
||||
|
@ -39,13 +39,11 @@
|
||||
;; - hook into file save (after-save-hook)
|
||||
;; - diff against other branch
|
||||
;; - renaming files from the status buffer
|
||||
;; - support for appending signed-off-by
|
||||
;; - creating tags
|
||||
;; - fetch/pull
|
||||
;; - switching branches
|
||||
;; - revlist browser
|
||||
;; - git-show-branch browser
|
||||
;; - customize support
|
||||
;; - menus
|
||||
;;
|
||||
|
||||
@ -53,65 +51,92 @@
|
||||
(require 'ewoc)
|
||||
|
||||
|
||||
;;;; Faces
|
||||
;;;; Customizations
|
||||
;;;; ------------------------------------------------------------
|
||||
|
||||
(defgroup git nil
|
||||
"Git user interface")
|
||||
|
||||
(defcustom git-committer-name nil
|
||||
"User name to use for commits.
|
||||
The default is to fall back to `add-log-full-name' and then `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 `add-log-mailing-address' and then `user-mail-address'."
|
||||
:group 'git
|
||||
:type '(choice (const :tag "Default" nil)
|
||||
(string :tag "Email")))
|
||||
|
||||
(defcustom git-commits-coding-system 'utf-8
|
||||
"Default coding system for the log message of git commits."
|
||||
:group 'git
|
||||
:type 'coding-system)
|
||||
|
||||
(defcustom git-append-signed-off-by nil
|
||||
"Whether to append a Signed-off-by line to the commit message before editing."
|
||||
: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.")
|
||||
"Git mode face used to highlight added and modified files."
|
||||
:group 'git)
|
||||
|
||||
(defface git-unmerged-face
|
||||
'((((class color) (background light)) (:foreground "red" :bold t)))
|
||||
"Git mode face used to highlight unmerged files.")
|
||||
"Git mode face used to highlight unmerged files."
|
||||
:group 'git)
|
||||
|
||||
(defface git-unknown-face
|
||||
'((((class color) (background light)) (:foreground "goldenrod" :bold t)))
|
||||
"Git mode face used to highlight unknown files.")
|
||||
"Git mode face used to highlight unknown files."
|
||||
:group 'git)
|
||||
|
||||
(defface git-uptodate-face
|
||||
'((((class color) (background light)) (:foreground "grey60")))
|
||||
"Git mode face used to highlight up-to-date files.")
|
||||
"Git mode face used to highlight up-to-date files."
|
||||
:group 'git)
|
||||
|
||||
(defface git-ignored-face
|
||||
'((((class color) (background light)) (:foreground "grey60")))
|
||||
"Git mode face used to highlight ignored files.")
|
||||
"Git mode face used to highlight ignored files."
|
||||
:group 'git)
|
||||
|
||||
(defface git-mark-face
|
||||
'((((class color) (background light)) (:foreground "red" :bold t)))
|
||||
"Git mode face used for the file marks.")
|
||||
"Git mode face used for the file marks."
|
||||
:group 'git)
|
||||
|
||||
(defface git-header-face
|
||||
'((((class color) (background light)) (:foreground "blue")))
|
||||
"Git mode face used for commit headers.")
|
||||
"Git mode face used for commit headers."
|
||||
:group 'git)
|
||||
|
||||
(defface git-separator-face
|
||||
'((((class color) (background light)) (:foreground "brown")))
|
||||
"Git mode face used for commit separator.")
|
||||
"Git mode face used for commit separator."
|
||||
:group 'git)
|
||||
|
||||
(defface git-permission-face
|
||||
'((((class color) (background light)) (:foreground "green" :bold t)))
|
||||
"Git mode face used for permission changes.")
|
||||
|
||||
(defvar git-committer-name nil
|
||||
"*User name to use for commits.
|
||||
If not set, fall back to `add-log-full-name' and then `user-full-name'.")
|
||||
|
||||
(defvar git-committer-email nil
|
||||
"*Email address to use for commits.
|
||||
If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.")
|
||||
|
||||
(defvar git-commits-coding-system 'utf-8
|
||||
"Default coding system for git commits.")
|
||||
|
||||
(defconst git-log-msg-separator "--- log message follows this line ---")
|
||||
|
||||
(defconst git-per-dir-ignore-file ".gitignore"
|
||||
"Name of the per-directory ignore file.")
|
||||
"Git mode face used for permission changes."
|
||||
:group 'git)
|
||||
|
||||
|
||||
;;;; Utilities
|
||||
;;;; ------------------------------------------------------------
|
||||
|
||||
(defconst git-log-msg-separator "--- log message follows this line ---")
|
||||
|
||||
(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))
|
||||
@ -213,14 +238,19 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
|
||||
"Add a file name to the ignore file in its directory."
|
||||
(let* ((fullname (expand-file-name file))
|
||||
(dir (file-name-directory fullname))
|
||||
(name (file-name-nondirectory fullname)))
|
||||
(name (file-name-nondirectory fullname))
|
||||
(ignore-name (expand-file-name git-per-dir-ignore-file dir))
|
||||
(created (not (file-exists-p ignore-name))))
|
||||
(save-window-excursion
|
||||
(set-buffer (find-file-noselect (expand-file-name git-per-dir-ignore-file dir)))
|
||||
(set-buffer (find-file-noselect ignore-name))
|
||||
(goto-char (point-max))
|
||||
(unless (zerop (current-column)) (insert "\n"))
|
||||
(insert name "\n")
|
||||
(sort-lines nil (point-min) (point-max))
|
||||
(save-buffer))))
|
||||
(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))))
|
||||
|
||||
|
||||
;;;; Wrappers for basic git commands
|
||||
@ -272,7 +302,7 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
|
||||
(with-current-buffer buffer
|
||||
(goto-char (point-min))
|
||||
(if
|
||||
(setq log-start (re-search-forward (concat "^" git-log-msg-separator "\n") nil t))
|
||||
(setq log-start (re-search-forward (concat "^" (regexp-quote git-log-msg-separator) "\n") nil t))
|
||||
(save-restriction
|
||||
(narrow-to-region (point-min) log-start)
|
||||
(goto-char (point-min))
|
||||
@ -388,9 +418,9 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
|
||||
(propertize
|
||||
(if (or (not old-perm)
|
||||
(not new-perm)
|
||||
(eq 0 (logand #O111 (logxor old-perm new-perm))))
|
||||
(eq 0 (logand ?\111 (logxor old-perm new-perm))))
|
||||
" "
|
||||
(if (eq 0 (logand #O111 old-perm)) "+x" "-x"))
|
||||
(if (eq 0 (logand ?\111 old-perm)) "+x" "-x"))
|
||||
'face 'git-permission-face))
|
||||
|
||||
(defun git-fileinfo-prettyprint (info)
|
||||
@ -787,7 +817,8 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
|
||||
(unless git-status (error "Not in git-status buffer."))
|
||||
(let ((buffer (get-buffer-create "*git-commit*"))
|
||||
(merge-heads (git-get-merge-heads))
|
||||
(dir default-directory))
|
||||
(dir default-directory)
|
||||
(sign-off git-append-signed-off-by))
|
||||
(with-current-buffer buffer
|
||||
(when (eq 0 (buffer-size))
|
||||
(cd dir)
|
||||
@ -804,9 +835,18 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
|
||||
'face 'git-header-face)
|
||||
(propertize git-log-msg-separator 'face 'git-separator-face)
|
||||
"\n")
|
||||
(when (and merge-heads (file-readable-p ".git/MERGE_MSG"))
|
||||
(insert-file-contents ".git/MERGE_MSG"))))
|
||||
(log-edit #'git-do-commit nil #'git-log-edit-files buffer)))
|
||||
(cond ((and merge-heads (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))))
|
||||
|
||||
(defun git-find-file ()
|
||||
"Visit the current file in its own buffer."
|
||||
@ -891,7 +931,7 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
|
||||
(define-key map "d" diff-map)
|
||||
(define-key map "=" 'git-diff-file)
|
||||
(define-key map "f" 'git-find-file)
|
||||
(define-key map [RET] 'git-find-file)
|
||||
(define-key map "\r" 'git-find-file)
|
||||
(define-key map "g" 'git-refresh-status)
|
||||
(define-key map "i" 'git-ignore-file)
|
||||
(define-key map "l" 'git-log-file)
|
||||
@ -937,6 +977,7 @@ Commands:
|
||||
(erase-buffer)
|
||||
(let ((status (ewoc-create 'git-fileinfo-prettyprint "" "")))
|
||||
(set (make-local-variable 'git-status) status))
|
||||
(set (make-local-variable 'list-buffers-directory) default-directory)
|
||||
(run-hooks 'git-status-mode-hook)))
|
||||
|
||||
(defun git-status (dir)
|
||||
@ -946,8 +987,8 @@ Commands:
|
||||
(if (file-directory-p (concat (file-name-as-directory dir) ".git"))
|
||||
(let ((buffer (create-file-buffer (expand-file-name "*git-status*" dir))))
|
||||
(switch-to-buffer buffer)
|
||||
(git-status-mode)
|
||||
(cd dir)
|
||||
(git-status-mode)
|
||||
(git-refresh-status)
|
||||
(goto-char (point-min)))
|
||||
(message "%s is not a git working tree." dir)))
|
||||
|
135
contrib/emacs/vc-git.el
Normal file
135
contrib/emacs/vc-git.el
Normal file
@ -0,0 +1,135 @@
|
||||
;;; vc-git.el --- VC backend for the git version control system
|
||||
|
||||
;; Copyright (C) 2006 Alexandre Julliard
|
||||
|
||||
;; This program is free software; you can redistribute it and/or
|
||||
;; modify it under the terms of the GNU General Public License as
|
||||
;; published by the Free Software Foundation; either version 2 of
|
||||
;; the License, or (at your option) any later version.
|
||||
;;
|
||||
;; This program is distributed in the hope that it will be
|
||||
;; useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
;; PURPOSE. See the GNU General Public License for more details.
|
||||
;;
|
||||
;; You should have received a copy of the GNU General Public
|
||||
;; License along with this program; if not, write to the Free
|
||||
;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
|
||||
;; MA 02111-1307 USA
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; This file contains a VC backend for the git version control
|
||||
;; system.
|
||||
;;
|
||||
;; To install: put this file on the load-path and add GIT to the list
|
||||
;; of supported backends in `vc-handled-backends'.
|
||||
;;
|
||||
;; TODO
|
||||
;; - changelog generation
|
||||
;; - working with revisions other than HEAD
|
||||
;;
|
||||
|
||||
(defvar git-commits-coding-system 'utf-8
|
||||
"Default coding system for git commits.")
|
||||
|
||||
(defun vc-git--run-command-string (file &rest args)
|
||||
"Run a git command on FILE and return its output as string."
|
||||
(let* ((ok t)
|
||||
(str (with-output-to-string
|
||||
(with-current-buffer standard-output
|
||||
(unless (eq 0 (apply #'call-process "git" nil '(t nil) nil
|
||||
(append args (list (file-relative-name file)))))
|
||||
(setq ok nil))))))
|
||||
(and ok str)))
|
||||
|
||||
(defun vc-git--run-command (file &rest args)
|
||||
"Run a git command on FILE, discarding any output."
|
||||
(let ((name (file-relative-name file)))
|
||||
(eq 0 (apply #'call-process "git" nil (get-buffer "*Messages") nil (append args (list name))))))
|
||||
|
||||
(defun vc-git-registered (file)
|
||||
"Check whether FILE is registered with git."
|
||||
(with-temp-buffer
|
||||
(let* ((dir (file-name-directory file))
|
||||
(name (file-relative-name file dir)))
|
||||
(when dir (cd dir))
|
||||
(and (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name))
|
||||
(let ((str (buffer-string)))
|
||||
(and (> (length str) (length name))
|
||||
(string= (substring str 0 (1+ (length name))) (concat name "\0"))))))))
|
||||
|
||||
(defun vc-git-state (file)
|
||||
"git-specific version of `vc-state'."
|
||||
(let ((diff (vc-git--run-command-string file "diff-index" "-z" "HEAD" "--")))
|
||||
(if (and diff (string-match ":[0-7]\\{6\\} [0-7]\\{6\\} [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} [ADMU]\0[^\0]+\0" diff))
|
||||
'edited
|
||||
'up-to-date)))
|
||||
|
||||
(defun vc-git-workfile-version (file)
|
||||
"git-specific version of `vc-workfile-version'."
|
||||
(let ((str (with-output-to-string
|
||||
(with-current-buffer standard-output
|
||||
(call-process "git" nil '(t nil) nil "symbolic-ref" "HEAD")))))
|
||||
(if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str)
|
||||
(match-string 2 str)
|
||||
str)))
|
||||
|
||||
(defun vc-git-revert (file &optional contents-done)
|
||||
"Revert FILE to the version stored in the git repository."
|
||||
(if contents-done
|
||||
(vc-git--run-command file "update-index" "--")
|
||||
(vc-git--run-command file "checkout" "HEAD")))
|
||||
|
||||
(defun vc-git-checkout-model (file)
|
||||
'implicit)
|
||||
|
||||
(defun vc-git-workfile-unchanged-p (file)
|
||||
(let ((sha1 (vc-git--run-command-string file "hash-object" "--"))
|
||||
(head (vc-git--run-command-string file "ls-tree" "-z" "HEAD" "--")))
|
||||
(and head
|
||||
(string-match "[0-7]\\{6\\} blob \\([0-9a-f]\\{40\\}\\)\t[^\0]+\0" head)
|
||||
(string= (car (split-string sha1 "\n")) (match-string 1 head)))))
|
||||
|
||||
(defun vc-git-register (file &optional rev comment)
|
||||
"Register FILE into the git version-control system."
|
||||
(vc-git--run-command file "update-index" "--add" "--"))
|
||||
|
||||
(defun vc-git-print-log (file)
|
||||
(let ((name (file-relative-name file))
|
||||
(coding-system-for-read git-commits-coding-system))
|
||||
(vc-do-command nil 'async "git" name "rev-list" "--pretty" "HEAD" "--")))
|
||||
|
||||
(defun vc-git-diff (file &optional rev1 rev2)
|
||||
(let ((name (file-relative-name file)))
|
||||
(if (and rev1 rev2)
|
||||
(vc-do-command "*vc-diff*" 0 "git" name "diff-tree" "-p" rev1 rev2 "--")
|
||||
(vc-do-command "*vc-diff*" 0 "git" name "diff-index" "-p" (or rev1 "HEAD") "--"))
|
||||
; git-diff-index doesn't set exit status like diff does
|
||||
(if (vc-git-workfile-unchanged-p file) 0 1)))
|
||||
|
||||
(defun vc-git-checkin (file rev comment)
|
||||
(let ((coding-system-for-write git-commits-coding-system))
|
||||
(vc-git--run-command file "commit" "-m" comment "--only" "--")))
|
||||
|
||||
(defun vc-git-checkout (file &optional editable rev destfile)
|
||||
(vc-git--run-command file "checkout" (or rev "HEAD")))
|
||||
|
||||
(defun vc-git-annotate-command (file buf &optional rev)
|
||||
; FIXME: rev is ignored
|
||||
(let ((name (file-relative-name file)))
|
||||
(call-process "git" nil buf nil "annotate" name)))
|
||||
|
||||
(defun vc-git-annotate-time ()
|
||||
(and (re-search-forward "[0-9a-f]+\t(.*\t\\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\)\t[0-9]+)" nil t)
|
||||
(vc-annotate-convert-time
|
||||
(apply #'encode-time (mapcar (lambda (match) (string-to-number (match-string match))) '(6 5 4 3 2 1 7))))))
|
||||
|
||||
;; Not really useful since we can't do anything with the revision yet
|
||||
;;(defun vc-annotate-extract-revision-at-line ()
|
||||
;; (save-excursion
|
||||
;; (move-beginning-of-line 1)
|
||||
;; (and (looking-at "[0-9a-f]+")
|
||||
;; (buffer-substring (match-beginning 0) (match-end 0)))))
|
||||
|
||||
(provide 'vc-git)
|
Loading…
Reference in New Issue
Block a user