;;; git.el --- A user interface for git
-;; Copyright (C) 2005, 2006 Alexandre Julliard <julliard@winehq.org>
+;; Copyright (C) 2005, 2006, 2007 Alexandre Julliard <julliard@winehq.org>
;; Version: 1.0
(error "Failed to run \"git %s\":\n%s" (mapconcat (lambda (x) x) args " ") (buffer-string)))
(message "Running git %s...done" (car args)))
+(defun git-run-hook (hook env &rest args)
+ "Run a git hook and display its output if any."
+ (let ((dir default-directory)
+ (hook-name (expand-file-name (concat ".git/hooks/" hook))))
+ (or (not (file-executable-p hook-name))
+ (let (status (buffer (get-buffer-create "*Git Hook Output*")))
+ (with-current-buffer buffer
+ (erase-buffer)
+ (cd dir)
+ (setq status
+ (if env
+ (apply #'call-process "env" nil (list buffer t) nil
+ (append (git-get-env-strings env) (list hook-name) args))
+ (apply #'call-process hook-name nil (list buffer t) nil args))))
+ (display-message-or-buffer buffer)
+ (eq 0 status)))))
+
(defun git-get-string-sha1 (string)
"Read a SHA1 from the specified string."
(and string
(locale-charset-to-coding-system repo-config))
'utf-8)))
+(defun git-get-logoutput-coding-system ()
+ "Return the coding system used for git-log output."
+ (let ((repo-config (or (git-config "i18n.logoutputencoding")
+ (git-config "i18n.commitencoding"))))
+ (or git-commits-coding-system
+ (and repo-config
+ (fboundp 'locale-charset-to-coding-system)
+ (locale-charset-to-coding-system repo-config))
+ 'utf-8)))
+
(defun git-escape-file-name (name)
"Escape a file name if necessary."
(if (string-match "[\n\t\"\\]" name)
(push (match-string 0) heads))))
(nreverse heads)))
+(defun git-get-commit-description (commit)
+ "Get a one-line description of COMMIT."
+ (let ((coding-system-for-read (git-get-logoutput-coding-system)))
+ (let ((descr (git-call-process-env-string nil "log" "--max-count=1" "--pretty=oneline" commit)))
+ (if (and descr (string-match "\\`\\([0-9a-f]\\{40\\}\\) *\\(.*\\)$" descr))
+ (concat (substring (match-string 1 descr) 0 10) " - " (match-string 2 descr))
+ descr))))
+
;;;; File info structure
;;;; ------------------------------------------------------------
"Refresh the ewoc header and footer."
(let ((branch (git-symbolic-ref "HEAD"))
(head (if (git-empty-db-p) "Nothing committed yet"
- (substring (git-rev-parse "HEAD") 0 10)))
+ (git-get-commit-description "HEAD")))
(merge-heads (git-get-merge-heads)))
(ewoc-set-hf status
(format "Directory: %s\nBranch: %s\nHead: %s%s\n"
head
(if merge-heads
(concat "\nMerging: "
- (mapconcat (lambda (str) (substring str 0 10)) merge-heads " "))
+ (mapconcat (lambda (str) (git-get-commit-description str)) merge-heads "\n "))
""))
(if (ewoc-nth status 0) "" " No changes."))))
(when modified
(apply #'git-run-command nil env "update-index" "--" (git-get-filenames modified)))))
+(defun git-run-pre-commit-hook ()
+ "Run the pre-commit hook if any."
+ (unless git-status (error "Not in git-status buffer."))
+ (let ((files (git-marked-files-state 'added 'deleted 'modified)))
+ (or (not files)
+ (not (file-executable-p ".git/hooks/pre-commit"))
+ (let ((index-file (make-temp-file "gitidx")))
+ (unwind-protect
+ (let ((head-tree (unless (git-empty-db-p) (git-rev-parse "HEAD^{tree}"))))
+ (git-read-tree head-tree index-file)
+ (git-update-index index-file files)
+ (git-run-hook "pre-commit" `(("GIT_INDEX_FILE" . ,index-file))))
+ (delete-file index-file))))))
+
(defun git-do-commit ()
"Perform the actual commit using the current buffer as log message."
(interactive)
(git-run-command nil nil "rerere"))
(git-refresh-files)
(git-refresh-ewoc-hf git-status)
- (message "Committed %s." commit))
+ (message "Committed %s." commit)
+ (git-run-hook "post-commit" nil))
(message "Commit aborted."))))
(message "No files to commit.")))
(delete-file index-file))))))
"Commit the marked file(s), asking for a commit message."
(interactive)
(unless git-status (error "Not in git-status buffer."))
- (let ((buffer (get-buffer-create "*git-commit*"))
- (coding-system (git-get-commits-coding-system))
- author-name author-email subject date)
- (when (eq 0 (buffer-size buffer))
- (when (file-readable-p ".dotest/info")
- (with-temp-buffer
- (insert-file-contents ".dotest/info")
- (goto-char (point-min))
- (when (re-search-forward "^Author: \\(.*\\)\nEmail: \\(.*\\)$" nil t)
- (setq author-name (match-string 1))
- (setq author-email (match-string 2)))
- (goto-char (point-min))
- (when (re-search-forward "^Subject: \\(.*\\)$" nil t)
- (setq subject (match-string 1)))
- (goto-char (point-min))
- (when (re-search-forward "^Date: \\(.*\\)$" nil t)
- (setq date (match-string 1)))))
- (git-setup-log-buffer buffer author-name author-email subject date))
- (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))
- (setq buffer-file-coding-system coding-system)
- (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t)))
+ (when (git-run-pre-commit-hook)
+ (let ((buffer (get-buffer-create "*git-commit*"))
+ (coding-system (git-get-commits-coding-system))
+ author-name author-email subject date)
+ (when (eq 0 (buffer-size buffer))
+ (when (file-readable-p ".dotest/info")
+ (with-temp-buffer
+ (insert-file-contents ".dotest/info")
+ (goto-char (point-min))
+ (when (re-search-forward "^Author: \\(.*\\)\nEmail: \\(.*\\)$" nil t)
+ (setq author-name (match-string 1))
+ (setq author-email (match-string 2)))
+ (goto-char (point-min))
+ (when (re-search-forward "^Subject: \\(.*\\)$" nil t)
+ (setq subject (match-string 1)))
+ (goto-char (point-min))
+ (when (re-search-forward "^Date: \\(.*\\)$" nil t)
+ (setq date (match-string 1)))))
+ (git-setup-log-buffer buffer author-name author-email subject date))
+ (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))
+ (setq buffer-file-coding-system coding-system)
+ (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."