Remove logentry primitive from backend API.
[bpt/emacs.git] / lisp / vc-git.el
CommitLineData
fff4a046
DN
1;;; vc-git.el --- VC backend for the git version control system
2
409cc4a3 3;; Copyright (C) 2006, 2007, 2008 Free Software Foundation, Inc.
fff4a046 4
8b38ce20 5;; Author: Alexandre Julliard <julliard@winehq.org>
fff4a046
DN
6;; Keywords: tools
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software; you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
625f71cf 12;; the Free Software Foundation; either version 3, or (at your option)
fff4a046
DN
13;; any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs; see the file COPYING. If not, write to the
22;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23;; Boston, MA 02110-1301, USA.
24
25;;; Commentary:
26
27;; This file contains a VC backend for the git version control
28;; system.
29;;
30
31;;; Installation:
32
4211679b 33;; To install: put this file on the load-path and add Git to the list
fff4a046
DN
34;; of supported backends in `vc-handled-backends'; the following line,
35;; placed in your ~/.emacs, will accomplish this:
36;;
4211679b 37;; (add-to-list 'vc-handled-backends 'Git)
fff4a046
DN
38
39;;; Todo:
53cc90ab
DN
40;; - check if more functions could use vc-git-command instead
41;; of start-process.
fff4a046 42;; - changelog generation
53cc90ab
DN
43
44;; Implement the rest of the vc interface. See the comment at the
45;; beginning of vc.el. The current status is:
7546c767 46;; ("??" means: "figure out what to do about it")
fff4a046 47;;
53cc90ab
DN
48;; FUNCTION NAME STATUS
49;; BACKEND PROPERTIES
50;; * revision-granularity OK
51;; STATE-QUERYING FUNCTIONS
52;; * registered (file) OK
53;; * state (file) OK
108607bc 54;; - state-heuristic (file) NOT NEEDED
53cc90ab 55;; - dir-state (dir) OK
ac3f4c6f 56;; * working-revision (file) OK
108607bc 57;; - latest-on-branch-p (file) NOT NEEDED
70e2f6c7 58;; * checkout-model (files) OK
7546c767 59;; - workfile-unchanged-p (file) OK
6f222162 60;; - mode-line-string (file) OK
0a299408 61;; - prettify-state-info (file) OK
53cc90ab
DN
62;; STATE-CHANGING FUNCTIONS
63;; * create-repo () OK
9143abff 64;; * register (files &optional rev comment) OK
ac3f4c6f 65;; - init-revision (file) NOT NEEDED
53cc90ab 66;; - responsible-p (file) OK
108607bc
DN
67;; - could-register (file) NOT NEEDED, DEFAULT IS GOOD
68;; - receive-file (file rev) NOT NEEDED
d7009f45 69;; - unregister (file) OK
9143abff 70;; * checkin (files rev comment) OK
ac3f4c6f 71;; * find-revision (file rev buffer) OK
53cc90ab
DN
72;; * checkout (file &optional editable rev) OK
73;; * revert (file &optional contents-done) OK
108607bc
DN
74;; - rollback (files) COULD BE SUPPORTED
75;; - merge (file rev1 rev2) It would be possible to merge changes into
76;; a single file, but when committing they
77;; wouldn't be identified as a merge by git,
b747d346
DN
78;; so it's probably not a good idea.
79;; - merge-news (file) see `merge'
5b5afd50 80;; - steal-lock (file &optional revision) NOT NEEDED
53cc90ab 81;; HISTORY FUNCTIONS
9143abff 82;; * print-log (files &optional buffer) OK
53cc90ab 83;; - log-view-mode () OK
b16bd82d 84;; - show-log-entry (revision) OK
108607bc 85;; - wash-log (file) COULD BE SUPPORTED
53cc90ab 86;; - comment-history (file) ??
108607bc 87;; - update-changelog (files) COULD BE SUPPORTED
b747d346 88;; * diff (file &optional rev1 rev2 buffer) OK
c8149699 89;; - revision-completion-table (files) OK
53cc90ab
DN
90;; - annotate-command (file buf &optional rev) OK
91;; - annotate-time () OK
108607bc 92;; - annotate-current-time () NOT NEEDED
53cc90ab
DN
93;; - annotate-extract-revision-at-line () OK
94;; SNAPSHOT SYSTEM
b747d346 95;; - create-snapshot (dir name branchp) OK
53cc90ab 96;; - assign-name (file name) NOT NEEDED
40ed3f4f 97;; - retrieve-snapshot (dir name update) OK, needs to update buffers
53cc90ab 98;; MISCELLANEOUS
108607bc
DN
99;; - make-version-backups-p (file) NOT NEEDED
100;; - repository-hostname (dirname) NOT NEEDED
5b5afd50
ER
101;; - previous-revision (file rev) OK
102;; - next-revision (file rev) OK
108607bc
DN
103;; - check-headers () COULD BE SUPPORTED
104;; - clear-headers () NOT NEEDED
8b38ce20
DN
105;; - delete-file (file) OK
106;; - rename-file (old new) OK
108607bc
DN
107;; - find-file-hook () NOT NEEDED
108;; - find-file-not-found-hook () NOT NEEDED
fff4a046 109
c8149699
DN
110(eval-when-compile
111 (require 'cl)
112 (require 'vc)
113 (require 'grep))
fff4a046
DN
114
115(defvar git-commits-coding-system 'utf-8
116 "Default coding system for git commits.")
117
53cc90ab
DN
118;;; BACKEND PROPERTIES
119
70e2f6c7
ER
120(defun vc-git-revision-granularity () 'repository)
121(defun vc-git-checkout-model (files) 'implicit)
53cc90ab
DN
122
123;;; STATE-QUERYING FUNCTIONS
124
125;;;###autoload (defun vc-git-registered (file)
126;;;###autoload "Return non-nil if FILE is registered with git."
127;;;###autoload (if (vc-find-root file ".git") ; short cut
128;;;###autoload (progn
129;;;###autoload (load "vc-git")
130;;;###autoload (vc-git-registered file))))
131
fff4a046
DN
132(defun vc-git-registered (file)
133 "Check whether FILE is registered with git."
53cc90ab
DN
134 (when (vc-git-root file)
135 (with-temp-buffer
136 (let* ((dir (file-name-directory file))
137 (name (file-relative-name file dir)))
138 (and (ignore-errors
2aa0736a 139 (when dir (cd dir))
5fdbecd8 140 (vc-git--out-ok "ls-files" "-c" "-z" "--" name))
53cc90ab
DN
141 (let ((str (buffer-string)))
142 (and (> (length str) (length name))
2aa0736a
TTN
143 (string= (substring str 0 (1+ (length name)))
144 (concat name "\0")))))))))
108607bc 145
75cb52be
DN
146(defun vc-git--state-code (code)
147 "Convert from a string to a added/deleted/modified state."
148 (case (string-to-char code)
149 (?M 'edited)
150 (?A 'added)
151 (?D 'removed)
152 (?U 'edited) ;; FIXME
153 (?T 'edited))) ;; FIXME
154
fff4a046 155(defun vc-git-state (file)
53cc90ab 156 "Git-specific version of `vc-state'."
722f037f 157 ;; FIXME: This can't set 'ignored yet
3702367b
ER
158 (if (not (vc-git-registered file))
159 'unregistered
160 (vc-git--call nil "add" "--refresh" "--" (file-relative-name file))
161 (let ((diff (vc-git--run-command-string file "diff-index" "-z" "HEAD" "--")))
162 (if (and diff (string-match ":[0-7]\\{6\\} [0-7]\\{6\\} [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\([ADMUT]\\)\0[^\0]+\0"
163 diff))
164 (vc-git--state-code (match-string 1 diff))
165 (if (vc-git--empty-db-p) 'added 'up-to-date)))))
fff4a046 166
b936ef8c
DN
167(defun vc-git--ls-files-state (state &rest args)
168 "Set state to STATE on all files found with git-ls-files ARGS."
53cc90ab 169 (with-temp-buffer
b936ef8c 170 (apply 'vc-git-command (current-buffer) nil nil "ls-files" "-z" args)
53cc90ab 171 (goto-char (point-min))
b936ef8c
DN
172 (let ((start (point)))
173 (while (search-forward "\0" nil t)
174 (let ((file (expand-file-name
175 (buffer-substring-no-properties start (1- (point))))))
176 (vc-file-setprop file 'vc-backend (if state 'Git 'none))
177 (vc-file-setprop file 'vc-state state))
178 (setq start (point))))))
179
180(defun vc-git-dir-state (dir)
181 "Git-specific version of `dir-state'."
182 (vc-git--ls-files-state 'up-to-date "-c")
183 (vc-git--ls-files-state 'edited "-m")
184 (vc-git--ls-files-state 'removed "-d")
185 (vc-git--ls-files-state 'ignored "-o" "-i" "--exclude-standard")
186 (vc-git--ls-files-state nil "-o" "--exclude-standard"))
53cc90ab 187
ac3f4c6f
ER
188(defun vc-git-working-revision (file)
189 "Git-specific version of `vc-working-revision'."
fff4a046
DN
190 (let ((str (with-output-to-string
191 (with-current-buffer standard-output
5fdbecd8 192 (vc-git--out-ok "symbolic-ref" "HEAD")))))
fff4a046
DN
193 (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str)
194 (match-string 2 str)
195 str)))
196
fff4a046 197(defun vc-git-workfile-unchanged-p (file)
00d67080 198 (eq 'up-to-date (vc-git-state file)))
fff4a046 199
6f222162
DN
200(defun vc-git-mode-line-string (file)
201 "Return string for placement into the modeline for FILE."
ac3f4c6f 202 (let* ((branch (vc-git-working-revision file))
6f222162
DN
203 (def-ml (vc-default-mode-line-string 'Git file))
204 (help-echo (get-text-property 0 'help-echo def-ml)))
205 (if (zerop (length branch))
206 (propertize
207 (concat def-ml "!")
208 'help-echo (concat help-echo "\nNo current branch (detached HEAD)"))
209 (propertize def-ml
210 'help-echo (concat help-echo "\nCurrent branch: " branch)))))
211
236b5827
DN
212(defstruct (vc-git-extra-fileinfo
213 (:copier nil)
214 (:constructor vc-git-create-extra-fileinfo (old-perm new-perm &optional rename-state orig-name))
215 (:conc-name vc-git-extra-fileinfo->))
216 old-perm new-perm ;; permission flags
217 rename-state ;; rename or copy state
218 orig-name) ;; original name for renames or copies
219
220(defun vc-git-escape-file-name (name)
221 "Escape a file name if necessary."
222 (if (string-match "[\n\t\"\\]" name)
223 (concat "\""
224 (mapconcat (lambda (c)
225 (case c
226 (?\n "\\n")
227 (?\t "\\t")
228 (?\\ "\\\\")
229 (?\" "\\\"")
230 (t (char-to-string c))))
231 name "")
232 "\"")
233 name))
234
235(defun vc-git-file-type-as-string (old-perm new-perm)
236 "Return a string describing the file type based on its permissions."
237 (let* ((old-type (lsh (or old-perm 0) -9))
238 (new-type (lsh (or new-perm 0) -9))
239 (str (case new-type
240 (?\100 ;; file
241 (case old-type
242 (?\100 nil)
243 (?\120 " (type change symlink -> file)")
244 (?\160 " (type change subproject -> file)")))
245 (?\120 ;; symlink
246 (case old-type
247 (?\100 " (type change file -> symlink)")
248 (?\160 " (type change subproject -> symlink)")
249 (t " (symlink)")))
250 (?\160 ;; subproject
251 (case old-type
252 (?\100 " (type change file -> subproject)")
253 (?\120 " (type change symlink -> subproject)")
254 (t " (subproject)")))
255 (?\110 nil) ;; directory (internal, not a real git state)
256 (?\000 ;; deleted or unknown
257 (case old-type
258 (?\120 " (symlink)")
259 (?\160 " (subproject)")))
260 (t (format " (unknown type %o)" new-type)))))
261 (cond (str (propertize str 'face 'font-lock-comment-face))
262 ((eq new-type ?\110) "/")
263 (t ""))))
264
265(defun vc-git-rename-as-string (state extra)
266 "Return a string describing the copy or rename associated with INFO, or an empty string if none."
267 (let ((rename-state (when extra
268 (vc-git-extra-fileinfo->rename-state extra))))
269 (if rename-state
270 (propertize
271 (concat " ("
272 (if (eq rename-state 'copy) "copied from "
273 (if (eq state 'added) "renamed from "
274 "renamed to "))
275 (vc-git-escape-file-name (vc-git-extra-fileinfo->orig-name extra))
276 ")") 'face 'font-lock-comment-face)
277 "")))
278
279(defun vc-git-permissions-as-string (old-perm new-perm)
280 "Format a permission change as string."
281 (propertize
282 (if (or (not old-perm)
283 (not new-perm)
284 (eq 0 (logand ?\111 (logxor old-perm new-perm))))
285 " "
286 (if (eq 0 (logand ?\111 old-perm)) "+x" "-x"))
287 'face 'font-lock-type-face))
288
289(defun vc-git-status-printer (info)
99e1b0c0 290 "Pretty-printer for the vc-dir-fileinfo structure."
c8149699
DN
291 (let* ((state (if (vc-dir-fileinfo->directory info)
292 'DIRECTORY
293 (vc-dir-fileinfo->state info)))
99e1b0c0 294 (extra (vc-dir-fileinfo->extra info))
236b5827
DN
295 (old-perm (when extra (vc-git-extra-fileinfo->old-perm extra)))
296 (new-perm (when extra (vc-git-extra-fileinfo->new-perm extra))))
297 (insert
298 " "
99e1b0c0 299 (propertize (format "%c" (if (vc-dir-fileinfo->marked info) ?* ? ))
236b5827
DN
300 'face 'font-lock-type-face)
301 " "
302 (propertize
303 (format "%-12s" state)
304 'face (cond ((eq state 'up-to-date) 'font-lock-builtin-face)
305 ((eq state 'missing) 'font-lock-warning-face)
306 (t 'font-lock-variable-name-face))
307 'mouse-face 'highlight)
308 " " (vc-git-permissions-as-string old-perm new-perm)
309 " "
99e1b0c0 310 (propertize (vc-git-escape-file-name (vc-dir-fileinfo->name info))
236b5827
DN
311 'face 'font-lock-function-name-face
312 'mouse-face 'highlight)
313 (vc-git-file-type-as-string old-perm new-perm)
314 (vc-git-rename-as-string state extra))))
315
d41080ca
AJ
316(defun vc-git-after-dir-status-stage (stage files update-function)
317 "Process sentinel for the various dir-status stages."
318 (let (remaining next-stage result)
319 (goto-char (point-min))
320 (case stage
321 ('update-index
322 (setq next-stage (if (vc-git--empty-db-p) 'ls-files-added
323 (if files 'ls-files-up-to-date 'diff-index))))
324 ('ls-files-added
325 (setq next-stage 'ls-files-unknown)
326 (while (re-search-forward "\\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} 0\t\\([^\0]+\\)\0" nil t)
327 (let ((new-perm (string-to-number (match-string 1) 8))
328 (name (match-string 2)))
329 (push (list name 'added (vc-git-create-extra-fileinfo 0 new-perm)) result))))
330 ('ls-files-up-to-date
331 (setq next-stage 'diff-index)
332 (while (re-search-forward "\\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} 0\t\\([^\0]+\\)\0" nil t)
333 (let ((perm (string-to-number (match-string 1) 8))
334 (name (match-string 2)))
335 (push (list name 'up-to-date (vc-git-create-extra-fileinfo perm perm)) result))))
336 ('ls-files-unknown
337 (when files (setq next-stage 'ls-files-ignored))
338 (while (re-search-forward "\\([^\0]*?\\)\0" nil t 1)
339 (push (list (match-string 1) 'unregistered (vc-git-create-extra-fileinfo 0 0)) result)))
340 ('ls-files-ignored
341 (while (re-search-forward "\\([^\0]*?\\)\0" nil t 1)
342 (push (list (match-string 1) 'ignored (vc-git-create-extra-fileinfo 0 0)) result)))
343 ('diff-index
344 (setq next-stage 'ls-files-unknown)
345 (while (re-search-forward
346 ":\\([0-7]\\{6\\}\\) \\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\(\\([ADMUT]\\)\0\\([^\0]+\\)\\|\\([CR]\\)[0-9]*\0\\([^\0]+\\)\0\\([^\0]+\\)\\)\0"
347 nil t 1)
348 (let ((old-perm (string-to-number (match-string 1) 8))
349 (new-perm (string-to-number (match-string 2) 8))
350 (state (or (match-string 4) (match-string 6)))
351 (name (or (match-string 5) (match-string 7)))
352 (new-name (match-string 8)))
353 (if new-name ; copy or rename
354 (if (eq ?C (string-to-char state))
355 (push (list new-name 'added (vc-git-create-extra-fileinfo old-perm new-perm 'copy name)) result)
356 (push (list name 'removed (vc-git-create-extra-fileinfo 0 0 'rename new-name)) result)
357 (push (list new-name 'added (vc-git-create-extra-fileinfo old-perm new-perm 'rename name)) result))
358 (push (list name (vc-git--state-code state) (vc-git-create-extra-fileinfo old-perm new-perm)) result))))))
359 (when result
360 (setq result (nreverse result))
361 (when files
362 (dolist (entry result) (setq files (delete (car entry) files)))
363 (unless files (setq next-stage nil))))
364 (when (or result (not next-stage)) (funcall update-function result next-stage))
365 (when next-stage (vc-git-dir-status-goto-stage next-stage files update-function))))
366
367(defun vc-git-dir-status-goto-stage (stage files update-function)
8e4e4aef 368 (erase-buffer)
d41080ca
AJ
369 (case stage
370 ('update-index
371 (if files
372 (vc-git-command (current-buffer) 'async files "add" "--refresh" "--")
373 (vc-git-command (current-buffer) 'async nil "update-index" "--refresh")))
374 ('ls-files-added
375 (vc-git-command (current-buffer) 'async files "ls-files" "-z" "-c" "-s" "--"))
376 ('ls-files-up-to-date
377 (vc-git-command (current-buffer) 'async files "ls-files" "-z" "-c" "-s" "--"))
378 ('ls-files-unknown
379 (vc-git-command (current-buffer) 'async files "ls-files" "-z" "-o"
380 "--directory" "--no-empty-directory" "--exclude-standard" "--"))
381 ('ls-files-ignored
382 (vc-git-command (current-buffer) 'async files "ls-files" "-z" "-o" "-i"
383 "--directory" "--no-empty-directory" "--exclude-standard" "--"))
384 ('diff-index
385 (vc-git-command (current-buffer) 'async files "diff-index" "-z" "-M" "HEAD" "--")))
8e4e4aef 386 (vc-exec-after
d41080ca 387 `(vc-git-after-dir-status-stage (quote ,stage) (quote ,files) (quote ,update-function))))
8e4e4aef 388
c1b51374 389(defun vc-git-dir-status (dir update-function)
d41080ca 390 "Return a list of (FILE STATE EXTRA) entries for DIR."
12cb746e 391 ;; Further things that would have to be fixed later:
12cb746e 392 ;; - how to handle unregistered directories
99e1b0c0 393 ;; - how to support vc-dir on a subdir of the project tree
d41080ca
AJ
394 (vc-git-dir-status-goto-stage 'update-index nil update-function))
395
396(defun vc-git-dir-status-files (dir files default-state update-function)
397 "Return a list of (FILE STATE EXTRA) entries for FILES in DIR."
398 (vc-git-dir-status-goto-stage 'update-index files update-function))
758dc0cc 399
15c5c970
DN
400(defun vc-git-status-extra-headers (dir)
401 (let ((str (with-output-to-string
402 (with-current-buffer standard-output
403 (vc-git--out-ok "symbolic-ref" "HEAD")))))
404 (concat
405 (propertize "Branch : " 'face 'font-lock-type-face)
406 (propertize
407 (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str)
408 (match-string 2 str)
409 "not (detached HEAD)")
410 'face 'font-lock-variable-name-face))))
411
53cc90ab
DN
412;;; STATE-CHANGING FUNCTIONS
413
414(defun vc-git-create-repo ()
4211679b 415 "Create a new Git repository."
00d67080 416 (vc-git-command nil 0 nil "init"))
53cc90ab 417
8b9783e0 418(defun vc-git-register (files &optional rev comment)
fff4a046 419 "Register FILE into the git version-control system."
8b9783e0 420 (vc-git-command nil 0 files "update-index" "--add" "--"))
fff4a046 421
53cc90ab
DN
422(defalias 'vc-git-responsible-p 'vc-git-root)
423
d7009f45
DN
424(defun vc-git-unregister (file)
425 (vc-git-command nil 0 file "rm" "-f" "--cached" "--"))
108607bc 426
d7009f45 427
8b9783e0 428(defun vc-git-checkin (files rev comment)
fff4a046 429 (let ((coding-system-for-write git-commits-coding-system))
8b9783e0 430 (vc-git-command nil 0 files "commit" "-m" comment "--only" "--")))
fff4a046 431
ac3f4c6f 432(defun vc-git-find-revision (file rev buffer)
b0f90937 433 (let ((coding-system-for-read 'binary)
8b38ce20
DN
434 (coding-system-for-write 'binary)
435 (fullname (substring
108607bc 436 (vc-git--run-command-string
8b38ce20
DN
437 file "ls-files" "-z" "--full-name" "--")
438 0 -1)))
108607bc
DN
439 (vc-git-command
440 buffer 0
8b38ce20 441 (concat (if rev rev "HEAD") ":" fullname) "cat-file" "blob")))
b0f90937
DN
442
443(defun vc-git-checkout (file &optional editable rev)
7546c767 444 (vc-git-command nil 0 file "checkout" (or rev "HEAD")))
fff4a046
DN
445
446(defun vc-git-revert (file &optional contents-done)
447 "Revert FILE to the version stored in the git repository."
448 (if contents-done
b0f90937
DN
449 (vc-git-command nil 0 file "update-index" "--")
450 (vc-git-command nil 0 file "checkout" "HEAD")))
fff4a046 451
53cc90ab
DN
452;;; HISTORY FUNCTIONS
453
8b9783e0
DN
454(defun vc-git-print-log (files &optional buffer)
455 "Get change log associated with FILES."
a0709d8d
DN
456 (let ((coding-system-for-read git-commits-coding-system)
457 ;; Support both the old print-log interface that passes a
458 ;; single file, and the new one that passes a file list.
459 (flist (if (listp files) files (list files))))
53cc90ab
DN
460 ;; `vc-do-command' creates the buffer, but we need it before running
461 ;; the command.
462 (vc-setup-buffer buffer)
463 ;; If the buffer exists from a previous invocation it might be
464 ;; read-only.
465 (let ((inhibit-read-only t))
64e3efd9
DN
466 ;; XXX `log-view-mode' needs to have something to identify where
467 ;; the log for each individual file starts. It seems that by
468 ;; default git does not output this info. So loop here and call
469 ;; "git rev-list" on each file separately to make sure that each
470 ;; file gets a "File:" header before the corresponding
471 ;; log. Maybe there is a way to do this with one command...
a0709d8d 472 (dolist (file flist)
64e3efd9
DN
473 (with-current-buffer
474 buffer
475 (insert "File: " (file-name-nondirectory file) "\n"))
108607bc 476 (vc-git-command buffer 'async (file-relative-name file)
64e3efd9 477 "rev-list" "--pretty" "HEAD" "--")))))
53cc90ab
DN
478
479(defvar log-view-message-re)
480(defvar log-view-file-re)
481(defvar log-view-font-lock-keywords)
482
4211679b 483(define-derived-mode vc-git-log-view-mode log-view-mode "Git-Log-View"
53cc90ab
DN
484 (require 'add-log) ;; we need the faces add-log
485 ;; Don't have file markers, so use impossible regexp.
486 (set (make-local-variable 'log-view-file-re) "^File:[ \t]+\\(.+\\)")
487 (set (make-local-variable 'log-view-message-re)
488 "^commit *\\([0-9a-z]+\\)")
489 (set (make-local-variable 'log-view-font-lock-keywords)
490 (append
2aa0736a
TTN
491 `((,log-view-message-re (1 'change-log-acknowledgement))
492 (,log-view-file-re (1 'change-log-file-face)))
493 ;; Handle the case:
494 ;; user: foo@bar
495 '(("^Author:[ \t]+\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)"
496 (1 'change-log-email))
497 ;; Handle the case:
498 ;; user: FirstName LastName <foo@bar>
499 ("^Author:[ \t]+\\([^<(]+?\\)[ \t]*[(<]\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)[>)]"
500 (1 'change-log-name)
501 (2 'change-log-email))
502 ("^ +\\(?:\\(?:[Aa]cked\\|[Ss]igned-[Oo]ff\\)-[Bb]y:\\)[ \t]+\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)"
503 (1 'change-log-name))
504 ("^ +\\(?:\\(?:[Aa]cked\\|[Ss]igned-[Oo]ff\\)-[Bb]y:\\)[ \t]+\\([^<(]+?\\)[ \t]*[(<]\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)[>)]"
505 (1 'change-log-name)
506 (2 'change-log-email))
507 ("^Merge: \\([0-9a-z]+\\) \\([0-9a-z]+\\)"
508 (1 'change-log-acknowledgement)
509 (2 'change-log-acknowledgement))
510 ("^Date: \\(.+\\)" (1 'change-log-date))
511 ("^summary:[ \t]+\\(.+\\)" (1 'log-view-message))))))
fff4a046 512
b16bd82d
TTN
513(defun vc-git-show-log-entry (revision)
514 "Move to the log entry for REVISION.
515REVISION may have the form BRANCH, BRANCH~N,
516or BRANCH^ (where \"^\" can be repeated)."
517 (goto-char (point-min))
518 (search-forward "\ncommit" nil t
519 (cond ((string-match "~\\([0-9]\\)$" revision)
520 (1+ (string-to-number (match-string 1 revision))))
521 ((string-match "\\^+$" revision)
522 (1+ (length (match-string 0 revision))))
523 (t nil)))
524 (beginning-of-line))
525
b747d346
DN
526(defun vc-git-diff (files &optional rev1 rev2 buffer)
527 (let ((buf (or buffer "*vc-diff*")))
fff4a046 528 (if (and rev1 rev2)
2aa0736a
TTN
529 (vc-git-command buf 1 files "diff-tree" "--exit-code" "-p"
530 rev1 rev2 "--")
531 (vc-git-command buf 1 files "diff-index" "--exit-code" "-p"
532 (or rev1 "HEAD") "--"))))
fff4a046 533
9f11ce4e
SM
534(defun vc-git-revision-table (files)
535 ;; What about `files'?!? --Stef
108607bc
DN
536 (let ((table (list "HEAD")))
537 (with-temp-buffer
538 (vc-git-command t nil nil "for-each-ref" "--format=%(refname)")
539 (goto-char (point-min))
540 (while (re-search-forward "^refs/\\(heads\\|tags\\)/\\(.*\\)$" nil t)
541 (push (match-string 2) table)))
542 table))
543
9f11ce4e
SM
544(defun vc-git-revision-completion-table (files)
545 (lexical-let ((files files)
108607bc
DN
546 table)
547 (setq table (lazy-completion-table
9f11ce4e 548 table (lambda () (vc-git-revision-table files))))
108607bc
DN
549 table))
550
fff4a046 551(defun vc-git-annotate-command (file buf &optional rev)
53cc90ab 552 ;; FIXME: rev is ignored
fff4a046 553 (let ((name (file-relative-name file)))
53cc90ab 554 (vc-git-command buf 0 name "blame" (if rev (concat "-r" rev)))))
fff4a046
DN
555
556(defun vc-git-annotate-time ()
0bcc6163 557 (and (re-search-forward "[0-9a-f]+[^()]+(.* \\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\) +[0-9]+) " nil t)
fff4a046 558 (vc-annotate-convert-time
2aa0736a
TTN
559 (apply #'encode-time (mapcar (lambda (match)
560 (string-to-number (match-string match)))
561 '(6 5 4 3 2 1 7))))))
fff4a046 562
53cc90ab 563(defun vc-git-annotate-extract-revision-at-line ()
2aa0736a
TTN
564 (save-excursion
565 (move-beginning-of-line 1)
dd3ffb9a 566 (and (looking-at "[0-9a-f^][0-9a-f]+")
2aa0736a 567 (buffer-substring-no-properties (match-beginning 0) (match-end 0)))))
53cc90ab 568
b747d346
DN
569;;; SNAPSHOT SYSTEM
570
571(defun vc-git-create-snapshot (dir name branchp)
572 (let ((default-directory dir))
573 (and (vc-git-command nil 0 nil "update-index" "--refresh")
574 (if branchp
575 (vc-git-command nil 0 nil "checkout" "-b" name)
576 (vc-git-command nil 0 nil "tag" name)))))
577
578(defun vc-git-retrieve-snapshot (dir name update)
579 (let ((default-directory dir))
580 (vc-git-command nil 0 nil "checkout" name)
581 ;; FIXME: update buffers if `update' is true
582 ))
583
584
53cc90ab 585;;; MISCELLANEOUS
fff4a046 586
5b5afd50
ER
587(defun vc-git-previous-revision (file rev)
588 "Git-specific version of `vc-previous-revision'."
fff4a046
DN
589 (let ((default-directory (file-name-directory (expand-file-name file)))
590 (file (file-name-nondirectory file)))
591 (vc-git-symbolic-commit
592 (with-temp-buffer
593 (and
5fdbecd8 594 (vc-git--out-ok "rev-list" "-2" rev "--" file)
fff4a046
DN
595 (goto-char (point-max))
596 (bolp)
597 (zerop (forward-line -1))
598 (not (bobp))
599 (buffer-substring-no-properties
2aa0736a
TTN
600 (point)
601 (1- (point-max))))))))
fff4a046 602
5b5afd50
ER
603(defun vc-git-next-revision (file rev)
604 "Git-specific version of `vc-next-revision'."
fff4a046
DN
605 (let* ((default-directory (file-name-directory
606 (expand-file-name file)))
2aa0736a
TTN
607 (file (file-name-nondirectory file))
608 (current-rev
609 (with-temp-buffer
610 (and
5fdbecd8 611 (vc-git--out-ok "rev-list" "-1" rev "--" file)
2aa0736a
TTN
612 (goto-char (point-max))
613 (bolp)
614 (zerop (forward-line -1))
615 (bobp)
616 (buffer-substring-no-properties
617 (point)
618 (1- (point-max)))))))
fff4a046
DN
619 (and current-rev
620 (vc-git-symbolic-commit
621 (with-temp-buffer
622 (and
5fdbecd8 623 (vc-git--out-ok "rev-list" "HEAD" "--" file)
fff4a046
DN
624 (goto-char (point-min))
625 (search-forward current-rev nil t)
626 (zerop (forward-line -1))
627 (buffer-substring-no-properties
628 (point)
629 (progn (forward-line 1) (1- (point))))))))))
630
8b38ce20
DN
631(defun vc-git-delete-file (file)
632 (vc-git-command nil 0 file "rm" "-f" "--"))
b0f90937 633
8b38ce20
DN
634(defun vc-git-rename-file (old new)
635 (vc-git-command nil 0 (list old new) "mv" "-f" "--"))
b0f90937 636
f0e1713e
DN
637(defvar vc-git-extra-menu-map
638 (let ((map (make-sparse-keymap)))
639 (define-key map [git-grep]
640 '(menu-item "Git grep..." vc-git-grep
641 :help "Run the `git grep' command"))
642 map))
643
644(defun vc-git-extra-menu () vc-git-extra-menu-map)
645
646(defun vc-git-extra-status-menu () vc-git-extra-menu-map)
647
648;; Derived from `lgrep'.
649(defun vc-git-grep (regexp &optional files dir)
650 "Run git grep, searching for REGEXP in FILES in directory DIR.
651The search is limited to file names matching shell pattern FILES.
652FILES may use abbreviations defined in `grep-files-aliases', e.g.
653entering `ch' is equivalent to `*.[ch]'.
654
655With \\[universal-argument] prefix, you can edit the constructed shell command line
656before it is executed.
657With two \\[universal-argument] prefixes, directly edit and run `grep-command'.
658
659Collect output in a buffer. While git grep runs asynchronously, you
660can use \\[next-error] (M-x next-error), or \\<grep-mode-map>\\[compile-goto-error] \
661in the grep output buffer,
662to go to the lines where grep found matches.
663
664This command shares argument histories with \\[rgrep] and \\[grep]."
665 (interactive
666 (progn
667 (grep-compute-defaults)
668 (cond
669 ((equal current-prefix-arg '(16))
670 (list (read-from-minibuffer "Run: " "git grep"
671 nil nil 'grep-history)
672 nil))
673 (t (let* ((regexp (grep-read-regexp))
674 (files (grep-read-files regexp))
675 (dir (read-directory-name "In directory: "
676 nil default-directory t)))
677 (list regexp files dir))))))
678 (require 'grep)
679 (when (and (stringp regexp) (> (length regexp) 0))
680 (let ((command regexp))
681 (if (null files)
682 (if (string= command "git grep")
683 (setq command nil))
684 (setq dir (file-name-as-directory (expand-file-name dir)))
685 (setq command
686 (grep-expand-template "git grep -n -e <R> -- <F>" regexp files))
687 (when command
688 (if (equal current-prefix-arg '(4))
689 (setq command
690 (read-from-minibuffer "Confirm: "
691 command nil nil 'grep-history))
692 (add-to-history 'grep-history command))))
693 (when command
694 (let ((default-directory dir)
695 (compilation-environment '("PAGER=")))
696 ;; Setting process-setup-function makes exit-message-function work
697 ;; even when async processes aren't supported.
698 (compilation-start command 'grep-mode))
699 (if (eq next-error-last-buffer (current-buffer))
700 (setq default-directory dir))))))
fff4a046 701\f
b747d346 702;;; Internal commands
fff4a046 703
53cc90ab
DN
704(defun vc-git-root (file)
705 (vc-find-root file ".git"))
706
8b9783e0 707(defun vc-git-command (buffer okstatus file-or-list &rest flags)
53cc90ab
DN
708 "A wrapper around `vc-do-command' for use in vc-git.el.
709The difference to vc-do-command is that this function always invokes `git'."
8b9783e0 710 (apply 'vc-do-command buffer okstatus "git" file-or-list flags))
53cc90ab 711
8e4e4aef
DN
712(defun vc-git--empty-db-p ()
713 "Check if the git db is empty (no commit done yet)."
714 (not (eq 0 (vc-git--call nil "rev-parse" "--verify" "HEAD"))))
715
5fdbecd8 716(defun vc-git--call (buffer command &rest args)
0664ff72
MA
717 ;; We don't need to care the arguments. If there is a file name, it
718 ;; is always a relative one. This works also for remote
719 ;; directories.
720 (apply 'process-file "git" nil buffer nil command args))
5fdbecd8
TTN
721
722(defun vc-git--out-ok (command &rest args)
723 (zerop (apply 'vc-git--call '(t nil) command args)))
724
fff4a046
DN
725(defun vc-git--run-command-string (file &rest args)
726 "Run a git command on FILE and return its output as string."
727 (let* ((ok t)
728 (str (with-output-to-string
729 (with-current-buffer standard-output
5fdbecd8
TTN
730 (unless (apply 'vc-git--out-ok
731 (append args (list (file-relative-name
732 file))))
fff4a046
DN
733 (setq ok nil))))))
734 (and ok str)))
735
fff4a046
DN
736(defun vc-git-symbolic-commit (commit)
737 "Translate COMMIT string into symbolic form.
738Returns nil if not possible."
739 (and commit
740 (with-temp-buffer
741 (and
5fdbecd8 742 (vc-git--out-ok "name-rev" "--name-only" "--tags" commit)
fff4a046
DN
743 (goto-char (point-min))
744 (= (forward-line 2) 1)
745 (bolp)
746 (buffer-substring-no-properties (point-min) (1- (point-max)))))))
747
748(provide 'vc-git)
53cc90ab 749
328471d9 750;; arch-tag: bd10664a-0e5b-48f5-a877-6c17b135be12
53cc90ab 751;;; vc-git.el ends here