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