{"thread":{"id":"3386","subject":"[PATCH] Add an Emacs interface in contrib.","startedAt":"2006-02-18T16:50:49Z","lastAt":"2006-02-18T16:50:49Z","messageCount":1,"participants":["Alexandre Julliard"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"16389","messageId":"87mzgok3va.fsf@wine.dyndns.org","threadId":"3386","inReplyTo":null,"subject":"[PATCH] Add an Emacs interface in contrib.","fromName":"Alexandre Julliard","fromEmail":"julliard@winehq.org","sentAt":"2006-02-18T16:50:49Z","receivedAt":"2006-02-18T16:50:49Z","isPatch":true,"sender":{"key":"julliard@winehq.org","avatar":null},"body":"This is an Emacs interface for git. The user interface is modeled on\npcl-cvs. It has been developed on Emacs 21 and will probably need some\ntweaking to work on XEmacs.\n\nThe basic command is 'M-x git-status' which displays a buffer listing\nmodified files in the selected project tree. In that buffer the\nfollowing features are supported:\n\n  - add/remove files\n  - list unknown files\n  - commit marked files\n  - manage .gitignore\n  - commit merges based on MERGE_HEAD\n  - revert files to the HEAD version\n  - resolve conflicts with smerge or ediff\n  - diff files against HEAD/base/mine/other or combined diff\n  - get a log of the revisions for specified files\n\nThere are plenty of unimplemented features too, see the TODO list at\nthe top of the file...\n\nSigned-off-by: Alexandre Julliard <julliard@winehq.org>\n\n---\n\n contrib/emacs/git.el |  956 ++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 956 insertions(+), 0 deletions(-)\n create mode 100644 contrib/emacs/git.el\n\nfd1fee63b25d241f6f345fa33bdc6fd7554841dc\ndiff --git a/contrib/emacs/git.el b/contrib/emacs/git.el\nnew file mode 100644\nindex 0000000..8f23477\n--- /dev/null\n+++ b/contrib/emacs/git.el\n@@ -0,0 +1,956 @@\n+;;; git.el --- A user interface for git\n+\n+;; Copyright (C) 2005, 2006 Alexandre Julliard <julliard@winehq.org>\n+\n+;; Version: 1.0\n+\n+;; This program is free software; you can redistribute it and/or\n+;; modify it under the terms of the GNU General Public License as\n+;; published by the Free Software Foundation; either version 2 of\n+;; the License, or (at your option) any later version.\n+;;\n+;; This program is distributed in the hope that it will be\n+;; useful, but WITHOUT ANY WARRANTY; without even the implied\n+;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR\n+;; PURPOSE.  See the GNU General Public License for more details.\n+;;\n+;; You should have received a copy of the GNU General Public\n+;; License along with this program; if not, write to the Free\n+;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,\n+;; MA 02111-1307 USA\n+\n+;;; Commentary:\n+\n+;; This file contains an interface for the git version control\n+;; system. It provides easy access to the most frequently used git\n+;; commands. The user interface is as far as possible identical to\n+;; that of the PCL-CVS mode.\n+;;\n+;; To install: put this file on the load-path and place the following\n+;; in your .emacs file:\n+;;\n+;;    (require 'git)\n+;;\n+;; To start: `M-x git-status'\n+;;\n+;; TODO\n+;;  - portability to XEmacs\n+;;  - better handling of subprocess errors\n+;;  - hook into file save (after-save-hook)\n+;;  - diff against other branch\n+;;  - renaming files from the status buffer\n+;;  - support for appending signed-off-by\n+;;  - creating tags\n+;;  - fetch/pull\n+;;  - switching branches\n+;;  - revlist browser\n+;;  - git-show-branch browser\n+;;  - customize support\n+;;  - menus\n+;;\n+\n+(eval-when-compile (require 'cl))\n+(require 'ewoc)\n+\n+\n+;;;; Faces\n+;;;; ------------------------------------------------------------\n+\n+(defface git-status-face\n+  '((((class color) (background light)) (:foreground \"purple\")))\n+  \"Git mode face used to highlight added and modified files.\")\n+\n+(defface git-unmerged-face\n+  '((((class color) (background light)) (:foreground \"red\" :bold t)))\n+  \"Git mode face used to highlight unmerged files.\")\n+\n+(defface git-unknown-face\n+  '((((class color) (background light)) (:foreground \"goldenrod\" :bold t)))\n+  \"Git mode face used to highlight unknown files.\")\n+\n+(defface git-uptodate-face\n+  '((((class color) (background light)) (:foreground \"grey60\")))\n+  \"Git mode face used to highlight up-to-date files.\")\n+\n+(defface git-ignored-face\n+  '((((class color) (background light)) (:foreground \"grey60\")))\n+  \"Git mode face used to highlight ignored files.\")\n+\n+(defface git-mark-face\n+  '((((class color) (background light)) (:foreground \"red\" :bold t)))\n+  \"Git mode face used for the file marks.\")\n+\n+(defface git-header-face\n+  '((((class color) (background light)) (:foreground \"blue\")))\n+  \"Git mode face used for commit headers.\")\n+\n+(defface git-separator-face\n+  '((((class color) (background light)) (:foreground \"brown\")))\n+  \"Git mode face used for commit separator.\")\n+\n+(defface git-permission-face\n+  '((((class color) (background light)) (:foreground \"green\" :bold t)))\n+  \"Git mode face used for permission changes.\")\n+\n+(defvar git-committer-name nil\n+  \"*User name to use for commits.\n+If not set, fall back to `add-log-full-name' and then `user-full-name'.\")\n+\n+(defvar git-committer-email nil\n+  \"*Email address to use for commits.\n+If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.\")\n+\n+(defvar git-commits-coding-system 'utf-8\n+  \"Default coding system for git commits.\")\n+\n+(defconst git-log-msg-separator \"--- log message follows this line ---\")\n+\n+(defconst git-per-dir-ignore-file \".gitignore\"\n+  \"Name of the per-directory ignore file.\")\n+\n+\n+;;;; Utilities\n+;;;; ------------------------------------------------------------\n+\n+(defun git-get-env-strings (env)\n+  \"Build a list of NAME=VALUE strings from a list of environment strings.\"\n+  (mapcar (lambda (entry) (concat (car entry) \"=\" (cdr entry))) env))\n+\n+(defun git-call-process-env (buffer env &rest args)\n+  \"Wrapper for call-process that sets environment strings.\"\n+  (if env\n+      (apply #'call-process \"env\" nil buffer nil\n+             (append (git-get-env-strings env) (list \"git\") args))\n+    (apply #'call-process \"git\" nil buffer nil args)))\n+\n+(defun git-run-process-region (buffer start end program args)\n+  \"Run a git process with a buffer region as input.\"\n+  (let ((output-buffer (current-buffer))\n+        (dir default-directory))\n+    (with-current-buffer buffer\n+      (cd dir)\n+      (apply #'call-process-region start end program\n+             nil (list output-buffer nil) nil args))))\n+\n+(defun git-run-command-buffer (buffer-name &rest args)\n+  \"Run a git command, sending the output to a buffer named BUFFER-NAME.\"\n+  (let ((dir default-directory)\n+        (buffer (get-buffer-create buffer-name)))\n+    (message \"Running git %s...\" (car args))\n+    (with-current-buffer buffer\n+      (let ((default-directory dir)\n+            (buffer-read-only nil))\n+        (erase-buffer)\n+        (apply #'git-call-process-env buffer nil args)))\n+    (message \"Running git %s...done\" (car args))\n+    buffer))\n+\n+(defun git-run-command (buffer env &rest args)\n+  (message \"Running git %s...\" (car args))\n+  (apply #'git-call-process-env buffer env args)\n+  (message \"Running git %s...done\" (car args)))\n+\n+(defun git-run-command-region (buffer start end env &rest args)\n+  \"Run a git command with specified buffer region as input.\"\n+  (message \"Running git %s...\" (car args))\n+  (unless (eq 0 (if env\n+                    (git-run-process-region\n+                     buffer start end \"env\"\n+                     (append (git-get-env-strings env) (list \"git\") args))\n+                  (git-run-process-region\n+                   buffer start end \"git\" args)))\n+    (error \"Failed to run \\\"git %s\\\":\\n%s\" (mapconcat (lambda (x) x) args \" \") (buffer-string)))\n+  (message \"Running git %s...done\" (car args)))\n+\n+(defun git-get-string-sha1 (string)\n+  \"Read a SHA1 from the specified string.\"\n+  (let ((pos (string-match \"[0-9a-f]\\\\{40\\\\}\" string)))\n+    (and pos (substring string pos (match-end 0)))))\n+\n+(defun git-get-committer-name ()\n+  \"Return the name to use as GIT_COMMITTER_NAME.\"\n+  ; copied from log-edit\n+  (or git-committer-name\n+      (and (boundp 'add-log-full-name) add-log-full-name)\n+      (and (fboundp 'user-full-name) (user-full-name))\n+      (and (boundp 'user-full-name) user-full-name)))\n+\n+(defun git-get-committer-email ()\n+  \"Return the email address to use as GIT_COMMITTER_EMAIL.\"\n+  ; copied from log-edit\n+  (or git-committer-email\n+      (and (boundp 'add-log-mailing-address) add-log-mailing-address)\n+      (and (fboundp 'user-mail-address) (user-mail-address))\n+      (and (boundp 'user-mail-address) user-mail-address)))\n+\n+(defun git-escape-file-name (name)\n+  \"Escape a file name if necessary.\"\n+  (if (string-match \"[\\n\\t\\\"\\\\]\" name)\n+      (concat \"\\\"\"\n+              (mapconcat (lambda (c)\n+                   (case c\n+                     (?\\n \"\\\\n\")\n+                     (?\\t \"\\\\t\")\n+                     (?\\\\ \"\\\\\\\\\")\n+                     (?\\\" \"\\\\\\\"\")\n+                     (t (char-to-string c))))\n+                 name \"\")\n+              \"\\\"\")\n+    name))\n+\n+(defun git-get-top-dir (dir)\n+  \"Retrieve the top-level directory of a git tree.\"\n+  (let ((cdup (with-output-to-string\n+                (with-current-buffer standard-output\n+                  (cd dir)\n+                  (unless (eq 0 (call-process \"git\" nil t nil \"rev-parse\" \"--show-cdup\"))\n+                    (error \"cannot find top-level git tree for %s.\" dir))))))\n+    (expand-file-name (concat (file-name-as-directory dir)\n+                              (car (split-string cdup \"\\n\"))))))\n+\n+;stolen from pcl-cvs\n+(defun git-append-to-ignore (file)\n+  \"Add a file name to the ignore file in its directory.\"\n+  (let* ((fullname (expand-file-name file))\n+         (dir (file-name-directory fullname))\n+         (name (file-name-nondirectory fullname)))\n+  (save-window-excursion\n+    (set-buffer (find-file-noselect (expand-file-name git-per-dir-ignore-file dir)))\n+    (goto-char (point-max))\n+    (unless (zerop (current-column)) (insert \"\\n\"))\n+    (insert name \"\\n\")\n+    (sort-lines nil (point-min) (point-max))\n+    (save-buffer))))\n+\n+\n+;;;; Wrappers for basic git commands\n+;;;; ------------------------------------------------------------\n+\n+(defun git-rev-parse (rev)\n+  \"Parse a revision name and return its SHA1.\"\n+  (git-get-string-sha1\n+   (with-output-to-string\n+     (with-current-buffer standard-output\n+       (git-call-process-env t nil \"rev-parse\" rev)))))\n+\n+(defun git-symbolic-ref (ref)\n+  \"Wrapper for the git-symbolic-ref command.\"\n+  (car\n+   (split-string\n+    (with-output-to-string\n+      (with-current-buffer standard-output\n+        (git-call-process-env t nil \"symbolic-ref\" ref)))\n+    \"\\n\")))\n+\n+(defun git-update-ref (ref val &optional oldval)\n+  \"Update a reference by calling git-update-ref.\"\n+  (apply #'git-call-process-env nil nil \"update-ref\" ref val (if oldval (list oldval))))\n+\n+(defun git-read-tree (tree &optional index-file)\n+  \"Read a tree into the index file.\"\n+  (apply #'git-call-process-env nil\n+         (if index-file `((\"GIT_INDEX_FILE\" . ,index-file)) nil)\n+         \"read-tree\" (if tree (list tree))))\n+\n+(defun git-write-tree (&optional index-file)\n+  \"Call git-write-tree and return the resulting tree SHA1 as a string.\"\n+  (git-get-string-sha1\n+   (with-output-to-string\n+     (with-current-buffer standard-output\n+       (git-call-process-env t\n+        (if index-file `((\"GIT_INDEX_FILE\" . ,index-file)) nil)\n+        \"write-tree\")))))\n+\n+(defun git-commit-tree (buffer tree head)\n+  \"Call git-commit-tree with buffer as input and return the resulting commit SHA1.\"\n+  (let ((author-name (git-get-committer-name))\n+        (author-email (git-get-committer-email))\n+        author-date log-start log-end args)\n+    (when head\n+      (push \"-p\" args)\n+      (push head args))\n+    (with-current-buffer buffer\n+      (goto-char (point-min))\n+      (if\n+          (setq log-start (re-search-forward (concat \"^\" git-log-msg-separator \"\\n\") nil t))\n+          (save-restriction\n+            (narrow-to-region (point-min) log-start)\n+            (goto-char (point-min))\n+            (when (re-search-forward \"^Author: +\\\\(.*?\\\\) *<\\\\(.*\\\\)> *$\" nil t)\n+              (setq author-name (match-string 1)\n+                    author-email (match-string 2)))\n+            (goto-char (point-min))\n+            (when (re-search-forward \"^Date: +\\\\(.*\\\\)$\" nil t)\n+              (setq author-date (match-string 1)))\n+            (goto-char (point-min))\n+            (while (re-search-forward \"^Parent: +\\\\([0-9a-f]+\\\\)\" nil t)\n+              (unless (string-equal head (match-string 1))\n+                (push \"-p\" args)\n+                (push (match-string 1) args))))\n+        (setq log-start (point-min)))\n+      (setq log-end (point-max)))\n+    (git-get-string-sha1\n+     (with-output-to-string\n+       (with-current-buffer standard-output\n+         (let ((coding-system-for-write git-commits-coding-system)\n+               (env `((\"GIT_AUTHOR_NAME\" . ,author-name)\n+                      (\"GIT_AUTHOR_EMAIL\" . ,author-email)\n+                      (\"GIT_COMMITTER_NAME\" . ,(git-get-committer-name))\n+                      (\"GIT_COMMITTER_EMAIL\" . ,(git-get-committer-email)))))\n+           (when author-date (push `(\"GIT_AUTHOR_DATE\" . ,author-date) env))\n+           (apply #'git-run-command-region\n+                  buffer log-start log-end env\n+                  \"commit-tree\" tree (nreverse args))))))))\n+\n+(defun git-empty-db-p ()\n+  \"Check if the git db is empty (no commit done yet).\"\n+  (not (eq 0 (call-process \"git\" nil nil nil \"rev-parse\" \"--verify\" \"HEAD\"))))\n+\n+(defun git-get-merge-heads ()\n+  \"Retrieve the merge heads from the MERGE_HEAD file if present.\"\n+  (let (heads)\n+    (when (file-readable-p \".git/MERGE_HEAD\")\n+      (with-temp-buffer\n+        (insert-file-contents \".git/MERGE_HEAD\" nil nil nil t)\n+        (goto-char (point-min))\n+        (while (re-search-forward \"[0-9a-f]\\\\{40\\\\}\" nil t)\n+          (push (match-string 0) heads))))\n+    (nreverse heads)))\n+\n+;;;; File info structure\n+;;;; ------------------------------------------------------------\n+\n+; fileinfo structure stolen from pcl-cvs\n+(defstruct (git-fileinfo\n+            (:copier nil)\n+            (:constructor git-create-fileinfo (state name &optional old-perm new-perm rename-state orig-name marked))\n+            (:conc-name git-fileinfo->))\n+  marked              ;; t/nil\n+  state               ;; current state\n+  name                ;; file name\n+  old-perm new-perm   ;; permission flags\n+  rename-state        ;; rename or copy state\n+  orig-name           ;; original name for renames or copies\n+  needs-refresh)      ;; whether file needs to be refreshed\n+\n+(defvar git-status nil)\n+\n+(defun git-clear-status (status)\n+  \"Remove everything from the status list.\"\n+  (ewoc-filter status (lambda (info) nil)))\n+\n+(defun git-set-files-state (files state)\n+  \"Set the state of a list of files.\"\n+  (dolist (info files)\n+    (unless (eq (git-fileinfo->state info) state)\n+      (setf (git-fileinfo->state info) state)\n+      (setf (git-fileinfo->rename-state info) nil)\n+      (setf (git-fileinfo->orig-name info) nil)\n+      (setf (git-fileinfo->needs-refresh info) t))))\n+\n+(defun git-state-code (code)\n+  \"Convert from a string to a added/deleted/modified state.\"\n+  (case (string-to-char code)\n+    (?M 'modified)\n+    (?? 'unknown)\n+    (?A 'added)\n+    (?D 'deleted)\n+    (?U 'unmerged)\n+    (t nil)))\n+\n+(defun git-status-code-as-string (code)\n+  \"Format a git status code as string.\"\n+  (case code\n+    ('modified (propertize \"Modified\" 'face 'git-status-face))\n+    ('unknown  (propertize \"Unknown \" 'face 'git-unknown-face))\n+    ('added    (propertize \"Added   \" 'face 'git-status-face))\n+    ('deleted  (propertize \"Deleted \" 'face 'git-status-face))\n+    ('unmerged (propertize \"Unmerged\" 'face 'git-unmerged-face))\n+    ('uptodate (propertize \"Uptodate\" 'face 'git-uptodate-face))\n+    ('ignored  (propertize \"Ignored \" 'face 'git-ignored-face))\n+    (t \"?       \")))\n+\n+(defun git-rename-as-string (info)\n+  \"Return a string describing the copy or rename associated with INFO, or an empty string if none.\"\n+  (let ((state (git-fileinfo->rename-state info)))\n+    (if state\n+        (propertize\n+         (concat \"   (\"\n+                 (if (eq state 'copy) \"copied from \"\n+                   (if (eq (git-fileinfo->state info) 'added) \"renamed to \"\n+                     \"renamed from \"))\n+                 (git-escape-file-name (git-fileinfo->orig-name info))\n+                 \")\") 'face 'git-status-face)\n+      \"\")))\n+\n+(defun git-permissions-as-string (old-perm new-perm)\n+  \"Format a permission change as string.\"\n+  (propertize\n+   (if (or (not old-perm)\n+           (not new-perm)\n+           (eq 0 (logand #O111 (logxor old-perm new-perm))))\n+       \"  \"\n+     (if (eq 0 (logand #O111 old-perm)) \"+x\" \"-x\"))\n+  'face 'git-permission-face))\n+\n+(defun git-fileinfo-prettyprint (info)\n+  \"Pretty-printer for the git-fileinfo structure.\"\n+  (insert (format \"   %s %s %s  %s%s\"\n+                  (if (git-fileinfo->marked info) (propertize \"*\" 'face 'git-mark-face) \" \")\n+                  (git-status-code-as-string (git-fileinfo->state info))\n+                  (git-permissions-as-string (git-fileinfo->old-perm info) (git-fileinfo->new-perm info))\n+                  (git-escape-file-name (git-fileinfo->name info))\n+                  (git-rename-as-string info))))\n+\n+(defun git-parse-status (status)\n+  \"Parse the output of git-diff-index in the current buffer.\"\n+  (goto-char (point-min))\n+  (while (re-search-forward\n+          \":\\\\([0-7]\\\\{6\\\\}\\\\) \\\\([0-7]\\\\{6\\\\}\\\\) [0-9a-f]\\\\{40\\\\} [0-9a-f]\\\\{40\\\\} \\\\(\\\\([ADMU]\\\\)\\0\\\\([^\\0]+\\\\)\\\\|\\\\([CR]\\\\)[0-9]*\\0\\\\([^\\0]+\\\\)\\0\\\\([^\\0]+\\\\)\\\\)\\0\"\n+          nil t 1)\n+    (let ((old-perm (string-to-number (match-string 1) 8))\n+          (new-perm (string-to-number (match-string 2) 8))\n+          (state (or (match-string 4) (match-string 6)))\n+          (name (or (match-string 5) (match-string 7)))\n+          (new-name (match-string 8)))\n+      (if new-name  ; copy or rename\n+          (if (eq ?C (string-to-char state))\n+              (ewoc-enter-last status (git-create-fileinfo 'added new-name old-perm new-perm 'copy name))\n+            (ewoc-enter-last status (git-create-fileinfo 'deleted name 0 0 'rename new-name))\n+            (ewoc-enter-last status (git-create-fileinfo 'added new-name old-perm new-perm 'rename name)))\n+        (ewoc-enter-last status (git-create-fileinfo (git-state-code state) name old-perm new-perm))))))\n+\n+(defun git-find-status-file (status file)\n+  \"Find a given file in the status ewoc and return its node.\"\n+  (let ((node (ewoc-nth status 0)))\n+    (while (and node (not (string= file (git-fileinfo->name (ewoc-data node)))))\n+      (setq node (ewoc-next status node)))\n+    node))\n+\n+(defun git-parse-ls-files (status default-state &optional skip-existing)\n+  \"Parse the output of git-ls-files in the current buffer.\"\n+  (goto-char (point-min))\n+  (let (infolist)\n+    (while (re-search-forward \"\\\\([HMRCK?]\\\\) \\\\([^\\0]*\\\\)\\0\" nil t 1)\n+      (let ((state (match-string 1))\n+            (name (match-string 2)))\n+        (unless (and skip-existing (git-find-status-file status name))\n+          (push (git-create-fileinfo (or (git-state-code state) default-state) name) infolist))))\n+    (dolist (info (nreverse infolist))\n+      (ewoc-enter-last status info))))\n+\n+(defun git-parse-ls-unmerged (status)\n+  \"Parse the output of git-ls-files -u in the current buffer.\"\n+  (goto-char (point-min))\n+  (let (files)\n+    (while (re-search-forward \"[0-7]\\\\{6\\\\} [0-9a-f]\\\\{40\\\\} [123]\\t\\\\([^\\0]+\\\\)\\0\" nil t)\n+      (let ((node (git-find-status-file status (match-string 1))))\n+        (when node (push (ewoc-data node) files))))\n+    (git-set-files-state files 'unmerged)))\n+\n+(defun git-add-status-file (state name)\n+  \"Add a new file to the status list (if not existing already) and return its node.\"\n+  (unless git-status (error \"Not in git-status buffer.\"))\n+  (or (git-find-status-file git-status name)\n+      (ewoc-enter-last git-status (git-create-fileinfo state name))))\n+\n+(defun git-marked-files ()\n+  \"Return a list of all marked files, or if none a list containing just the file at cursor position.\"\n+  (unless git-status (error \"Not in git-status buffer.\"))\n+  (or (ewoc-collect git-status (lambda (info) (git-fileinfo->marked info)))\n+      (list (ewoc-data (ewoc-locate git-status)))))\n+\n+(defun git-marked-files-state (&rest states)\n+  \"Return marked files that are in the specified states.\"\n+  (let ((files (git-marked-files))\n+        result)\n+    (dolist (info files)\n+      (when (memq (git-fileinfo->state info) states)\n+        (push info result)))\n+    result))\n+\n+(defun git-refresh-files ()\n+  \"Refresh all files that need it and clear the needs-refresh flag.\"\n+  (unless git-status (error \"Not in git-status buffer.\"))\n+  (ewoc-map\n+   (lambda (info)\n+     (let ((refresh (git-fileinfo->needs-refresh info)))\n+       (setf (git-fileinfo->needs-refresh info) nil)\n+       refresh))\n+   git-status)\n+  ; move back to goal column\n+  (when goal-column (move-to-column goal-column)))\n+\n+(defun git-refresh-ewoc-hf (status)\n+  \"Refresh the ewoc header and footer.\"\n+  (let ((branch (git-symbolic-ref \"HEAD\"))\n+        (head (if (git-empty-db-p) \"Nothing committed yet\"\n+                (substring (git-rev-parse \"HEAD\") 0 10)))\n+        (merge-heads (git-get-merge-heads)))\n+    (ewoc-set-hf status\n+                 (format \"Directory:  %s\\nBranch:     %s\\nHead:       %s%s\\n\"\n+                         default-directory\n+                         (if (string-match \"^refs/heads/\" branch)\n+                             (substring branch (match-end 0))\n+                           branch)\n+                         head\n+                         (if merge-heads\n+                             (concat \"\\nMerging:    \"\n+                                     (mapconcat (lambda (str) (substring str 0 10)) merge-heads \" \"))\n+                           \"\"))\n+                 (if (ewoc-nth status 0) \"\" \"    No changes.\"))))\n+\n+(defun git-get-filenames (files)\n+  (mapcar (lambda (info) (git-fileinfo->name info)) files))\n+\n+(defun git-update-index (index-file files)\n+  \"Run git-update-index on a list of files.\"\n+  (let ((env (and index-file `((\"GIT_INDEX_FILE\" . ,index-file))))\n+        added deleted modified)\n+    (dolist (info files)\n+      (case (git-fileinfo->state info)\n+        ('added (push info added))\n+        ('deleted (push info deleted))\n+        ('modified (push info modified))))\n+    (when added\n+      (apply #'git-run-command nil env \"update-index\" \"--add\" \"--\" (git-get-filenames added)))\n+    (when deleted\n+      (apply #'git-run-command nil env \"update-index\" \"--remove\" \"--\" (git-get-filenames deleted)))\n+    (when modified\n+      (apply #'git-run-command nil env \"update-index\" \"--\" (git-get-filenames modified)))))\n+\n+(defun git-do-commit ()\n+  \"Perform the actual commit using the current buffer as log message.\"\n+  (interactive)\n+  (let ((buffer (current-buffer))\n+        (index-file (make-temp-file \"gitidx\")))\n+    (with-current-buffer log-edit-parent-buffer\n+      (if (git-marked-files-state 'unmerged)\n+          (message \"You cannot commit unmerged files, resolve them first.\")\n+        (unwind-protect\n+            (let ((files (git-marked-files-state 'added 'deleted 'modified))\n+                  head head-tree)\n+              (unless (git-empty-db-p)\n+                (setq head (git-rev-parse \"HEAD\")\n+                      head-tree (git-rev-parse \"HEAD^{tree}\")))\n+              (if files\n+                  (progn\n+                    (git-read-tree head-tree index-file)\n+                    (git-update-index nil files)         ;update both the default index\n+                    (git-update-index index-file files)  ;and the temporary one\n+                    (let ((tree (git-write-tree index-file)))\n+                      (if (or (not (string-equal tree head-tree))\n+                              (yes-or-no-p \"The tree was not modified, do you really want to perform an empty commit? \"))\n+                          (let ((commit (git-commit-tree buffer tree head)))\n+                            (git-update-ref \"HEAD\" commit head)\n+                            (condition-case nil (delete-file \".git/MERGE_HEAD\") (error nil))\n+                            (with-current-buffer buffer (erase-buffer))\n+                            (git-set-files-state files 'uptodate)\n+                            (git-refresh-files)\n+                            (git-refresh-ewoc-hf git-status)\n+                            (message \"Committed %s.\" commit))\n+                        (message \"Commit aborted.\"))))\n+                (message \"No files to commit.\")))\n+          (delete-file index-file))))))\n+\n+\n+;;;; Interactive functions\n+;;;; ------------------------------------------------------------\n+\n+(defun git-mark-file ()\n+  \"Mark the file that the cursor is on and move to the next one.\"\n+  (interactive)\n+  (unless git-status (error \"Not in git-status buffer.\"))\n+  (let* ((pos (ewoc-locate git-status))\n+         (info (ewoc-data pos)))\n+    (setf (git-fileinfo->marked info) t)\n+    (ewoc-invalidate git-status pos)\n+    (ewoc-goto-next git-status 1)))\n+\n+(defun git-unmark-file ()\n+  \"Unmark the file that the cursor is on and move to the next one.\"\n+  (interactive)\n+  (unless git-status (error \"Not in git-status buffer.\"))\n+  (let* ((pos (ewoc-locate git-status))\n+         (info (ewoc-data pos)))\n+    (setf (git-fileinfo->marked info) nil)\n+    (ewoc-invalidate git-status pos)\n+    (ewoc-goto-next git-status 1)))\n+\n+(defun git-unmark-file-up ()\n+  \"Unmark the file that the cursor is on and move to the previous one.\"\n+  (interactive)\n+  (unless git-status (error \"Not in git-status buffer.\"))\n+  (let* ((pos (ewoc-locate git-status))\n+         (info (ewoc-data pos)))\n+    (setf (git-fileinfo->marked info) nil)\n+    (ewoc-invalidate git-status pos)\n+    (ewoc-goto-prev git-status 1)))\n+\n+(defun git-mark-all ()\n+  \"Mark all files.\"\n+  (interactive)\n+  (unless git-status (error \"Not in git-status buffer.\"))\n+  (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) t) t) git-status)\n+  ; move back to goal column after invalidate\n+  (when goal-column (move-to-column goal-column)))\n+\n+(defun git-unmark-all ()\n+  \"Unmark all files.\"\n+  (interactive)\n+  (unless git-status (error \"Not in git-status buffer.\"))\n+  (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) nil) t) git-status)\n+  ; move back to goal column after invalidate\n+  (when goal-column (move-to-column goal-column)))\n+\n+(defun git-toggle-all-marks ()\n+  \"Toggle all file marks.\"\n+  (interactive)\n+  (unless git-status (error \"Not in git-status buffer.\"))\n+  (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) (not (git-fileinfo->marked info))) t) git-status)\n+  ; move back to goal column after invalidate\n+  (when goal-column (move-to-column goal-column)))\n+\n+(defun git-next-file (&optional n)\n+  \"Move the selection down N files.\"\n+  (interactive \"p\")\n+  (unless git-status (error \"Not in git-status buffer.\"))\n+  (ewoc-goto-next git-status n))\n+\n+(defun git-prev-file (&optional n)\n+  \"Move the selection up N files.\"\n+  (interactive \"p\")\n+  (unless git-status (error \"Not in git-status buffer.\"))\n+  (ewoc-goto-prev git-status n))\n+\n+(defun git-add-file ()\n+  \"Add marked file(s) to the index cache.\"\n+  (interactive)\n+  (let ((files (git-marked-files-state 'unknown)))\n+    (unless files\n+      (push (ewoc-data\n+             (git-add-status-file 'added (file-relative-name\n+                                          (read-file-name \"File to add: \" nil nil t))))\n+            files))\n+    (apply #'git-run-command nil nil \"update-index\" \"--info-only\" \"--add\" \"--\" (git-get-filenames files))\n+    (git-set-files-state files 'added)\n+    (git-refresh-files)))\n+\n+(defun git-ignore-file ()\n+  \"Add marked file(s) to the ignore list.\"\n+  (interactive)\n+  (let ((files (git-marked-files-state 'unknown)))\n+    (unless files\n+      (push (ewoc-data\n+             (git-add-status-file 'unknown (file-relative-name\n+                                            (read-file-name \"File to ignore: \" nil nil t))))\n+            files))\n+    (dolist (info files) (git-append-to-ignore (git-fileinfo->name info)))\n+    (git-set-files-state files 'ignored)\n+    (git-refresh-files)))\n+\n+(defun git-remove-file ()\n+  \"Remove the marked file(s).\"\n+  (interactive)\n+  (let ((files (git-marked-files-state 'added 'modified 'unknown 'uptodate)))\n+    (unless files\n+      (push (ewoc-data\n+             (git-add-status-file 'unknown (file-relative-name\n+                                            (read-file-name \"File to remove: \" nil nil t))))\n+            files))\n+    (if (yes-or-no-p\n+         (format \"Remove %d file%s? \" (length files) (if (> (length files) 1) \"s\" \"\")))\n+        (progn\n+          (dolist (info files)\n+            (let ((name (git-fileinfo->name info)))\n+              (when (file-exists-p name) (delete-file name))))\n+          (apply #'git-run-command nil nil \"update-index\" \"--info-only\" \"--remove\" \"--\" (git-get-filenames files))\n+          ; remove unknown files from the list, set the others to deleted\n+          (ewoc-filter git-status\n+                       (lambda (info files)\n+                         (not (and (memq info files) (eq (git-fileinfo->state info) 'unknown))))\n+                       files)\n+          (git-set-files-state files 'deleted)\n+          (git-refresh-files)\n+          (unless (ewoc-nth git-status 0)  ; refresh header if list is empty\n+            (git-refresh-ewoc-hf git-status)))\n+      (message \"Aborting\"))))\n+\n+(defun git-revert-file ()\n+  \"Revert changes to the marked file(s).\"\n+  (interactive)\n+  (let ((files (git-marked-files))\n+        added modified)\n+    (when (and files\n+               (yes-or-no-p\n+                (format \"Revert %d file%s? \" (length files) (if (> (length files) 1) \"s\" \"\"))))\n+      (dolist (info files)\n+        (case (git-fileinfo->state info)\n+          ('added (push info added))\n+          ('deleted (push info modified))\n+          ('unmerged (push info modified))\n+          ('modified (push info modified))))\n+      (when added\n+          (apply #'git-run-command nil nil \"update-index\" \"--force-remove\" \"--\" (git-get-filenames added))\n+          (git-set-files-state added 'unknown))\n+      (when modified\n+          (apply #'git-run-command nil nil \"checkout\" \"HEAD\" (git-get-filenames modified))\n+          (git-set-files-state modified 'uptodate))\n+      (git-refresh-files))))\n+\n+(defun git-resolve-file ()\n+  \"Resolve conflicts in marked file(s).\"\n+  (interactive)\n+  (let ((files (git-marked-files-state 'unmerged)))\n+    (when files\n+      (apply #'git-run-command nil nil \"update-index\" \"--info-only\" \"--\" (git-get-filenames files))\n+      (git-set-files-state files 'modified)\n+      (git-refresh-files))))\n+\n+(defun git-remove-handled ()\n+  \"Remove handled files from the status list.\"\n+  (interactive)\n+  (ewoc-filter git-status\n+               (lambda (info)\n+                 (not (or (eq (git-fileinfo->state info) 'ignored)\n+                          (eq (git-fileinfo->state info) 'uptodate)))))\n+  (unless (ewoc-nth git-status 0)  ; refresh header if list is empty\n+    (git-refresh-ewoc-hf git-status)))\n+\n+(defun git-setup-diff-buffer (buffer)\n+  \"Setup a buffer for displaying a diff.\"\n+  (with-current-buffer buffer\n+    (diff-mode)\n+    (goto-char (point-min))\n+    (setq buffer-read-only t))\n+  (display-buffer buffer)\n+  (shrink-window-if-larger-than-buffer))\n+\n+(defun git-diff-file ()\n+  \"Diff the marked file(s) against HEAD.\"\n+  (interactive)\n+  (let ((files (git-marked-files)))\n+    (git-setup-diff-buffer\n+     (apply #'git-run-command-buffer \"*git-diff*\" \"diff-index\" \"-p\" \"-M\" \"HEAD\" \"--\" (git-get-filenames files)))))\n+\n+(defun git-diff-unmerged-file (stage)\n+  \"Diff the marked unmerged file(s) against the specified stage.\"\n+  (let ((files (git-marked-files)))\n+    (git-setup-diff-buffer\n+     (apply #'git-run-command-buffer \"*git-diff*\" \"diff-files\" \"-p\" stage \"--\" (git-get-filenames files)))))\n+\n+(defun git-diff-file-base ()\n+  \"Diff the marked unmerged file(s) against the common base file.\"\n+  (interactive)\n+  (git-diff-unmerged-file \"-1\"))\n+\n+(defun git-diff-file-mine ()\n+  \"Diff the marked unmerged file(s) against my pre-merge version.\"\n+  (interactive)\n+  (git-diff-unmerged-file \"-2\"))\n+\n+(defun git-diff-file-other ()\n+  \"Diff the marked unmerged file(s) against the other's pre-merge version.\"\n+  (interactive)\n+  (git-diff-unmerged-file \"-3\"))\n+\n+(defun git-diff-file-combined ()\n+  \"Do a combined diff of the marked unmerged file(s).\"\n+  (interactive)\n+  (git-diff-unmerged-file \"-c\"))\n+\n+(defun git-diff-file-idiff ()\n+  \"Perform an interactive diff on the current file.\"\n+  (interactive)\n+  (error \"Interactive diffs not implemented yet.\"))\n+\n+(defun git-log-file ()\n+  \"Display a log of changes to the marked file(s).\"\n+  (interactive)\n+  (let* ((files (git-marked-files))\n+         (coding-system-for-read git-commits-coding-system)\n+         (buffer (apply #'git-run-command-buffer \"*git-log*\" \"rev-list\" \"--pretty\" \"HEAD\" \"--\" (git-get-filenames files))))\n+    (with-current-buffer buffer\n+      ; (git-log-mode)  FIXME: implement log mode\n+      (goto-char (point-min))\n+      (setq buffer-read-only t))\n+    (display-buffer buffer)))\n+\n+(defun git-log-edit-files ()\n+  \"Return a list of marked files for use in the log-edit buffer.\"\n+  (with-current-buffer log-edit-parent-buffer\n+    (git-get-filenames (git-marked-files-state 'added 'deleted 'modified))))\n+\n+(defun git-commit-file ()\n+  \"Commit the marked file(s), asking for a commit message.\"\n+  (interactive)\n+  (unless git-status (error \"Not in git-status buffer.\"))\n+  (let ((buffer (get-buffer-create \"*git-commit*\"))\n+        (merge-heads (git-get-merge-heads))\n+        (dir default-directory))\n+    (with-current-buffer buffer\n+      (when (eq 0 (buffer-size))\n+        (cd dir)\n+        (erase-buffer)\n+        (insert\n+         (propertize\n+          (format \"Author: %s <%s>\\n%s\"\n+                  (git-get-committer-name) (git-get-committer-email)\n+                  (if merge-heads\n+                      (format \"Parent: %s\\n%s\\n\"\n+                              (git-rev-parse \"HEAD\")\n+                              (mapconcat (lambda (str) (concat \"Parent: \" str)) merge-heads \"\\n\"))\n+                    \"\"))\n+          'face 'git-header-face)\n+         (propertize git-log-msg-separator 'face 'git-separator-face)\n+         \"\\n\")\n+        (when (and merge-heads (file-readable-p \".git/MERGE_MSG\"))\n+          (insert-file-contents \".git/MERGE_MSG\"))))\n+      (log-edit #'git-do-commit nil #'git-log-edit-files buffer)))\n+\n+(defun git-find-file ()\n+  \"Visit the current file in its own buffer.\"\n+  (interactive)\n+  (unless git-status (error \"Not in git-status buffer.\"))\n+  (let ((info (ewoc-data (ewoc-locate git-status))))\n+    (find-file (git-fileinfo->name info))\n+    (when (eq 'unmerged (git-fileinfo->state info))\n+      (smerge-mode))))\n+\n+(defun git-find-file-imerge ()\n+  \"Visit the current file in interactive merge mode.\"\n+  (interactive)\n+  (unless git-status (error \"Not in git-status buffer.\"))\n+  (let ((info (ewoc-data (ewoc-locate git-status))))\n+    (find-file (git-fileinfo->name info))\n+    (smerge-ediff)))\n+\n+(defun git-view-file ()\n+  \"View the current file in its own buffer.\"\n+  (interactive)\n+  (unless git-status (error \"Not in git-status buffer.\"))\n+  (let ((info (ewoc-data (ewoc-locate git-status))))\n+    (view-file (git-fileinfo->name info))))\n+\n+(defun git-refresh-status ()\n+  \"Refresh the git status buffer.\"\n+  (interactive)\n+  (let* ((status git-status)\n+         (pos (ewoc-locate status))\n+         (cur-name (and pos (git-fileinfo->name (ewoc-data pos)))))\n+    (unless status (error \"Not in git-status buffer.\"))\n+    (git-clear-status status)\n+    (git-run-command nil nil \"update-index\" \"--info-only\" \"--refresh\")\n+    (if (git-empty-db-p)\n+        ; we need some special handling for an empty db\n+        (with-temp-buffer\n+          (git-run-command t nil \"ls-files\" \"-z\" \"-t\" \"-c\")\n+          (git-parse-ls-files status 'added))\n+      (with-temp-buffer\n+        (git-run-command t nil \"diff-index\" \"-z\" \"-M\" \"HEAD\")\n+        (git-parse-status status)))\n+      (with-temp-buffer\n+        (git-run-command t nil \"ls-files\" \"-z\" \"-u\")\n+        (git-parse-ls-unmerged status))\n+      (when (file-readable-p \".git/info/exclude\")\n+        (with-temp-buffer\n+          (git-run-command t nil \"ls-files\" \"-z\" \"-t\" \"-o\"\n+                           \"--exclude-from=.git/info/exclude\"\n+                           (concat \"--exclude-per-directory=\" git-per-dir-ignore-file))\n+          (git-parse-ls-files status 'unknown)))\n+    (git-refresh-files)\n+    (git-refresh-ewoc-hf status)\n+    ; move point to the current file name if any\n+    (let ((node (and cur-name (git-find-status-file status cur-name))))\n+      (when node (ewoc-goto-node status node)))))\n+\n+(defun git-status-quit ()\n+  \"Quit git-status mode.\"\n+  (interactive)\n+  (bury-buffer))\n+\n+;;;; Major Mode\n+;;;; ------------------------------------------------------------\n+\n+(defvar git-status-mode-hook nil\n+  \"Run after `git-status-mode' is setup.\")\n+\n+(defvar git-status-mode-map nil\n+  \"Keymap for git major mode.\")\n+\n+(defvar git-status nil\n+  \"List of all files managed by the git-status mode.\")\n+\n+(unless git-status-mode-map\n+  (let ((map (make-keymap))\n+        (diff-map (make-sparse-keymap)))\n+    (suppress-keymap map)\n+    (define-key map \" \"   'git-next-file)\n+    (define-key map \"a\"   'git-add-file)\n+    (define-key map \"c\"   'git-commit-file)\n+    (define-key map \"d\"    diff-map)\n+    (define-key map \"=\"   'git-diff-file)\n+    (define-key map \"f\"   'git-find-file)\n+    (define-key map [RET] 'git-find-file)\n+    (define-key map \"g\"   'git-refresh-status)\n+    (define-key map \"i\"   'git-ignore-file)\n+    (define-key map \"l\"   'git-log-file)\n+    (define-key map \"m\"   'git-mark-file)\n+    (define-key map \"M\"   'git-mark-all)\n+    (define-key map \"n\"   'git-next-file)\n+    (define-key map \"p\"   'git-prev-file)\n+    (define-key map \"q\"   'git-status-quit)\n+    (define-key map \"r\"   'git-remove-file)\n+    (define-key map \"R\"   'git-resolve-file)\n+    (define-key map \"T\"   'git-toggle-all-marks)\n+    (define-key map \"u\"   'git-unmark-file)\n+    (define-key map \"U\"   'git-revert-file)\n+    (define-key map \"v\"   'git-view-file)\n+    (define-key map \"x\"   'git-remove-handled)\n+    (define-key map \"\\C-?\" 'git-unmark-file-up)\n+    (define-key map \"\\M-\\C-?\" 'git-unmark-all)\n+    ; the diff submap\n+    (define-key diff-map \"b\" 'git-diff-file-base)\n+    (define-key diff-map \"c\" 'git-diff-file-combined)\n+    (define-key diff-map \"=\" 'git-diff-file)\n+    (define-key diff-map \"e\" 'git-diff-file-idiff)\n+    (define-key diff-map \"E\" 'git-find-file-imerge)\n+    (define-key diff-map \"m\" 'git-diff-file-mine)\n+    (define-key diff-map \"o\" 'git-diff-file-other)\n+    (setq git-status-mode-map map)))\n+\n+;; git mode should only run in the *git status* buffer\n+(put 'git-status-mode 'mode-class 'special)\n+\n+(defun git-status-mode ()\n+  \"Major mode for interacting with Git.\n+Commands:\n+\\\\{git-status-mode-map}\"\n+  (kill-all-local-variables)\n+  (buffer-disable-undo)\n+  (setq mode-name \"git status\"\n+        major-mode 'git-status-mode\n+        goal-column 17\n+        buffer-read-only t)\n+  (use-local-map git-status-mode-map)\n+  (let ((buffer-read-only nil))\n+    (erase-buffer)\n+  (let ((status (ewoc-create 'git-fileinfo-prettyprint \"\" \"\")))\n+    (set (make-local-variable 'git-status) status))\n+  (run-hooks 'git-status-mode-hook)))\n+\n+(defun git-status (dir)\n+  \"Entry point into git-status mode.\"\n+  (interactive \"DSelect directory: \")\n+  (setq dir (git-get-top-dir dir))\n+  (if (file-directory-p (concat (file-name-as-directory dir) \".git\"))\n+      (let ((buffer (create-file-buffer (expand-file-name \"*git-status*\" dir))))\n+        (switch-to-buffer buffer)\n+        (git-status-mode)\n+        (cd dir)\n+        (git-refresh-status)\n+        (goto-char (point-min)))\n+    (message \"%s is not a git working tree.\" dir)))\n+\n+(provide 'git)\n+;;; git.el ends here\n-- \n1.2.1.gfd1fe\n\n-- \nAlexandre Julliard\njulliard@winehq.org\n"}]}