* ido.el (ido-minibuffer-setup): Don't set cua-inhibit-cua-keys (Bug#5765).
[bpt/emacs.git] / lisp / vc-git.el
CommitLineData
fff4a046
DN
1;;; vc-git.el --- VC backend for the git version control system
2
114f9c96 3;; Copyright (C) 2006, 2007, 2008, 2009, 2010 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
eb3fa2cf 10;; GNU Emacs is free software: you can redistribute it and/or modify
fff4a046 11;; it under the terms of the GNU General Public License as published by
eb3fa2cf
GM
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
fff4a046
DN
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
eb3fa2cf 21;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
fff4a046
DN
22
23;;; Commentary:
24
25;; This file contains a VC backend for the git version control
26;; system.
27;;
28
29;;; Installation:
30
4211679b 31;; To install: put this file on the load-path and add Git to the list
fff4a046
DN
32;; of supported backends in `vc-handled-backends'; the following line,
33;; placed in your ~/.emacs, will accomplish this:
34;;
4211679b 35;; (add-to-list 'vc-handled-backends 'Git)
fff4a046
DN
36
37;;; Todo:
53cc90ab
DN
38;; - check if more functions could use vc-git-command instead
39;; of start-process.
fff4a046 40;; - changelog generation
53cc90ab
DN
41
42;; Implement the rest of the vc interface. See the comment at the
43;; beginning of vc.el. The current status is:
7546c767 44;; ("??" means: "figure out what to do about it")
fff4a046 45;;
b91f0762 46;; FUNCTION NAME STATUS
53cc90ab 47;; BACKEND PROPERTIES
b91f0762 48;; * revision-granularity OK
53cc90ab 49;; STATE-QUERYING FUNCTIONS
b91f0762
DN
50;; * registered (file) OK
51;; * state (file) OK
52;; - state-heuristic (file) NOT NEEDED
53;; * working-revision (file) OK
54;; - latest-on-branch-p (file) NOT NEEDED
55;; * checkout-model (files) OK
56;; - workfile-unchanged-p (file) OK
57;; - mode-line-string (file) OK
53cc90ab 58;; STATE-CHANGING FUNCTIONS
b91f0762
DN
59;; * create-repo () OK
60;; * register (files &optional rev comment) OK
61;; - init-revision (file) NOT NEEDED
62;; - responsible-p (file) OK
63;; - could-register (file) NOT NEEDED, DEFAULT IS GOOD
64;; - receive-file (file rev) NOT NEEDED
65;; - unregister (file) OK
66;; * checkin (files rev comment) OK
67;; * find-revision (file rev buffer) OK
68;; * checkout (file &optional editable rev) OK
69;; * revert (file &optional contents-done) OK
70;; - rollback (files) COULD BE SUPPORTED
370fded4 71;; - merge (file rev1 rev2) It would be possible to merge
4c83ed3d
SM
72;; changes into a single file, but
73;; when committing they wouldn't
370fded4
ER
74;; be identified as a merge
75;; by git, so it's probably
76;; not a good idea.
b91f0762
DN
77;; - merge-news (file) see `merge'
78;; - steal-lock (file &optional revision) NOT NEEDED
53cc90ab 79;; HISTORY FUNCTIONS
662c5698 80;; * print-log (files buffer &optional shortlog start-revision limit) OK
b91f0762
DN
81;; - log-view-mode () OK
82;; - show-log-entry (revision) OK
83;; - comment-history (file) ??
84;; - update-changelog (files) COULD BE SUPPORTED
85;; * diff (file &optional rev1 rev2 buffer) OK
86;; - revision-completion-table (files) OK
87;; - annotate-command (file buf &optional rev) OK
88;; - annotate-time () OK
89;; - annotate-current-time () NOT NEEDED
90;; - annotate-extract-revision-at-line () OK
370fded4 91;; TAG SYSTEM
b91f0762
DN
92;; - create-tag (dir name branchp) OK
93;; - retrieve-tag (dir name update) OK
53cc90ab 94;; MISCELLANEOUS
b91f0762
DN
95;; - make-version-backups-p (file) NOT NEEDED
96;; - repository-hostname (dirname) NOT NEEDED
97;; - previous-revision (file rev) OK
98;; - next-revision (file rev) OK
99;; - check-headers () COULD BE SUPPORTED
100;; - clear-headers () NOT NEEDED
101;; - delete-file (file) OK
102;; - rename-file (old new) OK
103;; - find-file-hook () NOT NEEDED
fff4a046 104
c8149699
DN
105(eval-when-compile
106 (require 'cl)
107 (require 'vc)
10c7e431 108 (require 'vc-dir)
c8149699 109 (require 'grep))
fff4a046 110
a0bea999 111(defcustom vc-git-diff-switches t
b6889b65
GM
112 "String or list of strings specifying switches for Git diff under VC.
113If nil, use the value of `vc-diff-switches'. If t, use no switches."
a0bea999
GM
114 :type '(choice (const :tag "Unspecified" nil)
115 (const :tag "None" t)
116 (string :tag "Argument String")
b6889b65 117 (repeat :tag "Argument List" :value ("") string))
a0bea999
GM
118 :version "23.1"
119 :group 'vc)
120
e97a42c1 121(defvar vc-git-commits-coding-system 'utf-8
fff4a046
DN
122 "Default coding system for git commits.")
123
53cc90ab
DN
124;;; BACKEND PROPERTIES
125
70e2f6c7
ER
126(defun vc-git-revision-granularity () 'repository)
127(defun vc-git-checkout-model (files) 'implicit)
53cc90ab
DN
128
129;;; STATE-QUERYING FUNCTIONS
130
131;;;###autoload (defun vc-git-registered (file)
132;;;###autoload "Return non-nil if FILE is registered with git."
4c83ed3d 133;;;###autoload (if (vc-find-root file ".git") ; Short cut.
53cc90ab
DN
134;;;###autoload (progn
135;;;###autoload (load "vc-git")
136;;;###autoload (vc-git-registered file))))
137
fff4a046
DN
138(defun vc-git-registered (file)
139 "Check whether FILE is registered with git."
379241fa
DN
140 (let ((dir (vc-git-root file)))
141 (when dir
142 (with-temp-buffer
143 (let* (process-file-side-effects
144 ;; Do not use the `file-name-directory' here: git-ls-files
145 ;; sometimes fails to return the correct status for relative
146 ;; path specs.
147 ;; See also: http://marc.info/?l=git&m=125787684318129&w=2
148 (name (file-relative-name file dir))
149 (str (ignore-errors
150 (cd dir)
151 (vc-git--out-ok "ls-files" "-c" "-z" "--" name)
4c83ed3d
SM
152 ;; If result is empty, use ls-tree to check for deleted
153 ;; file.
379241fa 154 (when (eq (point-min) (point-max))
4c83ed3d
SM
155 (vc-git--out-ok "ls-tree" "--name-only" "-z" "HEAD"
156 "--" name))
379241fa
DN
157 (buffer-string))))
158 (and str
159 (> (length str) (length name))
160 (string= (substring str 0 (1+ (length name)))
161 (concat name "\0"))))))))
108607bc 162
75cb52be
DN
163(defun vc-git--state-code (code)
164 "Convert from a string to a added/deleted/modified state."
165 (case (string-to-char code)
166 (?M 'edited)
167 (?A 'added)
168 (?D 'removed)
169 (?U 'edited) ;; FIXME
170 (?T 'edited))) ;; FIXME
171
fff4a046 172(defun vc-git-state (file)
53cc90ab 173 "Git-specific version of `vc-state'."
f6d90772
ER
174 ;; FIXME: This can't set 'ignored or 'conflict yet
175 ;; The 'ignored state could be detected with `git ls-files -i -o
176 ;; --exclude-standard` It also can't set 'needs-update or
177 ;; 'needs-merge. The rough equivalent would be that upstream branch
178 ;; for current branch is in fast-forward state i.e. current branch
179 ;; is direct ancestor of corresponding upstream branch, and the file
180 ;; was modified upstream. But we can't check that without a network
181 ;; operation.
3702367b
ER
182 (if (not (vc-git-registered file))
183 'unregistered
184 (vc-git--call nil "add" "--refresh" "--" (file-relative-name file))
4c83ed3d
SM
185 (let ((diff (vc-git--run-command-string
186 file "diff-index" "-z" "HEAD" "--")))
3702367b
ER
187 (if (and diff (string-match ":[0-7]\\{6\\} [0-7]\\{6\\} [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\([ADMUT]\\)\0[^\0]+\0"
188 diff))
189 (vc-git--state-code (match-string 1 diff))
190 (if (vc-git--empty-db-p) 'added 'up-to-date)))))
fff4a046 191
ac3f4c6f
ER
192(defun vc-git-working-revision (file)
193 "Git-specific version of `vc-working-revision'."
20c76c55
MA
194 (let* (process-file-side-effects
195 (str (with-output-to-string
196 (with-current-buffer standard-output
197 (vc-git--out-ok "symbolic-ref" "HEAD")))))
fff4a046
DN
198 (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str)
199 (match-string 2 str)
200 str)))
201
fff4a046 202(defun vc-git-workfile-unchanged-p (file)
00d67080 203 (eq 'up-to-date (vc-git-state file)))
fff4a046 204
6f222162
DN
205(defun vc-git-mode-line-string (file)
206 "Return string for placement into the modeline for FILE."
ac3f4c6f 207 (let* ((branch (vc-git-working-revision file))
6f222162
DN
208 (def-ml (vc-default-mode-line-string 'Git file))
209 (help-echo (get-text-property 0 'help-echo def-ml)))
210 (if (zerop (length branch))
211 (propertize
212 (concat def-ml "!")
213 'help-echo (concat help-echo "\nNo current branch (detached HEAD)"))
214 (propertize def-ml
215 'help-echo (concat help-echo "\nCurrent branch: " branch)))))
216
236b5827
DN
217(defstruct (vc-git-extra-fileinfo
218 (:copier nil)
4c83ed3d
SM
219 (:constructor vc-git-create-extra-fileinfo
220 (old-perm new-perm &optional rename-state orig-name))
236b5827 221 (:conc-name vc-git-extra-fileinfo->))
4c83ed3d
SM
222 old-perm new-perm ;; Permission flags.
223 rename-state ;; Rename or copy state.
224 orig-name) ;; Original name for renames or copies.
236b5827
DN
225
226(defun vc-git-escape-file-name (name)
227 "Escape a file name if necessary."
228 (if (string-match "[\n\t\"\\]" name)
229 (concat "\""
230 (mapconcat (lambda (c)
231 (case c
232 (?\n "\\n")
233 (?\t "\\t")
234 (?\\ "\\\\")
235 (?\" "\\\"")
236 (t (char-to-string c))))
237 name "")
238 "\"")
239 name))
240
241(defun vc-git-file-type-as-string (old-perm new-perm)
242 "Return a string describing the file type based on its permissions."
243 (let* ((old-type (lsh (or old-perm 0) -9))
244 (new-type (lsh (or new-perm 0) -9))
245 (str (case new-type
4c83ed3d 246 (?\100 ;; File.
236b5827
DN
247 (case old-type
248 (?\100 nil)
249 (?\120 " (type change symlink -> file)")
250 (?\160 " (type change subproject -> file)")))
4c83ed3d 251 (?\120 ;; Symlink.
236b5827
DN
252 (case old-type
253 (?\100 " (type change file -> symlink)")
254 (?\160 " (type change subproject -> symlink)")
255 (t " (symlink)")))
4c83ed3d 256 (?\160 ;; Subproject.
236b5827
DN
257 (case old-type
258 (?\100 " (type change file -> subproject)")
259 (?\120 " (type change symlink -> subproject)")
260 (t " (subproject)")))
4c83ed3d
SM
261 (?\110 nil) ;; Directory (internal, not a real git state).
262 (?\000 ;; Deleted or unknown.
236b5827
DN
263 (case old-type
264 (?\120 " (symlink)")
265 (?\160 " (subproject)")))
266 (t (format " (unknown type %o)" new-type)))))
267 (cond (str (propertize str 'face 'font-lock-comment-face))
268 ((eq new-type ?\110) "/")
269 (t ""))))
270
271(defun vc-git-rename-as-string (state extra)
4c83ed3d
SM
272 "Return a string describing the copy or rename associated with INFO,
273or an empty string if none."
20c76c55 274 (let ((rename-state (when extra
236b5827
DN
275 (vc-git-extra-fileinfo->rename-state extra))))
276 (if rename-state
277 (propertize
278 (concat " ("
279 (if (eq rename-state 'copy) "copied from "
280 (if (eq state 'added) "renamed from "
281 "renamed to "))
4c83ed3d
SM
282 (vc-git-escape-file-name
283 (vc-git-extra-fileinfo->orig-name extra))
284 ")")
285 'face 'font-lock-comment-face)
236b5827
DN
286 "")))
287
288(defun vc-git-permissions-as-string (old-perm new-perm)
289 "Format a permission change as string."
290 (propertize
291 (if (or (not old-perm)
292 (not new-perm)
293 (eq 0 (logand ?\111 (logxor old-perm new-perm))))
294 " "
295 (if (eq 0 (logand ?\111 old-perm)) "+x" "-x"))
296 'face 'font-lock-type-face))
297
13ad7457 298(defun vc-git-dir-printer (info)
99e1b0c0 299 "Pretty-printer for the vc-dir-fileinfo structure."
d1bfcce1
DN
300 (let* ((isdir (vc-dir-fileinfo->directory info))
301 (state (if isdir "" (vc-dir-fileinfo->state info)))
99e1b0c0 302 (extra (vc-dir-fileinfo->extra info))
236b5827
DN
303 (old-perm (when extra (vc-git-extra-fileinfo->old-perm extra)))
304 (new-perm (when extra (vc-git-extra-fileinfo->new-perm extra))))
305 (insert
306 " "
99e1b0c0 307 (propertize (format "%c" (if (vc-dir-fileinfo->marked info) ?* ? ))
236b5827
DN
308 'face 'font-lock-type-face)
309 " "
310 (propertize
311 (format "%-12s" state)
312 'face (cond ((eq state 'up-to-date) 'font-lock-builtin-face)
313 ((eq state 'missing) 'font-lock-warning-face)
314 (t 'font-lock-variable-name-face))
315 'mouse-face 'highlight)
316 " " (vc-git-permissions-as-string old-perm new-perm)
7f4a3168 317 " "
99e1b0c0 318 (propertize (vc-git-escape-file-name (vc-dir-fileinfo->name info))
4c83ed3d
SM
319 'face (if isdir 'font-lock-comment-delimiter-face
320 'font-lock-function-name-face)
631601b5
DN
321 'help-echo
322 (if isdir
323 "Directory\nVC operations can be applied to it\nmouse-3: Pop-up menu"
324 "File\nmouse-3: Pop-up menu")
c7f9e440 325 'keymap vc-dir-filename-mouse-map
631601b5 326 'mouse-face 'highlight)
236b5827
DN
327 (vc-git-file-type-as-string old-perm new-perm)
328 (vc-git-rename-as-string state extra))))
329
d41080ca
AJ
330(defun vc-git-after-dir-status-stage (stage files update-function)
331 "Process sentinel for the various dir-status stages."
4c83ed3d 332 (let (next-stage result)
d41080ca
AJ
333 (goto-char (point-min))
334 (case stage
4c83ed3d 335 (update-index
d41080ca
AJ
336 (setq next-stage (if (vc-git--empty-db-p) 'ls-files-added
337 (if files 'ls-files-up-to-date 'diff-index))))
4c83ed3d 338 (ls-files-added
d41080ca
AJ
339 (setq next-stage 'ls-files-unknown)
340 (while (re-search-forward "\\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} 0\t\\([^\0]+\\)\0" nil t)
341 (let ((new-perm (string-to-number (match-string 1) 8))
342 (name (match-string 2)))
4c83ed3d
SM
343 (push (list name 'added (vc-git-create-extra-fileinfo 0 new-perm))
344 result))))
345 (ls-files-up-to-date
d41080ca
AJ
346 (setq next-stage 'diff-index)
347 (while (re-search-forward "\\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} 0\t\\([^\0]+\\)\0" nil t)
348 (let ((perm (string-to-number (match-string 1) 8))
349 (name (match-string 2)))
4c83ed3d
SM
350 (push (list name 'up-to-date
351 (vc-git-create-extra-fileinfo perm perm))
352 result))))
353 (ls-files-unknown
d41080ca
AJ
354 (when files (setq next-stage 'ls-files-ignored))
355 (while (re-search-forward "\\([^\0]*?\\)\0" nil t 1)
4c83ed3d
SM
356 (push (list (match-string 1) 'unregistered
357 (vc-git-create-extra-fileinfo 0 0))
358 result)))
359 (ls-files-ignored
d41080ca 360 (while (re-search-forward "\\([^\0]*?\\)\0" nil t 1)
4c83ed3d
SM
361 (push (list (match-string 1) 'ignored
362 (vc-git-create-extra-fileinfo 0 0))
363 result)))
364 (diff-index
d41080ca
AJ
365 (setq next-stage 'ls-files-unknown)
366 (while (re-search-forward
367 ":\\([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"
368 nil t 1)
369 (let ((old-perm (string-to-number (match-string 1) 8))
370 (new-perm (string-to-number (match-string 2) 8))
371 (state (or (match-string 4) (match-string 6)))
372 (name (or (match-string 5) (match-string 7)))
373 (new-name (match-string 8)))
4c83ed3d 374 (if new-name ; Copy or rename.
d41080ca 375 (if (eq ?C (string-to-char state))
4c83ed3d
SM
376 (push (list new-name 'added
377 (vc-git-create-extra-fileinfo old-perm new-perm
378 'copy name))
379 result)
380 (push (list name 'removed
381 (vc-git-create-extra-fileinfo 0 0
382 'rename new-name))
383 result)
384 (push (list new-name 'added
385 (vc-git-create-extra-fileinfo old-perm new-perm
386 'rename name))
387 result))
388 (push (list name (vc-git--state-code state)
389 (vc-git-create-extra-fileinfo old-perm new-perm))
390 result))))))
d41080ca
AJ
391 (when result
392 (setq result (nreverse result))
393 (when files
394 (dolist (entry result) (setq files (delete (car entry) files)))
395 (unless files (setq next-stage nil))))
4c83ed3d
SM
396 (when (or result (not next-stage))
397 (funcall update-function result next-stage))
398 (when next-stage
399 (vc-git-dir-status-goto-stage next-stage files update-function))))
d41080ca
AJ
400
401(defun vc-git-dir-status-goto-stage (stage files update-function)
8e4e4aef 402 (erase-buffer)
d41080ca 403 (case stage
4c83ed3d 404 (update-index
d41080ca
AJ
405 (if files
406 (vc-git-command (current-buffer) 'async files "add" "--refresh" "--")
4c83ed3d
SM
407 (vc-git-command (current-buffer) 'async nil
408 "update-index" "--refresh")))
409 (ls-files-added
410 (vc-git-command (current-buffer) 'async files
411 "ls-files" "-z" "-c" "-s" "--"))
412 (ls-files-up-to-date
413 (vc-git-command (current-buffer) 'async files
414 "ls-files" "-z" "-c" "-s" "--"))
415 (ls-files-unknown
416 (vc-git-command (current-buffer) 'async files
417 "ls-files" "-z" "-o" "--directory"
418 "--no-empty-directory" "--exclude-standard" "--"))
419 (ls-files-ignored
420 (vc-git-command (current-buffer) 'async files
421 "ls-files" "-z" "-o" "-i" "--directory"
422 "--no-empty-directory" "--exclude-standard" "--"))
10a31174 423 ;; --relative added in Git 1.5.5.
4c83ed3d
SM
424 (diff-index
425 (vc-git-command (current-buffer) 'async files
426 "diff-index" "--relative" "-z" "-M" "HEAD" "--")))
8e4e4aef 427 (vc-exec-after
4c83ed3d 428 `(vc-git-after-dir-status-stage ',stage ',files ',update-function)))
8e4e4aef 429
c1b51374 430(defun vc-git-dir-status (dir update-function)
d41080ca 431 "Return a list of (FILE STATE EXTRA) entries for DIR."
12cb746e 432 ;; Further things that would have to be fixed later:
12cb746e 433 ;; - how to handle unregistered directories
99e1b0c0 434 ;; - how to support vc-dir on a subdir of the project tree
d41080ca
AJ
435 (vc-git-dir-status-goto-stage 'update-index nil update-function))
436
437(defun vc-git-dir-status-files (dir files default-state update-function)
438 "Return a list of (FILE STATE EXTRA) entries for FILES in DIR."
439 (vc-git-dir-status-goto-stage 'update-index files update-function))
758dc0cc 440
881e4184
GM
441(defvar vc-git-stash-map
442 (let ((map (make-sparse-keymap)))
7fa4876f
DN
443 ;; Turn off vc-dir marking
444 (define-key map [mouse-2] 'ignore)
445
446 (define-key map [down-mouse-3] 'vc-git-stash-menu)
881e4184
GM
447 (define-key map "\C-k" 'vc-git-stash-delete-at-point)
448 (define-key map "=" 'vc-git-stash-show-at-point)
449 (define-key map "\C-m" 'vc-git-stash-show-at-point)
7fa4876f
DN
450 (define-key map "A" 'vc-git-stash-apply-at-point)
451 (define-key map "P" 'vc-git-stash-pop-at-point)
e2f3c692 452 (define-key map "S" 'vc-git-stash-snapshot)
7fa4876f
DN
453 map))
454
455(defvar vc-git-stash-menu-map
456 (let ((map (make-sparse-keymap "Git Stash")))
457 (define-key map [de]
458 '(menu-item "Delete stash" vc-git-stash-delete-at-point
459 :help "Delete the current stash"))
460 (define-key map [ap]
461 '(menu-item "Apply stash" vc-git-stash-apply-at-point
462 :help "Apply the current stash and keep it in the stash list"))
463 (define-key map [po]
464 '(menu-item "Apply and remove stash (pop)" vc-git-stash-pop-at-point
465 :help "Apply the current stash and remove it"))
466 (define-key map [sh]
467 '(menu-item "Show stash" vc-git-stash-show-at-point
468 :help "Show the contents of the current stash"))
881e4184
GM
469 map))
470
13ad7457 471(defun vc-git-dir-extra-headers (dir)
15c5c970
DN
472 (let ((str (with-output-to-string
473 (with-current-buffer standard-output
2a0e3379 474 (vc-git--out-ok "symbolic-ref" "HEAD"))))
60308853 475 (stash (vc-git-stash-list))
7fa4876f 476 (stash-help-echo "Use M-x vc-git-stash to create stashes.")
60308853
DN
477 branch remote remote-url)
478 (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str)
479 (progn
480 (setq branch (match-string 2 str))
60308853
DN
481 (setq remote
482 (with-output-to-string
483 (with-current-buffer standard-output
4c83ed3d
SM
484 (vc-git--out-ok "config"
485 (concat "branch." branch ".remote")))))
60308853
DN
486 (when (string-match "\\([^\n]+\\)" remote)
487 (setq remote (match-string 1 remote)))
488 (when remote
489 (setq remote-url
490 (with-output-to-string
491 (with-current-buffer standard-output
4c83ed3d
SM
492 (vc-git--out-ok "config"
493 (concat "remote." remote ".url"))))))
60308853
DN
494 (when (string-match "\\([^\n]+\\)" remote-url)
495 (setq remote-url (match-string 1 remote-url))))
52964e54 496 (setq branch "not (detached HEAD)"))
2a0e3379 497 ;; FIXME: maybe use a different face when nothing is stashed.
15c5c970
DN
498 (concat
499 (propertize "Branch : " 'face 'font-lock-type-face)
60308853
DN
500 (propertize branch
501 'face 'font-lock-variable-name-face)
502 (when remote
503 (concat
504 "\n"
505 (propertize "Remote : " 'face 'font-lock-type-face)
506 (propertize remote-url
507 'face 'font-lock-variable-name-face)))
2a0e3379 508 "\n"
0e172cc2
DN
509 (if stash
510 (concat
7fa4876f
DN
511 (propertize "Stash :\n" 'face 'font-lock-type-face
512 'help-echo stash-help-echo)
0e172cc2
DN
513 (mapconcat
514 (lambda (x)
515 (propertize x
516 'face 'font-lock-variable-name-face
517 'mouse-face 'highlight
7fa4876f 518 'help-echo "mouse-3: Show stash menu\nRET: Show stash\nA: Apply stash\nP: Apply and remove stash (pop)\nC-k: Delete stash"
0e172cc2
DN
519 'keymap vc-git-stash-map))
520 stash "\n"))
521 (concat
7fa4876f
DN
522 (propertize "Stash : " 'face 'font-lock-type-face
523 'help-echo stash-help-echo)
0e172cc2 524 (propertize "Nothing stashed"
7fa4876f 525 'help-echo stash-help-echo
0e172cc2 526 'face 'font-lock-variable-name-face))))))
15c5c970 527
53cc90ab
DN
528;;; STATE-CHANGING FUNCTIONS
529
530(defun vc-git-create-repo ()
4211679b 531 "Create a new Git repository."
00d67080 532 (vc-git-command nil 0 nil "init"))
53cc90ab 533
8b9783e0 534(defun vc-git-register (files &optional rev comment)
b91f0762
DN
535 "Register FILES into the git version-control system."
536 (let (flist dlist)
537 (dolist (crt files)
538 (if (file-directory-p crt)
539 (push crt dlist)
540 (push crt flist)))
541 (when flist
542 (vc-git-command nil 0 flist "update-index" "--add" "--"))
543 (when dlist
544 (vc-git-command nil 0 dlist "add"))))
fff4a046 545
53cc90ab
DN
546(defalias 'vc-git-responsible-p 'vc-git-root)
547
d7009f45
DN
548(defun vc-git-unregister (file)
549 (vc-git-command nil 0 file "rm" "-f" "--cached" "--"))
108607bc 550
e97a42c1 551(declare-function log-edit-extract-headers "log-edit" (headers string))
d7009f45 552
e97a42c1
SM
553(defun vc-git-checkin (files rev comment)
554 (let ((coding-system-for-write vc-git-commits-coding-system))
6aebd58c 555 (apply 'vc-git-command nil 0 files
e97a42c1
SM
556 (nconc (list "commit" "-m")
557 (log-edit-extract-headers '(("Author" . "--author"))
558 comment)
559 (list "--only" "--")))))
fff4a046 560
ac3f4c6f 561(defun vc-git-find-revision (file rev buffer)
20c76c55
MA
562 (let* (process-file-side-effects
563 (coding-system-for-read 'binary)
564 (coding-system-for-write 'binary)
565 (fullname (substring
566 (vc-git--run-command-string
567 file "ls-files" "-z" "--full-name" "--")
568 0 -1)))
108607bc
DN
569 (vc-git-command
570 buffer 0
8b38ce20 571 (concat (if rev rev "HEAD") ":" fullname) "cat-file" "blob")))
b0f90937
DN
572
573(defun vc-git-checkout (file &optional editable rev)
7546c767 574 (vc-git-command nil 0 file "checkout" (or rev "HEAD")))
fff4a046
DN
575
576(defun vc-git-revert (file &optional contents-done)
577 "Revert FILE to the version stored in the git repository."
578 (if contents-done
b0f90937 579 (vc-git-command nil 0 file "update-index" "--")
cebf8ec6
DN
580 (vc-git-command nil 0 file "reset" "-q" "--")
581 (vc-git-command nil nil file "checkout" "-q" "--")))
fff4a046 582
53cc90ab
DN
583;;; HISTORY FUNCTIONS
584
662c5698 585(defun vc-git-print-log (files buffer &optional shortlog start-revision limit)
e9ef9777 586 "Get change log associated with FILES.
10a31174
GM
587Note that using SHORTLOG requires at least Git version 1.5.6,
588for the --graph option."
e97a42c1 589 (let ((coding-system-for-read vc-git-commits-coding-system))
53cc90ab
DN
590 ;; `vc-do-command' creates the buffer, but we need it before running
591 ;; the command.
592 (vc-setup-buffer buffer)
593 ;; If the buffer exists from a previous invocation it might be
594 ;; read-only.
595 (let ((inhibit-read-only t))
934a944e
AJ
596 (with-current-buffer
597 buffer
0d3f8a78
DN
598 (apply 'vc-git-command buffer
599 'async files
600 (append
58941d03 601 '("log" "--no-color")
0d3f8a78 602 (when shortlog
4c83ed3d 603 '("--graph" "--decorate" "--date=short"
25344b05 604 "--pretty=tformat:%d%h %ad %s" "--abbrev-commit"))
0d3f8a78 605 (when limit (list "-n" (format "%s" limit)))
662c5698 606 (when start-revision (list start-revision))
0d3f8a78 607 '("--")))))))
53cc90ab 608
31527c56
DN
609(defun vc-git-log-outgoing (buffer remote-location)
610 (interactive)
611 (vc-git-command
612 buffer 0 nil
613 "log" (if (string= remote-location "")
614 ;; FIXME: this hardcodes the location, it should compute
615 ;; it properly.
616 "origin/master..HEAD"
617 remote-location)))
618
53cc90ab
DN
619(defvar log-view-message-re)
620(defvar log-view-file-re)
621(defvar log-view-font-lock-keywords)
934a944e 622(defvar log-view-per-file-logs)
53cc90ab 623
4211679b 624(define-derived-mode vc-git-log-view-mode log-view-mode "Git-Log-View"
4c83ed3d 625 (require 'add-log) ;; We need the faces add-log.
53cc90ab 626 ;; Don't have file markers, so use impossible regexp.
934a944e
AJ
627 (set (make-local-variable 'log-view-file-re) "\\`a\\`")
628 (set (make-local-variable 'log-view-per-file-logs) nil)
53cc90ab 629 (set (make-local-variable 'log-view-message-re)
31527c56 630 (if (eq vc-log-view-type 'short)
0d3f8a78 631 "^\\(?:[*/\\| ]+ \\)?\\(?: ([^)]+)\\)?\\([0-9a-z]+\\) \\([-a-z0-9]+\\) \\(.*\\)"
79d316d3 632 "^commit *\\([0-9a-z]+\\)"))
53cc90ab 633 (set (make-local-variable 'log-view-font-lock-keywords)
31527c56 634 (if (eq vc-log-view-type 'short)
0d3f8a78
DN
635 '(
636 ;; Same as log-view-message-re, except that we don't
637 ;; want the shy group for the tag name.
638 ("^\\(?:[*/\\| ]+ \\)?\\( ([^)]+)\\)?\\([0-9a-z]+\\) \\([-a-z0-9]+\\) \\(.*\\)"
639 (1 'highlight nil lax)
640 (2 'change-log-acknowledgement)
641 (3 'change-log-date)))
53cc90ab 642 (append
0d3f8a78 643 `((,log-view-message-re (1 'change-log-acknowledgement)))
2aa0736a
TTN
644 ;; Handle the case:
645 ;; user: foo@bar
646 '(("^Author:[ \t]+\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)"
647 (1 'change-log-email))
648 ;; Handle the case:
649 ;; user: FirstName LastName <foo@bar>
650 ("^Author:[ \t]+\\([^<(]+?\\)[ \t]*[(<]\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)[>)]"
651 (1 'change-log-name)
652 (2 'change-log-email))
653 ("^ +\\(?:\\(?:[Aa]cked\\|[Ss]igned-[Oo]ff\\)-[Bb]y:\\)[ \t]+\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)"
654 (1 'change-log-name))
655 ("^ +\\(?:\\(?:[Aa]cked\\|[Ss]igned-[Oo]ff\\)-[Bb]y:\\)[ \t]+\\([^<(]+?\\)[ \t]*[(<]\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)[>)]"
656 (1 'change-log-name)
657 (2 'change-log-email))
658 ("^Merge: \\([0-9a-z]+\\) \\([0-9a-z]+\\)"
659 (1 'change-log-acknowledgement)
660 (2 'change-log-acknowledgement))
661 ("^Date: \\(.+\\)" (1 'change-log-date))
32ba3abc
DN
662 ("^summary:[ \t]+\\(.+\\)" (1 'log-view-message)))))))
663
fff4a046 664
b16bd82d
TTN
665(defun vc-git-show-log-entry (revision)
666 "Move to the log entry for REVISION.
667REVISION may have the form BRANCH, BRANCH~N,
668or BRANCH^ (where \"^\" can be repeated)."
669 (goto-char (point-min))
4c83ed3d
SM
670 (prog1
671 (when revision
672 (search-forward
673 (format "\ncommit %s" revision) nil t
674 (cond ((string-match "~\\([0-9]\\)\\'" revision)
675 (1+ (string-to-number (match-string 1 revision))))
676 ((string-match "\\^+\\'" revision)
677 (1+ (length (match-string 0 revision))))
678 (t nil))))
679 (beginning-of-line)))
b16bd82d 680
b747d346 681(defun vc-git-diff (files &optional rev1 rev2 buffer)
a0bea999 682 "Get a difference report using Git between two revisions of FILES."
20c76c55
MA
683 (let (process-file-side-effects)
684 (apply #'vc-git-command (or buffer "*vc-diff*") 1 files
685 (if (and rev1 rev2) "diff-tree" "diff-index")
686 "--exit-code"
687 (append (vc-switches 'git 'diff)
688 (list "-p" (or rev1 "HEAD") rev2 "--")))))
fff4a046 689
9f11ce4e
SM
690(defun vc-git-revision-table (files)
691 ;; What about `files'?!? --Stef
20c76c55
MA
692 (let (process-file-side-effects
693 (table (list "HEAD")))
108607bc
DN
694 (with-temp-buffer
695 (vc-git-command t nil nil "for-each-ref" "--format=%(refname)")
696 (goto-char (point-min))
53ef91b1
SM
697 (while (re-search-forward "^refs/\\(heads\\|tags\\|remotes\\)/\\(.*\\)$"
698 nil t)
108607bc
DN
699 (push (match-string 2) table)))
700 table))
701
9f11ce4e
SM
702(defun vc-git-revision-completion-table (files)
703 (lexical-let ((files files)
108607bc
DN
704 table)
705 (setq table (lazy-completion-table
9f11ce4e 706 table (lambda () (vc-git-revision-table files))))
108607bc
DN
707 table))
708
fff4a046 709(defun vc-git-annotate-command (file buf &optional rev)
fff4a046 710 (let ((name (file-relative-name file)))
d1e4c403 711 (vc-git-command buf 'async name "blame" "--date=iso" "-C" "-C" rev)))
fff4a046 712
f8bd9ac6
DN
713(declare-function vc-annotate-convert-time "vc-annotate" (time))
714
fff4a046 715(defun vc-git-annotate-time ()
0bcc6163 716 (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 717 (vc-annotate-convert-time
2aa0736a
TTN
718 (apply #'encode-time (mapcar (lambda (match)
719 (string-to-number (match-string match)))
720 '(6 5 4 3 2 1 7))))))
fff4a046 721
53cc90ab 722(defun vc-git-annotate-extract-revision-at-line ()
2aa0736a
TTN
723 (save-excursion
724 (move-beginning-of-line 1)
d1e4c403
DN
725 (when (looking-at "\\([0-9a-f^][0-9a-f]+\\) \\(\\([^(]+\\) \\)?")
726 (let ((revision (match-string-no-properties 1)))
727 (if (match-beginning 2)
1c465a6a
CY
728 (cons revision (expand-file-name (match-string-no-properties 3)
729 (vc-git-root default-directory)))
d1e4c403 730 revision)))))
53cc90ab 731
370fded4 732;;; TAG SYSTEM
b747d346 733
370fded4 734(defun vc-git-create-tag (dir name branchp)
b747d346
DN
735 (let ((default-directory dir))
736 (and (vc-git-command nil 0 nil "update-index" "--refresh")
737 (if branchp
738 (vc-git-command nil 0 nil "checkout" "-b" name)
739 (vc-git-command nil 0 nil "tag" name)))))
740
370fded4 741(defun vc-git-retrieve-tag (dir name update)
b747d346
DN
742 (let ((default-directory dir))
743 (vc-git-command nil 0 nil "checkout" name)
744 ;; FIXME: update buffers if `update' is true
745 ))
746
747
53cc90ab 748;;; MISCELLANEOUS
fff4a046 749
5b5afd50
ER
750(defun vc-git-previous-revision (file rev)
751 "Git-specific version of `vc-previous-revision'."
934a944e 752 (if file
4537363c
AJ
753 (let* ((default-directory (file-name-directory (expand-file-name file)))
754 (file (file-name-nondirectory file))
755 (prev-rev (with-temp-buffer
756 (and
757 (vc-git--out-ok "rev-list" "-2" rev "--" file)
758 (goto-char (point-max))
759 (bolp)
760 (zerop (forward-line -1))
761 (not (bobp))
762 (buffer-substring-no-properties
763 (point)
764 (1- (point-max)))))))
765 (or (vc-git-symbolic-commit prev-rev) prev-rev))
934a944e
AJ
766 (with-temp-buffer
767 (and
768 (vc-git--out-ok "rev-parse" (concat rev "^"))
769 (buffer-substring-no-properties (point-min) (+ (point-min) 40))))))
fff4a046 770
5b5afd50
ER
771(defun vc-git-next-revision (file rev)
772 "Git-specific version of `vc-next-revision'."
fff4a046
DN
773 (let* ((default-directory (file-name-directory
774 (expand-file-name file)))
2aa0736a
TTN
775 (file (file-name-nondirectory file))
776 (current-rev
777 (with-temp-buffer
778 (and
5fdbecd8 779 (vc-git--out-ok "rev-list" "-1" rev "--" file)
2aa0736a
TTN
780 (goto-char (point-max))
781 (bolp)
782 (zerop (forward-line -1))
783 (bobp)
784 (buffer-substring-no-properties
785 (point)
4537363c
AJ
786 (1- (point-max))))))
787 (next-rev
788 (and current-rev
789 (with-temp-buffer
790 (and
791 (vc-git--out-ok "rev-list" "HEAD" "--" file)
792 (goto-char (point-min))
793 (search-forward current-rev nil t)
794 (zerop (forward-line -1))
795 (buffer-substring-no-properties
796 (point)
797 (progn (forward-line 1) (1- (point)))))))))
798 (or (vc-git-symbolic-commit next-rev) next-rev)))
fff4a046 799
8b38ce20
DN
800(defun vc-git-delete-file (file)
801 (vc-git-command nil 0 file "rm" "-f" "--"))
b0f90937 802
8b38ce20
DN
803(defun vc-git-rename-file (old new)
804 (vc-git-command nil 0 (list old new) "mv" "-f" "--"))
b0f90937 805
f0e1713e
DN
806(defvar vc-git-extra-menu-map
807 (let ((map (make-sparse-keymap)))
808 (define-key map [git-grep]
809 '(menu-item "Git grep..." vc-git-grep
810 :help "Run the `git grep' command"))
e2f3c692
DN
811 (define-key map [git-sn]
812 '(menu-item "Stash a snapshot" vc-git-stash-snapshot
813 :help "Stash the current state of the tree and keep the current state"))
2ddf440d 814 (define-key map [git-st]
e2f3c692 815 '(menu-item "Create Stash..." vc-git-stash
2ddf440d
DN
816 :help "Stash away changes"))
817 (define-key map [git-ss]
818 '(menu-item "Show Stash..." vc-git-stash-show
819 :help "Show stash contents"))
f0e1713e
DN
820 map))
821
822(defun vc-git-extra-menu () vc-git-extra-menu-map)
823
824(defun vc-git-extra-status-menu () vc-git-extra-menu-map)
825
32ba3abc
DN
826(defun vc-git-root (file)
827 (vc-find-root file ".git"))
828
f0e1713e
DN
829;; Derived from `lgrep'.
830(defun vc-git-grep (regexp &optional files dir)
831 "Run git grep, searching for REGEXP in FILES in directory DIR.
832The search is limited to file names matching shell pattern FILES.
833FILES may use abbreviations defined in `grep-files-aliases', e.g.
834entering `ch' is equivalent to `*.[ch]'.
835
836With \\[universal-argument] prefix, you can edit the constructed shell command line
837before it is executed.
838With two \\[universal-argument] prefixes, directly edit and run `grep-command'.
839
840Collect output in a buffer. While git grep runs asynchronously, you
841can use \\[next-error] (M-x next-error), or \\<grep-mode-map>\\[compile-goto-error] \
842in the grep output buffer,
843to go to the lines where grep found matches.
844
845This command shares argument histories with \\[rgrep] and \\[grep]."
846 (interactive
847 (progn
848 (grep-compute-defaults)
849 (cond
850 ((equal current-prefix-arg '(16))
851 (list (read-from-minibuffer "Run: " "git grep"
852 nil nil 'grep-history)
853 nil))
854 (t (let* ((regexp (grep-read-regexp))
855 (files (grep-read-files regexp))
856 (dir (read-directory-name "In directory: "
857 nil default-directory t)))
858 (list regexp files dir))))))
859 (require 'grep)
860 (when (and (stringp regexp) (> (length regexp) 0))
861 (let ((command regexp))
862 (if (null files)
863 (if (string= command "git grep")
864 (setq command nil))
865 (setq dir (file-name-as-directory (expand-file-name dir)))
866 (setq command
867 (grep-expand-template "git grep -n -e <R> -- <F>" regexp files))
868 (when command
869 (if (equal current-prefix-arg '(4))
870 (setq command
871 (read-from-minibuffer "Confirm: "
872 command nil nil 'grep-history))
873 (add-to-history 'grep-history command))))
874 (when command
875 (let ((default-directory dir)
876 (compilation-environment '("PAGER=")))
877 ;; Setting process-setup-function makes exit-message-function work
878 ;; even when async processes aren't supported.
879 (compilation-start command 'grep-mode))
880 (if (eq next-error-last-buffer (current-buffer))
881 (setq default-directory dir))))))
2a0e3379 882
2ddf440d
DN
883(defun vc-git-stash (name)
884 "Create a stash."
885 (interactive "sStash name: ")
886 (let ((root (vc-git-root default-directory)))
887 (when root
888 (vc-git--call nil "stash" "save" name)
889 (vc-resynch-buffer root t t))))
890
891(defun vc-git-stash-show (name)
892 "Show the contents of stash NAME."
893 (interactive "sStash name: ")
894 (vc-setup-buffer "*vc-git-stash*")
895 (vc-git-command "*vc-git-stash*" 'async nil "stash" "show" "-p" name)
896 (set-buffer "*vc-git-stash*")
897 (diff-mode)
898 (setq buffer-read-only t)
899 (pop-to-buffer (current-buffer)))
900
7fa4876f
DN
901(defun vc-git-stash-apply (name)
902 "Apply stash NAME."
903 (interactive "sApply stash: ")
904 (vc-git-command "*vc-git-stash*" 0 nil "stash" "apply" "-q" name)
905 (vc-resynch-buffer (vc-git-root default-directory) t t))
906
907(defun vc-git-stash-pop (name)
908 "Pop stash NAME."
909 (interactive "sPop stash: ")
910 (vc-git-command "*vc-git-stash*" 0 nil "stash" "pop" "-q" name)
911 (vc-resynch-buffer (vc-git-root default-directory) t t))
912
e2f3c692
DN
913(defun vc-git-stash-snapshot ()
914 "Create a stash with the current tree state."
915 (interactive)
916 (vc-git--call nil "stash" "save"
917 (let ((ct (current-time)))
918 (concat
919 (format-time-string "Snapshot on %Y-%m-%d" ct)
920 (format-time-string " at %H:%M" ct))))
921 (vc-git-command "*vc-git-stash*" 0 nil "stash" "apply" "-q" "stash@{0}")
922 (vc-resynch-buffer (vc-git-root default-directory) t t))
923
2a0e3379 924(defun vc-git-stash-list ()
0e172cc2
DN
925 (delete
926 ""
927 (split-string
928 (replace-regexp-in-string
929 "^stash@" " " (vc-git--run-command-string nil "stash" "list"))
930 "\n")))
931
932(defun vc-git-stash-get-at-point (point)
933 (save-excursion
934 (goto-char point)
935 (beginning-of-line)
936 (if (looking-at "^ +\\({[0-9]+}\\):")
937 (match-string 1)
938 (error "Cannot find stash at point"))))
939
940(defun vc-git-stash-delete-at-point ()
941 (interactive)
942 (let ((stash (vc-git-stash-get-at-point (point))))
e2f3c692 943 (when (y-or-n-p (format "Remove stash %s ? " stash))
0e172cc2
DN
944 (vc-git--run-command-string nil "stash" "drop" (format "stash@%s" stash))
945 (vc-dir-refresh))))
946
947(defun vc-git-stash-show-at-point ()
948 (interactive)
949 (vc-git-stash-show (format "stash@%s" (vc-git-stash-get-at-point (point)))))
950
7fa4876f
DN
951(defun vc-git-stash-apply-at-point ()
952 (interactive)
953 (vc-git-stash-apply (format "stash@%s" (vc-git-stash-get-at-point (point)))))
954
955(defun vc-git-stash-pop-at-point ()
956 (interactive)
957 (vc-git-stash-pop (format "stash@%s" (vc-git-stash-get-at-point (point)))))
958
959(defun vc-git-stash-menu (e)
960 (interactive "e")
961 (vc-dir-at-event e (popup-menu vc-git-stash-menu-map e)))
962
fff4a046 963\f
b747d346 964;;; Internal commands
fff4a046 965
8b9783e0 966(defun vc-git-command (buffer okstatus file-or-list &rest flags)
53cc90ab
DN
967 "A wrapper around `vc-do-command' for use in vc-git.el.
968The difference to vc-do-command is that this function always invokes `git'."
2888a97e 969 (apply 'vc-do-command (or buffer "*vc*") okstatus "git" file-or-list flags))
53cc90ab 970
8e4e4aef
DN
971(defun vc-git--empty-db-p ()
972 "Check if the git db is empty (no commit done yet)."
20c76c55
MA
973 (let (process-file-side-effects)
974 (not (eq 0 (vc-git--call nil "rev-parse" "--verify" "HEAD")))))
8e4e4aef 975
5fdbecd8 976(defun vc-git--call (buffer command &rest args)
0664ff72
MA
977 ;; We don't need to care the arguments. If there is a file name, it
978 ;; is always a relative one. This works also for remote
979 ;; directories.
980 (apply 'process-file "git" nil buffer nil command args))
5fdbecd8
TTN
981
982(defun vc-git--out-ok (command &rest args)
983 (zerop (apply 'vc-git--call '(t nil) command args)))
984
fff4a046 985(defun vc-git--run-command-string (file &rest args)
2a0e3379
DN
986 "Run a git command on FILE and return its output as string.
987FILE can be nil."
fff4a046
DN
988 (let* ((ok t)
989 (str (with-output-to-string
990 (with-current-buffer standard-output
5fdbecd8 991 (unless (apply 'vc-git--out-ok
2a0e3379
DN
992 (if file
993 (append args (list (file-relative-name
994 file)))
995 args))
fff4a046
DN
996 (setq ok nil))))))
997 (and ok str)))
998
fff4a046
DN
999(defun vc-git-symbolic-commit (commit)
1000 "Translate COMMIT string into symbolic form.
1001Returns nil if not possible."
1002 (and commit
4537363c
AJ
1003 (let ((name (with-temp-buffer
1004 (and
1005 (vc-git--out-ok "name-rev" "--name-only" commit)
1006 (goto-char (point-min))
1007 (= (forward-line 2) 1)
1008 (bolp)
4c83ed3d
SM
1009 (buffer-substring-no-properties (point-min)
1010 (1- (point-max)))))))
4537363c 1011 (and name (not (string= name "undefined")) name))))
fff4a046
DN
1012
1013(provide 'vc-git)
53cc90ab 1014
328471d9 1015;; arch-tag: bd10664a-0e5b-48f5-a877-6c17b135be12
53cc90ab 1016;;; vc-git.el ends here