]> code.delx.au - gnu-emacs/blob - lisp/vc/vc-git.el
Update copyright year to 2015
[gnu-emacs] / lisp / vc / vc-git.el
1 ;;; vc-git.el --- VC backend for the git version control system -*- lexical-binding: t -*-
2
3 ;; Copyright (C) 2006-2015 Free Software Foundation, Inc.
4
5 ;; Author: Alexandre Julliard <julliard@winehq.org>
6 ;; Keywords: vc tools
7 ;; Package: vc
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software: you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation, either version 3 of the License, or
14 ;; (at your option) any later version.
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
22 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
23
24 ;;; Commentary:
25
26 ;; This file contains a VC backend for the git version control
27 ;; system.
28 ;;
29
30 ;;; Installation:
31
32 ;; To install: put this file on the load-path and add Git to the list
33 ;; of supported backends in `vc-handled-backends'; the following line,
34 ;; placed in your init file, will accomplish this:
35 ;;
36 ;; (add-to-list 'vc-handled-backends 'Git)
37
38 ;;; Todo:
39 ;; - check if more functions could use vc-git-command instead
40 ;; of start-process.
41 ;; - changelog generation
42
43 ;; Implement the rest of the vc interface. See the comment at the
44 ;; beginning of vc.el. The current status is:
45 ;; ("??" means: "figure out what to do about it")
46 ;;
47 ;; FUNCTION NAME STATUS
48 ;; BACKEND PROPERTIES
49 ;; * revision-granularity OK
50 ;; STATE-QUERYING FUNCTIONS
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
59 ;; STATE-CHANGING FUNCTIONS
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
72 ;; - merge (file rev1 rev2) It would be possible to merge
73 ;; changes into a single file, but
74 ;; when committing they wouldn't
75 ;; be identified as a merge
76 ;; by git, so it's probably
77 ;; not a good idea.
78 ;; - merge-news (file) see `merge'
79 ;; - steal-lock (file &optional revision) NOT NEEDED
80 ;; HISTORY FUNCTIONS
81 ;; * print-log (files buffer &optional shortlog start-revision limit) OK
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
92 ;; TAG SYSTEM
93 ;; - create-tag (dir name branchp) OK
94 ;; - retrieve-tag (dir name update) OK
95 ;; MISCELLANEOUS
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
105
106 ;;; Code:
107
108 (eval-when-compile
109 (require 'cl-lib)
110 (require 'vc)
111 (require 'vc-dir)
112 (require 'grep))
113
114 (defgroup vc-git nil
115 "VC Git backend."
116 :version "24.1"
117 :group 'vc)
118
119 (defcustom vc-git-diff-switches t
120 "String or list of strings specifying switches for Git diff under VC.
121 If nil, use the value of `vc-diff-switches'. If t, use no switches."
122 :type '(choice (const :tag "Unspecified" nil)
123 (const :tag "None" t)
124 (string :tag "Argument String")
125 (repeat :tag "Argument List" :value ("") string))
126 :version "23.1"
127 :group 'vc-git)
128
129 (defcustom vc-git-program "git"
130 "Name of the Git executable (excluding any arguments)."
131 :version "24.1"
132 :type 'string
133 :group 'vc-git)
134
135 (defcustom vc-git-root-log-format
136 '("%d%h..: %an %ad %s"
137 ;; The first shy group matches the characters drawn by --graph.
138 ;; We use numbered groups because `log-view-message-re' wants the
139 ;; revision number to be group 1.
140 "^\\(?:[*/\\| ]+ \\)?\\(?2: ([^)]+)\\)?\\(?1:[0-9a-z]+\\)..: \
141 \\(?3:.*?\\)[ \t]+\\(?4:[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)"
142 ((1 'log-view-message-face)
143 (2 'change-log-list nil lax)
144 (3 'change-log-name)
145 (4 'change-log-date)))
146 "Git log format for `vc-print-root-log'.
147 This should be a list (FORMAT REGEXP KEYWORDS), where FORMAT is a
148 format string (which is passed to \"git log\" via the argument
149 \"--pretty=tformat:FORMAT\"), REGEXP is a regular expression
150 matching the resulting Git log output, and KEYWORDS is a list of
151 `font-lock-keywords' for highlighting the Log View buffer."
152 :type '(list string string (repeat sexp))
153 :group 'vc-git
154 :version "24.1")
155
156 (defvar vc-git-commits-coding-system 'utf-8
157 "Default coding system for git commits.")
158
159 ;; History of Git commands.
160 (defvar vc-git-history nil)
161
162 ;;; BACKEND PROPERTIES
163
164 (defun vc-git-revision-granularity () 'repository)
165 (defun vc-git-checkout-model (_files) 'implicit)
166
167 ;;; STATE-QUERYING FUNCTIONS
168
169 ;;;###autoload (defun vc-git-registered (file)
170 ;;;###autoload "Return non-nil if FILE is registered with git."
171 ;;;###autoload (if (vc-find-root file ".git") ; Short cut.
172 ;;;###autoload (progn
173 ;;;###autoload (load "vc-git" nil t)
174 ;;;###autoload (vc-git-registered file))))
175
176 (defun vc-git-registered (file)
177 "Check whether FILE is registered with git."
178 (let ((dir (vc-git-root file)))
179 (when dir
180 (with-temp-buffer
181 (let* (process-file-side-effects
182 ;; Do not use the `file-name-directory' here: git-ls-files
183 ;; sometimes fails to return the correct status for relative
184 ;; path specs.
185 ;; See also: http://marc.info/?l=git&m=125787684318129&w=2
186 (name (file-relative-name file dir))
187 (str (ignore-errors
188 (cd dir)
189 (vc-git--out-ok "ls-files" "-c" "-z" "--" name)
190 ;; If result is empty, use ls-tree to check for deleted
191 ;; file.
192 (when (eq (point-min) (point-max))
193 (vc-git--out-ok "ls-tree" "--name-only" "-z" "HEAD"
194 "--" name))
195 (buffer-string))))
196 (and str
197 (> (length str) (length name))
198 (string= (substring str 0 (1+ (length name)))
199 (concat name "\0"))))))))
200
201 (defun vc-git--state-code (code)
202 "Convert from a string to a added/deleted/modified state."
203 (pcase (string-to-char code)
204 (?M 'edited)
205 (?A 'added)
206 (?D 'removed)
207 (?U 'edited) ;; FIXME
208 (?T 'edited))) ;; FIXME
209
210 (defun vc-git-state (file)
211 "Git-specific version of `vc-state'."
212 ;; FIXME: This can't set 'ignored or 'conflict yet
213 ;; The 'ignored state could be detected with `git ls-files -i -o
214 ;; --exclude-standard` It also can't set 'needs-update or
215 ;; 'needs-merge. The rough equivalent would be that upstream branch
216 ;; for current branch is in fast-forward state i.e. current branch
217 ;; is direct ancestor of corresponding upstream branch, and the file
218 ;; was modified upstream. But we can't check that without a network
219 ;; operation.
220 ;; This assumes that status is known to be not `unregistered' because
221 ;; we've been successfully dispatched here from `vc-state', that
222 ;; means `vc-git-registered' returned t earlier once. Bug#11757
223 (let ((diff (vc-git--run-command-string
224 file "diff-index" "-p" "--raw" "-z" "HEAD" "--")))
225 (if (and diff
226 (string-match ":[0-7]\\{6\\} [0-7]\\{6\\} [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\([ADMUT]\\)\0[^\0]+\0\\(.*\n.\\)?"
227 diff))
228 (let ((diff-letter (match-string 1 diff)))
229 (if (not (match-beginning 2))
230 ;; Empty diff: file contents is the same as the HEAD
231 ;; revision, but timestamps are different (eg, file
232 ;; was "touch"ed). Update timestamp in index:
233 (prog1 'up-to-date
234 (vc-git--call nil "add" "--refresh" "--"
235 (file-relative-name file)))
236 (vc-git--state-code diff-letter)))
237 (if (vc-git--empty-db-p) 'added 'up-to-date))))
238
239 (defun vc-git-working-revision (file)
240 "Git-specific version of `vc-working-revision'."
241 (let* (process-file-side-effects
242 (str (vc-git--run-command-string nil "symbolic-ref" "HEAD")))
243 (vc-file-setprop file 'vc-git-detached (null str))
244 (if str
245 (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str)
246 (match-string 2 str)
247 str)
248 (vc-git--rev-parse "HEAD"))))
249
250 (defun vc-git-workfile-unchanged-p (file)
251 (eq 'up-to-date (vc-git-state file)))
252
253 (defun vc-git-mode-line-string (file)
254 "Return a string for `vc-mode-line' to put in the mode line for FILE."
255 (let* ((rev (vc-working-revision file))
256 (detached (vc-file-getprop file 'vc-git-detached))
257 (def-ml (vc-default-mode-line-string 'Git file))
258 (help-echo (get-text-property 0 'help-echo def-ml)))
259 (propertize (if detached
260 (substring def-ml 0 (- 7 (length rev)))
261 def-ml)
262 'help-echo (concat help-echo "\nCurrent revision: " rev))))
263
264 (cl-defstruct (vc-git-extra-fileinfo
265 (:copier nil)
266 (:constructor vc-git-create-extra-fileinfo
267 (old-perm new-perm &optional rename-state orig-name))
268 (:conc-name vc-git-extra-fileinfo->))
269 old-perm new-perm ;; Permission flags.
270 rename-state ;; Rename or copy state.
271 orig-name) ;; Original name for renames or copies.
272
273 (defun vc-git-escape-file-name (name)
274 "Escape a file name if necessary."
275 (if (string-match "[\n\t\"\\]" name)
276 (concat "\""
277 (mapconcat (lambda (c)
278 (pcase c
279 (?\n "\\n")
280 (?\t "\\t")
281 (?\\ "\\\\")
282 (?\" "\\\"")
283 (_ (char-to-string c))))
284 name "")
285 "\"")
286 name))
287
288 (defun vc-git-file-type-as-string (old-perm new-perm)
289 "Return a string describing the file type based on its permissions."
290 (let* ((old-type (lsh (or old-perm 0) -9))
291 (new-type (lsh (or new-perm 0) -9))
292 (str (pcase new-type
293 (?\100 ;; File.
294 (pcase old-type
295 (?\100 nil)
296 (?\120 " (type change symlink -> file)")
297 (?\160 " (type change subproject -> file)")))
298 (?\120 ;; Symlink.
299 (pcase old-type
300 (?\100 " (type change file -> symlink)")
301 (?\160 " (type change subproject -> symlink)")
302 (t " (symlink)")))
303 (?\160 ;; Subproject.
304 (pcase old-type
305 (?\100 " (type change file -> subproject)")
306 (?\120 " (type change symlink -> subproject)")
307 (t " (subproject)")))
308 (?\110 nil) ;; Directory (internal, not a real git state).
309 (?\000 ;; Deleted or unknown.
310 (pcase old-type
311 (?\120 " (symlink)")
312 (?\160 " (subproject)")))
313 (_ (format " (unknown type %o)" new-type)))))
314 (cond (str (propertize str 'face 'font-lock-comment-face))
315 ((eq new-type ?\110) "/")
316 (t ""))))
317
318 (defun vc-git-rename-as-string (state extra)
319 "Return a string describing the copy or rename associated with INFO,
320 or an empty string if none."
321 (let ((rename-state (when extra
322 (vc-git-extra-fileinfo->rename-state extra))))
323 (if rename-state
324 (propertize
325 (concat " ("
326 (if (eq rename-state 'copy) "copied from "
327 (if (eq state 'added) "renamed from "
328 "renamed to "))
329 (vc-git-escape-file-name
330 (vc-git-extra-fileinfo->orig-name extra))
331 ")")
332 'face 'font-lock-comment-face)
333 "")))
334
335 (defun vc-git-permissions-as-string (old-perm new-perm)
336 "Format a permission change as string."
337 (propertize
338 (if (or (not old-perm)
339 (not new-perm)
340 (eq 0 (logand ?\111 (logxor old-perm new-perm))))
341 " "
342 (if (eq 0 (logand ?\111 old-perm)) "+x" "-x"))
343 'face 'font-lock-type-face))
344
345 (defun vc-git-dir-printer (info)
346 "Pretty-printer for the vc-dir-fileinfo structure."
347 (let* ((isdir (vc-dir-fileinfo->directory info))
348 (state (if isdir "" (vc-dir-fileinfo->state info)))
349 (extra (vc-dir-fileinfo->extra info))
350 (old-perm (when extra (vc-git-extra-fileinfo->old-perm extra)))
351 (new-perm (when extra (vc-git-extra-fileinfo->new-perm extra))))
352 (insert
353 " "
354 (propertize (format "%c" (if (vc-dir-fileinfo->marked info) ?* ? ))
355 'face 'font-lock-type-face)
356 " "
357 (propertize
358 (format "%-12s" state)
359 'face (cond ((eq state 'up-to-date) 'font-lock-builtin-face)
360 ((eq state 'missing) 'font-lock-warning-face)
361 (t 'font-lock-variable-name-face))
362 'mouse-face 'highlight)
363 " " (vc-git-permissions-as-string old-perm new-perm)
364 " "
365 (propertize (vc-git-escape-file-name (vc-dir-fileinfo->name info))
366 'face (if isdir 'font-lock-comment-delimiter-face
367 'font-lock-function-name-face)
368 'help-echo
369 (if isdir
370 "Directory\nVC operations can be applied to it\nmouse-3: Pop-up menu"
371 "File\nmouse-3: Pop-up menu")
372 'keymap vc-dir-filename-mouse-map
373 'mouse-face 'highlight)
374 (vc-git-file-type-as-string old-perm new-perm)
375 (vc-git-rename-as-string state extra))))
376
377 (defun vc-git-after-dir-status-stage (stage files update-function)
378 "Process sentinel for the various dir-status stages."
379 (let (next-stage result)
380 (goto-char (point-min))
381 (pcase stage
382 (`update-index
383 (setq next-stage (if (vc-git--empty-db-p) 'ls-files-added 'diff-index)))
384 (`ls-files-added
385 (setq next-stage 'ls-files-unknown)
386 (while (re-search-forward "\\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} 0\t\\([^\0]+\\)\0" nil t)
387 (let ((new-perm (string-to-number (match-string 1) 8))
388 (name (match-string 2)))
389 (push (list name 'added (vc-git-create-extra-fileinfo 0 new-perm))
390 result))))
391 (`ls-files-up-to-date
392 (setq next-stage 'ls-files-unknown)
393 (while (re-search-forward "\\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} 0\t\\([^\0]+\\)\0" nil t)
394 (let ((perm (string-to-number (match-string 1) 8))
395 (name (match-string 2)))
396 (push (list name 'up-to-date
397 (vc-git-create-extra-fileinfo perm perm))
398 result))))
399 (`ls-files-unknown
400 (when files (setq next-stage 'ls-files-ignored))
401 (while (re-search-forward "\\([^\0]*?\\)\0" nil t 1)
402 (push (list (match-string 1) 'unregistered
403 (vc-git-create-extra-fileinfo 0 0))
404 result)))
405 (`ls-files-ignored
406 (while (re-search-forward "\\([^\0]*?\\)\0" nil t 1)
407 (push (list (match-string 1) 'ignored
408 (vc-git-create-extra-fileinfo 0 0))
409 result)))
410 (`diff-index
411 (setq next-stage (if files 'ls-files-up-to-date 'ls-files-unknown))
412 (while (re-search-forward
413 ":\\([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"
414 nil t 1)
415 (let ((old-perm (string-to-number (match-string 1) 8))
416 (new-perm (string-to-number (match-string 2) 8))
417 (state (or (match-string 4) (match-string 6)))
418 (name (or (match-string 5) (match-string 7)))
419 (new-name (match-string 8)))
420 (if new-name ; Copy or rename.
421 (if (eq ?C (string-to-char state))
422 (push (list new-name 'added
423 (vc-git-create-extra-fileinfo old-perm new-perm
424 'copy name))
425 result)
426 (push (list name 'removed
427 (vc-git-create-extra-fileinfo 0 0
428 'rename new-name))
429 result)
430 (push (list new-name 'added
431 (vc-git-create-extra-fileinfo old-perm new-perm
432 'rename name))
433 result))
434 (push (list name (vc-git--state-code state)
435 (vc-git-create-extra-fileinfo old-perm new-perm))
436 result))))))
437 (when result
438 (setq result (nreverse result))
439 (when files
440 (dolist (entry result) (setq files (delete (car entry) files)))
441 (unless files (setq next-stage nil))))
442 (when (or result (not next-stage))
443 (funcall update-function result next-stage))
444 (when next-stage
445 (vc-git-dir-status-goto-stage next-stage files update-function))))
446
447 ;; Follows vc-git-command (or vc-do-async-command), which uses vc-do-command
448 ;; from vc-dispatcher.
449 (declare-function vc-exec-after "vc-dispatcher" (code))
450 ;; Follows vc-exec-after.
451 (declare-function vc-set-async-update "vc-dispatcher" (process-buffer))
452
453 (defun vc-git-dir-status-goto-stage (stage files update-function)
454 (erase-buffer)
455 (pcase stage
456 (`update-index
457 (if files
458 (vc-git-command (current-buffer) 'async files "add" "--refresh" "--")
459 (vc-git-command (current-buffer) 'async nil
460 "update-index" "--refresh")))
461 (`ls-files-added
462 (vc-git-command (current-buffer) 'async files
463 "ls-files" "-z" "-c" "-s" "--"))
464 (`ls-files-up-to-date
465 (vc-git-command (current-buffer) 'async files
466 "ls-files" "-z" "-c" "-s" "--"))
467 (`ls-files-unknown
468 (vc-git-command (current-buffer) 'async files
469 "ls-files" "-z" "-o" "--directory"
470 "--no-empty-directory" "--exclude-standard" "--"))
471 (`ls-files-ignored
472 (vc-git-command (current-buffer) 'async files
473 "ls-files" "-z" "-o" "-i" "--directory"
474 "--no-empty-directory" "--exclude-standard" "--"))
475 ;; --relative added in Git 1.5.5.
476 (`diff-index
477 (vc-git-command (current-buffer) 'async files
478 "diff-index" "--relative" "-z" "-M" "HEAD" "--")))
479 (vc-run-delayed
480 (vc-git-after-dir-status-stage stage files update-function)))
481
482 (defun vc-git-dir-status (_dir update-function)
483 "Return a list of (FILE STATE EXTRA) entries for DIR."
484 ;; Further things that would have to be fixed later:
485 ;; - how to handle unregistered directories
486 ;; - how to support vc-dir on a subdir of the project tree
487 (vc-git-dir-status-goto-stage 'update-index nil update-function))
488
489 (defun vc-git-dir-status-files (_dir files _default-state update-function)
490 "Return a list of (FILE STATE EXTRA) entries for FILES in DIR."
491 (vc-git-dir-status-goto-stage 'update-index files update-function))
492
493 (defvar vc-git-stash-map
494 (let ((map (make-sparse-keymap)))
495 ;; Turn off vc-dir marking
496 (define-key map [mouse-2] 'ignore)
497
498 (define-key map [down-mouse-3] 'vc-git-stash-menu)
499 (define-key map "\C-k" 'vc-git-stash-delete-at-point)
500 (define-key map "=" 'vc-git-stash-show-at-point)
501 (define-key map "\C-m" 'vc-git-stash-show-at-point)
502 (define-key map "A" 'vc-git-stash-apply-at-point)
503 (define-key map "P" 'vc-git-stash-pop-at-point)
504 (define-key map "S" 'vc-git-stash-snapshot)
505 map))
506
507 (defvar vc-git-stash-menu-map
508 (let ((map (make-sparse-keymap "Git Stash")))
509 (define-key map [de]
510 '(menu-item "Delete Stash" vc-git-stash-delete-at-point
511 :help "Delete the current stash"))
512 (define-key map [ap]
513 '(menu-item "Apply Stash" vc-git-stash-apply-at-point
514 :help "Apply the current stash and keep it in the stash list"))
515 (define-key map [po]
516 '(menu-item "Apply and Remove Stash (Pop)" vc-git-stash-pop-at-point
517 :help "Apply the current stash and remove it"))
518 (define-key map [sh]
519 '(menu-item "Show Stash" vc-git-stash-show-at-point
520 :help "Show the contents of the current stash"))
521 map))
522
523 (defun vc-git-dir-extra-headers (dir)
524 (let ((str (with-output-to-string
525 (with-current-buffer standard-output
526 (vc-git--out-ok "symbolic-ref" "HEAD"))))
527 (stash (vc-git-stash-list))
528 (stash-help-echo "Use M-x vc-git-stash to create stashes.")
529 branch remote remote-url)
530 (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str)
531 (progn
532 (setq branch (match-string 2 str))
533 (setq remote
534 (with-output-to-string
535 (with-current-buffer standard-output
536 (vc-git--out-ok "config"
537 (concat "branch." branch ".remote")))))
538 (when (string-match "\\([^\n]+\\)" remote)
539 (setq remote (match-string 1 remote)))
540 (when remote
541 (setq remote-url
542 (with-output-to-string
543 (with-current-buffer standard-output
544 (vc-git--out-ok "config"
545 (concat "remote." remote ".url"))))))
546 (when (string-match "\\([^\n]+\\)" remote-url)
547 (setq remote-url (match-string 1 remote-url))))
548 (setq branch "not (detached HEAD)"))
549 ;; FIXME: maybe use a different face when nothing is stashed.
550 (concat
551 (propertize "Branch : " 'face 'font-lock-type-face)
552 (propertize branch
553 'face 'font-lock-variable-name-face)
554 (when remote
555 (concat
556 "\n"
557 (propertize "Remote : " 'face 'font-lock-type-face)
558 (propertize remote-url
559 'face 'font-lock-variable-name-face)))
560 "\n"
561 ;; For now just a heading, key bindings can be added later for various bisect actions
562 (when (file-exists-p (expand-file-name ".git/BISECT_START" (vc-git-root dir)))
563 (propertize "Bisect : in progress\n" 'face 'font-lock-warning-face))
564 (when (file-exists-p (expand-file-name ".git/rebase-apply" (vc-git-root dir)))
565 (propertize "Rebase : in progress\n" 'face 'font-lock-warning-face))
566 (if stash
567 (concat
568 (propertize "Stash :\n" 'face 'font-lock-type-face
569 'help-echo stash-help-echo)
570 (mapconcat
571 (lambda (x)
572 (propertize x
573 'face 'font-lock-variable-name-face
574 'mouse-face 'highlight
575 'help-echo "mouse-3: Show stash menu\nRET: Show stash\nA: Apply stash\nP: Apply and remove stash (pop)\nC-k: Delete stash"
576 'keymap vc-git-stash-map))
577 stash "\n"))
578 (concat
579 (propertize "Stash : " 'face 'font-lock-type-face
580 'help-echo stash-help-echo)
581 (propertize "Nothing stashed"
582 'help-echo stash-help-echo
583 'face 'font-lock-variable-name-face))))))
584
585 (defun vc-git-branches ()
586 "Return the existing branches, as a list of strings.
587 The car of the list is the current branch."
588 (with-temp-buffer
589 (vc-git--call t "branch")
590 (goto-char (point-min))
591 (let (current-branch branches)
592 (while (not (eobp))
593 (when (looking-at "^\\([ *]\\) \\(.+\\)$")
594 (if (string-equal (match-string 1) "*")
595 (setq current-branch (match-string 2))
596 (push (match-string 2) branches)))
597 (forward-line 1))
598 (cons current-branch (nreverse branches)))))
599
600 ;;; STATE-CHANGING FUNCTIONS
601
602 (defun vc-git-create-repo ()
603 "Create a new Git repository."
604 (vc-git-command nil 0 nil "init"))
605
606 (defun vc-git-register (files &optional _rev _comment)
607 "Register FILES into the git version-control system."
608 (let (flist dlist)
609 (dolist (crt files)
610 (if (file-directory-p crt)
611 (push crt dlist)
612 (push crt flist)))
613 (when flist
614 (vc-git-command nil 0 flist "update-index" "--add" "--"))
615 (when dlist
616 (vc-git-command nil 0 dlist "add"))))
617
618 (defalias 'vc-git-responsible-p 'vc-git-root)
619
620 (defun vc-git-unregister (file)
621 (vc-git-command nil 0 file "rm" "-f" "--cached" "--"))
622
623 (declare-function log-edit-mode "log-edit" ())
624 (declare-function log-edit-toggle-header "log-edit" (header value))
625 (declare-function log-edit-extract-headers "log-edit" (headers string))
626 (declare-function log-edit-set-header "log-edit" (header value &optional toggle))
627
628 (defun vc-git-log-edit-toggle-signoff ()
629 "Toggle whether to add the \"Signed-off-by\" line at the end of
630 the commit message."
631 (interactive)
632 (log-edit-toggle-header "Sign-Off" "yes"))
633
634 (defun vc-git-log-edit-toggle-amend ()
635 "Toggle whether this will amend the previous commit.
636 If toggling on, also insert its message into the buffer."
637 (interactive)
638 (when (log-edit-toggle-header "Amend" "yes")
639 (goto-char (point-max))
640 (unless (bolp) (insert "\n"))
641 (insert (with-output-to-string
642 (vc-git-command
643 standard-output 1 nil
644 "log" "--max-count=1" "--pretty=format:%B" "HEAD")))
645 (save-excursion
646 (rfc822-goto-eoh)
647 (forward-line 1)
648 (let ((pt (point)))
649 (and (zerop (forward-line 1))
650 (looking-at "\n\\|\\'")
651 (let ((summary (buffer-substring-no-properties pt (1- (point)))))
652 (skip-chars-forward " \n")
653 (delete-region pt (point))
654 (log-edit-set-header "Summary" summary)))))))
655
656 (defvar vc-git-log-edit-mode-map
657 (let ((map (make-sparse-keymap "Git-Log-Edit")))
658 (define-key map "\C-c\C-s" 'vc-git-log-edit-toggle-signoff)
659 (define-key map "\C-c\C-e" 'vc-git-log-edit-toggle-amend)
660 map))
661
662 (define-derived-mode vc-git-log-edit-mode log-edit-mode "Log-Edit/git"
663 "Major mode for editing Git log messages.
664 It is based on `log-edit-mode', and has Git-specific extensions.")
665
666 (defun vc-git-checkin (files _rev comment)
667 (let* ((file1 (or (car files) default-directory))
668 (root (vc-git-root file1))
669 (default-directory (expand-file-name root))
670 (only (or (cdr files)
671 (not (equal root (abbreviate-file-name file1)))))
672 (coding-system-for-write vc-git-commits-coding-system))
673 (cl-flet ((boolean-arg-fn
674 (argument)
675 (lambda (value) (when (equal value "yes") (list argument)))))
676 ;; When operating on the whole tree, better pass "-a" than ".", since "."
677 ;; fails when we're committing a merge.
678 (apply 'vc-git-command nil 0 (if only files)
679 (nconc (list "commit" "-m")
680 (log-edit-extract-headers
681 `(("Author" . "--author")
682 ("Date" . "--date")
683 ("Amend" . ,(boolean-arg-fn "--amend"))
684 ("Sign-Off" . ,(boolean-arg-fn "--signoff")))
685 comment)
686 (if only (list "--only" "--") '("-a")))))))
687
688 (defun vc-git-find-revision (file rev buffer)
689 (let* (process-file-side-effects
690 (coding-system-for-read 'binary)
691 (coding-system-for-write 'binary)
692 (fullname
693 (let ((fn (vc-git--run-command-string
694 file "ls-files" "-z" "--full-name" "--")))
695 ;; ls-files does not return anything when looking for a
696 ;; revision of a file that has been renamed or removed.
697 (if (string= fn "")
698 (file-relative-name file (vc-git-root default-directory))
699 (substring fn 0 -1)))))
700 (vc-git-command
701 buffer 0
702 nil
703 "cat-file" "blob" (concat (if rev rev "HEAD") ":" fullname))))
704
705 (defun vc-git-find-ignore-file (file)
706 "Return the root directory of the repository of FILE."
707 (expand-file-name ".gitignore"
708 (vc-git-root file)))
709
710 (defun vc-git-checkout (file &optional _editable rev)
711 (vc-git-command nil 0 file "checkout" (or rev "HEAD")))
712
713 (defun vc-git-revert (file &optional contents-done)
714 "Revert FILE to the version stored in the git repository."
715 (if contents-done
716 (vc-git-command nil 0 file "update-index" "--")
717 (vc-git-command nil 0 file "reset" "-q" "--")
718 (vc-git-command nil nil file "checkout" "-q" "--")))
719
720 (defvar vc-git-error-regexp-alist
721 '(("^ \\(.+\\) |" 1 nil nil 0))
722 "Value of `compilation-error-regexp-alist' in *vc-git* buffers.")
723
724 ;; To be called via vc-pull from vc.el, which requires vc-dispatcher.
725 (declare-function vc-compilation-mode "vc-dispatcher" (backend))
726
727 (defun vc-git-pull (prompt)
728 "Pull changes into the current Git branch.
729 Normally, this runs \"git pull\". If PROMPT is non-nil, prompt
730 for the Git command to run."
731 (let* ((root (vc-git-root default-directory))
732 (buffer (format "*vc-git : %s*" (expand-file-name root)))
733 (command "pull")
734 (git-program vc-git-program)
735 args)
736 ;; If necessary, prompt for the exact command.
737 (when prompt
738 (setq args (split-string
739 (read-shell-command "Git pull command: "
740 (format "%s pull" git-program)
741 'vc-git-history)
742 " " t))
743 (setq git-program (car args)
744 command (cadr args)
745 args (cddr args)))
746 (require 'vc-dispatcher)
747 (apply 'vc-do-async-command buffer root git-program command args)
748 (with-current-buffer buffer (vc-run-delayed (vc-compilation-mode 'git)))
749 (vc-set-async-update buffer)))
750
751 (defun vc-git-merge-branch ()
752 "Merge changes into the current Git branch.
753 This prompts for a branch to merge from."
754 (let* ((root (vc-git-root default-directory))
755 (buffer (format "*vc-git : %s*" (expand-file-name root)))
756 (branches (cdr (vc-git-branches)))
757 (merge-source
758 (completing-read "Merge from branch: "
759 (if (or (member "FETCH_HEAD" branches)
760 (not (file-readable-p
761 (expand-file-name ".git/FETCH_HEAD"
762 root))))
763 branches
764 (cons "FETCH_HEAD" branches))
765 nil t)))
766 (apply 'vc-do-async-command buffer root vc-git-program "merge"
767 (list merge-source))
768 (with-current-buffer buffer (vc-run-delayed (vc-compilation-mode 'git)))
769 (vc-set-async-update buffer)))
770
771 ;;; HISTORY FUNCTIONS
772
773 (autoload 'vc-setup-buffer "vc-dispatcher")
774
775 (defun vc-git-print-log (files buffer &optional shortlog start-revision limit)
776 "Print commit log associated with FILES into specified BUFFER.
777 If SHORTLOG is non-nil, use a short format based on `vc-git-root-log-format'.
778 \(This requires at least Git version 1.5.6, for the --graph option.)
779 If START-REVISION is non-nil, it is the newest revision to show.
780 If LIMIT is non-nil, show no more than this many entries."
781 (let ((coding-system-for-read vc-git-commits-coding-system))
782 ;; `vc-do-command' creates the buffer, but we need it before running
783 ;; the command.
784 (vc-setup-buffer buffer)
785 ;; If the buffer exists from a previous invocation it might be
786 ;; read-only.
787 (let ((inhibit-read-only t))
788 (with-current-buffer
789 buffer
790 (apply 'vc-git-command buffer
791 'async files
792 (append
793 '("log" "--no-color")
794 (when shortlog
795 `("--graph" "--decorate" "--date=short"
796 ,(format "--pretty=tformat:%s"
797 (car vc-git-root-log-format))
798 "--abbrev-commit"))
799 (when limit (list "-n" (format "%s" limit)))
800 (when start-revision (list start-revision))
801 '("--")))))))
802
803 (defun vc-git-log-outgoing (buffer remote-location)
804 (interactive)
805 (vc-git-command
806 buffer 0 nil
807 "log"
808 "--no-color" "--graph" "--decorate" "--date=short"
809 (format "--pretty=tformat:%s" (car vc-git-root-log-format))
810 "--abbrev-commit"
811 (concat (if (string= remote-location "")
812 "@{upstream}"
813 remote-location)
814 "..HEAD")))
815
816 (defun vc-git-log-incoming (buffer remote-location)
817 (interactive)
818 (vc-git-command nil 0 nil "fetch")
819 (vc-git-command
820 buffer 0 nil
821 "log"
822 "--no-color" "--graph" "--decorate" "--date=short"
823 (format "--pretty=tformat:%s" (car vc-git-root-log-format))
824 "--abbrev-commit"
825 (concat "HEAD.." (if (string= remote-location "")
826 "@{upstream}"
827 remote-location))))
828
829 (defvar log-view-message-re)
830 (defvar log-view-file-re)
831 (defvar log-view-font-lock-keywords)
832 (defvar log-view-per-file-logs)
833 (defvar log-view-expanded-log-entry-function)
834
835 (define-derived-mode vc-git-log-view-mode log-view-mode "Git-Log-View"
836 (require 'add-log) ;; We need the faces add-log.
837 ;; Don't have file markers, so use impossible regexp.
838 (set (make-local-variable 'log-view-file-re) "\\`a\\`")
839 (set (make-local-variable 'log-view-per-file-logs) nil)
840 (set (make-local-variable 'log-view-message-re)
841 (if (not (eq vc-log-view-type 'long))
842 (cadr vc-git-root-log-format)
843 "^commit *\\([0-9a-z]+\\)"))
844 ;; Allow expanding short log entries
845 (when (eq vc-log-view-type 'short)
846 (setq truncate-lines t)
847 (set (make-local-variable 'log-view-expanded-log-entry-function)
848 'vc-git-expanded-log-entry))
849 (set (make-local-variable 'log-view-font-lock-keywords)
850 (if (not (eq vc-log-view-type 'long))
851 (list (cons (nth 1 vc-git-root-log-format)
852 (nth 2 vc-git-root-log-format)))
853 (append
854 `((,log-view-message-re (1 'change-log-acknowledgment)))
855 ;; Handle the case:
856 ;; user: foo@bar
857 '(("^Author:[ \t]+\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)"
858 (1 'change-log-email))
859 ;; Handle the case:
860 ;; user: FirstName LastName <foo@bar>
861 ("^Author:[ \t]+\\([^<(]+?\\)[ \t]*[(<]\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)[>)]"
862 (1 'change-log-name)
863 (2 'change-log-email))
864 ("^ +\\(?:\\(?:[Aa]cked\\|[Ss]igned-[Oo]ff\\)-[Bb]y:\\)[ \t]+\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)"
865 (1 'change-log-name))
866 ("^ +\\(?:\\(?:[Aa]cked\\|[Ss]igned-[Oo]ff\\)-[Bb]y:\\)[ \t]+\\([^<(]+?\\)[ \t]*[(<]\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)[>)]"
867 (1 'change-log-name)
868 (2 'change-log-email))
869 ("^Merge: \\([0-9a-z]+\\) \\([0-9a-z]+\\)"
870 (1 'change-log-acknowledgment)
871 (2 'change-log-acknowledgment))
872 ("^Date: \\(.+\\)" (1 'change-log-date))
873 ("^summary:[ \t]+\\(.+\\)" (1 'log-view-message)))))))
874
875
876 (defun vc-git-show-log-entry (revision)
877 "Move to the log entry for REVISION.
878 REVISION may have the form BRANCH, BRANCH~N,
879 or BRANCH^ (where \"^\" can be repeated)."
880 (goto-char (point-min))
881 (prog1
882 (when revision
883 (search-forward
884 (format "\ncommit %s" revision) nil t
885 (cond ((string-match "~\\([0-9]\\)\\'" revision)
886 (1+ (string-to-number (match-string 1 revision))))
887 ((string-match "\\^+\\'" revision)
888 (1+ (length (match-string 0 revision))))
889 (t nil))))
890 (beginning-of-line)))
891
892 (defun vc-git-expanded-log-entry (revision)
893 (with-temp-buffer
894 (apply 'vc-git-command t nil nil (list "log" revision "-1"))
895 (goto-char (point-min))
896 (unless (eobp)
897 ;; Indent the expanded log entry.
898 (indent-region (point-min) (point-max) 2)
899 (buffer-string))))
900
901 (autoload 'vc-switches "vc")
902
903 (defun vc-git-diff (files &optional rev1 rev2 buffer)
904 "Get a difference report using Git between two revisions of FILES."
905 (let (process-file-side-effects)
906 (apply #'vc-git-command (or buffer "*vc-diff*") 1 files
907 (if (and rev1 rev2) "diff-tree" "diff-index")
908 "--exit-code"
909 (append (vc-switches 'git 'diff)
910 (list "-p" (or rev1 "HEAD") rev2 "--")))))
911
912 (defun vc-git-revision-table (_files)
913 ;; What about `files'?!? --Stef
914 (let (process-file-side-effects
915 (table (list "HEAD")))
916 (with-temp-buffer
917 (vc-git-command t nil nil "for-each-ref" "--format=%(refname)")
918 (goto-char (point-min))
919 (while (re-search-forward "^refs/\\(heads\\|tags\\|remotes\\)/\\(.*\\)$"
920 nil t)
921 (push (match-string 2) table)))
922 table))
923
924 (defun vc-git-revision-completion-table (files)
925 (letrec ((table (lazy-completion-table
926 table (lambda () (vc-git-revision-table files)))))
927 table))
928
929 (defun vc-git-annotate-command (file buf &optional rev)
930 (let ((name (file-relative-name file)))
931 (vc-git-command buf 'async nil "blame" "--date=iso" "-C" "-C" rev "--" name)))
932
933 (declare-function vc-annotate-convert-time "vc-annotate" (time))
934
935 (defun vc-git-annotate-time ()
936 (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)
937 (vc-annotate-convert-time
938 (apply #'encode-time (mapcar (lambda (match)
939 (string-to-number (match-string match)))
940 '(6 5 4 3 2 1 7))))))
941
942 (defun vc-git-annotate-extract-revision-at-line ()
943 (save-excursion
944 (beginning-of-line)
945 (when (looking-at "\\([0-9a-f^][0-9a-f]+\\) \\(\\([^(]+\\) \\)?")
946 (let ((revision (match-string-no-properties 1)))
947 (if (match-beginning 2)
948 (let ((fname (match-string-no-properties 3)))
949 ;; Remove trailing whitespace from the file name.
950 (when (string-match " +\\'" fname)
951 (setq fname (substring fname 0 (match-beginning 0))))
952 (cons revision
953 (expand-file-name fname (vc-git-root default-directory))))
954 revision)))))
955
956 ;;; TAG SYSTEM
957
958 (defun vc-git-create-tag (dir name branchp)
959 (let ((default-directory dir))
960 (and (vc-git-command nil 0 nil "update-index" "--refresh")
961 (if branchp
962 (vc-git-command nil 0 nil "checkout" "-b" name)
963 (vc-git-command nil 0 nil "tag" name)))))
964
965 (defun vc-git-retrieve-tag (dir name _update)
966 (let ((default-directory dir))
967 (vc-git-command nil 0 nil "checkout" name)
968 ;; FIXME: update buffers if `update' is true
969 ))
970
971
972 ;;; MISCELLANEOUS
973
974 (defun vc-git-previous-revision (file rev)
975 "Git-specific version of `vc-previous-revision'."
976 (if file
977 (let* ((fname (file-relative-name file))
978 (prev-rev (with-temp-buffer
979 (and
980 (vc-git--out-ok "rev-list" "-2" rev "--" fname)
981 (goto-char (point-max))
982 (bolp)
983 (zerop (forward-line -1))
984 (not (bobp))
985 (buffer-substring-no-properties
986 (point)
987 (1- (point-max)))))))
988 (or (vc-git-symbolic-commit prev-rev) prev-rev))
989 ;; We used to use "^" here, but that fails on MS-Windows if git is
990 ;; invoked via a batch file, in which case cmd.exe strips the "^"
991 ;; because it is a special character for cmd which process-file
992 ;; does not (and cannot) quote.
993 (vc-git--rev-parse (concat rev "~1"))))
994
995 (defun vc-git--rev-parse (rev)
996 (with-temp-buffer
997 (and
998 (vc-git--out-ok "rev-parse" rev)
999 (buffer-substring-no-properties (point-min) (+ (point-min) 40)))))
1000
1001 (defun vc-git-next-revision (file rev)
1002 "Git-specific version of `vc-next-revision'."
1003 (let* ((default-directory (file-name-directory
1004 (expand-file-name file)))
1005 (file (file-name-nondirectory file))
1006 (current-rev
1007 (with-temp-buffer
1008 (and
1009 (vc-git--out-ok "rev-list" "-1" rev "--" file)
1010 (goto-char (point-max))
1011 (bolp)
1012 (zerop (forward-line -1))
1013 (bobp)
1014 (buffer-substring-no-properties
1015 (point)
1016 (1- (point-max))))))
1017 (next-rev
1018 (and current-rev
1019 (with-temp-buffer
1020 (and
1021 (vc-git--out-ok "rev-list" "HEAD" "--" file)
1022 (goto-char (point-min))
1023 (search-forward current-rev nil t)
1024 (zerop (forward-line -1))
1025 (buffer-substring-no-properties
1026 (point)
1027 (progn (forward-line 1) (1- (point)))))))))
1028 (or (vc-git-symbolic-commit next-rev) next-rev)))
1029
1030 (defun vc-git-delete-file (file)
1031 (vc-git-command nil 0 file "rm" "-f" "--"))
1032
1033 (defun vc-git-rename-file (old new)
1034 (vc-git-command nil 0 (list old new) "mv" "-f" "--"))
1035
1036 (defvar vc-git-extra-menu-map
1037 (let ((map (make-sparse-keymap)))
1038 (define-key map [git-grep]
1039 '(menu-item "Git grep..." vc-git-grep
1040 :help "Run the `git grep' command"))
1041 (define-key map [git-sn]
1042 '(menu-item "Stash a Snapshot" vc-git-stash-snapshot
1043 :help "Stash the current state of the tree and keep the current state"))
1044 (define-key map [git-st]
1045 '(menu-item "Create Stash..." vc-git-stash
1046 :help "Stash away changes"))
1047 (define-key map [git-ss]
1048 '(menu-item "Show Stash..." vc-git-stash-show
1049 :help "Show stash contents"))
1050 map))
1051
1052 (defun vc-git-extra-menu () vc-git-extra-menu-map)
1053
1054 (defun vc-git-extra-status-menu () vc-git-extra-menu-map)
1055
1056 (defun vc-git-root (file)
1057 (or (vc-file-getprop file 'git-root)
1058 (vc-file-setprop file 'git-root (vc-find-root file ".git"))))
1059
1060 ;; grep-compute-defaults autoloads grep.
1061 (declare-function grep-read-regexp "grep" ())
1062 (declare-function grep-read-files "grep" (regexp))
1063 (declare-function grep-expand-template "grep"
1064 (template &optional regexp files dir excl))
1065
1066 ;; Derived from `lgrep'.
1067 (defun vc-git-grep (regexp &optional files dir)
1068 "Run git grep, searching for REGEXP in FILES in directory DIR.
1069 The search is limited to file names matching shell pattern FILES.
1070 FILES may use abbreviations defined in `grep-files-aliases', e.g.
1071 entering `ch' is equivalent to `*.[ch]'.
1072
1073 With \\[universal-argument] prefix, you can edit the constructed shell command line
1074 before it is executed.
1075 With two \\[universal-argument] prefixes, directly edit and run `grep-command'.
1076
1077 Collect output in a buffer. While git grep runs asynchronously, you
1078 can use \\[next-error] (M-x next-error), or \\<grep-mode-map>\\[compile-goto-error] \
1079 in the grep output buffer,
1080 to go to the lines where grep found matches.
1081
1082 This command shares argument histories with \\[rgrep] and \\[grep]."
1083 (interactive
1084 (progn
1085 (grep-compute-defaults)
1086 (cond
1087 ((equal current-prefix-arg '(16))
1088 (list (read-from-minibuffer "Run: " "git grep"
1089 nil nil 'grep-history)
1090 nil))
1091 (t (let* ((regexp (grep-read-regexp))
1092 (files (grep-read-files regexp))
1093 (dir (read-directory-name "In directory: "
1094 nil default-directory t)))
1095 (list regexp files dir))))))
1096 (require 'grep)
1097 (when (and (stringp regexp) (> (length regexp) 0))
1098 (let ((command regexp))
1099 (if (null files)
1100 (if (string= command "git grep")
1101 (setq command nil))
1102 (setq dir (file-name-as-directory (expand-file-name dir)))
1103 (setq command
1104 (grep-expand-template "git --no-pager grep -n -e <R> -- <F>"
1105 regexp files))
1106 (when command
1107 (if (equal current-prefix-arg '(4))
1108 (setq command
1109 (read-from-minibuffer "Confirm: "
1110 command nil nil 'grep-history))
1111 (add-to-history 'grep-history command))))
1112 (when command
1113 (let ((default-directory dir)
1114 (compilation-environment (cons "PAGER=" compilation-environment)))
1115 ;; Setting process-setup-function makes exit-message-function work
1116 ;; even when async processes aren't supported.
1117 (compilation-start command 'grep-mode))
1118 (if (eq next-error-last-buffer (current-buffer))
1119 (setq default-directory dir))))))
1120
1121 ;; Everywhere but here, follows vc-git-command, which uses vc-do-command
1122 ;; from vc-dispatcher.
1123 (autoload 'vc-resynch-buffer "vc-dispatcher")
1124
1125 (defun vc-git-stash (name)
1126 "Create a stash."
1127 (interactive "sStash name: ")
1128 (let ((root (vc-git-root default-directory)))
1129 (when root
1130 (vc-git--call nil "stash" "save" name)
1131 (vc-resynch-buffer root t t))))
1132
1133 (defun vc-git-stash-show (name)
1134 "Show the contents of stash NAME."
1135 (interactive "sStash name: ")
1136 (vc-setup-buffer "*vc-git-stash*")
1137 (vc-git-command "*vc-git-stash*" 'async nil "stash" "show" "-p" name)
1138 (set-buffer "*vc-git-stash*")
1139 (diff-mode)
1140 (setq buffer-read-only t)
1141 (pop-to-buffer (current-buffer)))
1142
1143 (defun vc-git-stash-apply (name)
1144 "Apply stash NAME."
1145 (interactive "sApply stash: ")
1146 (vc-git-command "*vc-git-stash*" 0 nil "stash" "apply" "-q" name)
1147 (vc-resynch-buffer (vc-git-root default-directory) t t))
1148
1149 (defun vc-git-stash-pop (name)
1150 "Pop stash NAME."
1151 (interactive "sPop stash: ")
1152 (vc-git-command "*vc-git-stash*" 0 nil "stash" "pop" "-q" name)
1153 (vc-resynch-buffer (vc-git-root default-directory) t t))
1154
1155 (defun vc-git-stash-snapshot ()
1156 "Create a stash with the current tree state."
1157 (interactive)
1158 (vc-git--call nil "stash" "save"
1159 (let ((ct (current-time)))
1160 (concat
1161 (format-time-string "Snapshot on %Y-%m-%d" ct)
1162 (format-time-string " at %H:%M" ct))))
1163 (vc-git-command "*vc-git-stash*" 0 nil "stash" "apply" "-q" "stash@{0}")
1164 (vc-resynch-buffer (vc-git-root default-directory) t t))
1165
1166 (defun vc-git-stash-list ()
1167 (delete
1168 ""
1169 (split-string
1170 (replace-regexp-in-string
1171 "^stash@" " " (vc-git--run-command-string nil "stash" "list"))
1172 "\n")))
1173
1174 (defun vc-git-stash-get-at-point (point)
1175 (save-excursion
1176 (goto-char point)
1177 (beginning-of-line)
1178 (if (looking-at "^ +\\({[0-9]+}\\):")
1179 (match-string 1)
1180 (error "Cannot find stash at point"))))
1181
1182 ;; vc-git-stash-delete-at-point must be called from a vc-dir buffer.
1183 (declare-function vc-dir-refresh "vc-dir" ())
1184
1185 (defun vc-git-stash-delete-at-point ()
1186 (interactive)
1187 (let ((stash (vc-git-stash-get-at-point (point))))
1188 (when (y-or-n-p (format "Remove stash %s ? " stash))
1189 (vc-git--run-command-string nil "stash" "drop" (format "stash@%s" stash))
1190 (vc-dir-refresh))))
1191
1192 (defun vc-git-stash-show-at-point ()
1193 (interactive)
1194 (vc-git-stash-show (format "stash@%s" (vc-git-stash-get-at-point (point)))))
1195
1196 (defun vc-git-stash-apply-at-point ()
1197 (interactive)
1198 (vc-git-stash-apply (format "stash@%s" (vc-git-stash-get-at-point (point)))))
1199
1200 (defun vc-git-stash-pop-at-point ()
1201 (interactive)
1202 (vc-git-stash-pop (format "stash@%s" (vc-git-stash-get-at-point (point)))))
1203
1204 (defun vc-git-stash-menu (e)
1205 (interactive "e")
1206 (vc-dir-at-event e (popup-menu vc-git-stash-menu-map e)))
1207
1208 \f
1209 ;;; Internal commands
1210
1211 (defun vc-git-command (buffer okstatus file-or-list &rest flags)
1212 "A wrapper around `vc-do-command' for use in vc-git.el.
1213 The difference to vc-do-command is that this function always invokes
1214 `vc-git-program'."
1215 (let ((coding-system-for-read vc-git-commits-coding-system)
1216 (coding-system-for-write vc-git-commits-coding-system))
1217 (apply 'vc-do-command (or buffer "*vc*") okstatus vc-git-program
1218 ;; http://debbugs.gnu.org/16897
1219 (unless (and (not (cdr-safe file-or-list))
1220 (let ((file (or (car-safe file-or-list)
1221 file-or-list)))
1222 (and file
1223 (eq ?/ (aref file (1- (length file))))
1224 (equal file (vc-git-root file)))))
1225 file-or-list)
1226 (cons "--no-pager" flags))))
1227
1228 (defun vc-git--empty-db-p ()
1229 "Check if the git db is empty (no commit done yet)."
1230 (let (process-file-side-effects)
1231 (not (eq 0 (vc-git--call nil "rev-parse" "--verify" "HEAD")))))
1232
1233 (defun vc-git--call (buffer command &rest args)
1234 ;; We don't need to care the arguments. If there is a file name, it
1235 ;; is always a relative one. This works also for remote
1236 ;; directories. We enable `inhibit-null-byte-detection', otherwise
1237 ;; Tramp's eol conversion might be confused.
1238 (let ((inhibit-null-byte-detection t)
1239 (coding-system-for-read vc-git-commits-coding-system)
1240 (coding-system-for-write vc-git-commits-coding-system)
1241 (process-environment (cons "PAGER=" process-environment)))
1242 (apply 'process-file vc-git-program nil buffer nil command args)))
1243
1244 (defun vc-git--out-ok (command &rest args)
1245 (zerop (apply 'vc-git--call '(t nil) command args)))
1246
1247 (defun vc-git--run-command-string (file &rest args)
1248 "Run a git command on FILE and return its output as string.
1249 FILE can be nil."
1250 (let* ((ok t)
1251 (str (with-output-to-string
1252 (with-current-buffer standard-output
1253 (unless (apply 'vc-git--out-ok
1254 (if file
1255 (append args (list (file-relative-name
1256 file)))
1257 args))
1258 (setq ok nil))))))
1259 (and ok str)))
1260
1261 (defun vc-git-symbolic-commit (commit)
1262 "Translate COMMIT string into symbolic form.
1263 Returns nil if not possible."
1264 (and commit
1265 (let ((name (with-temp-buffer
1266 (and
1267 (vc-git--out-ok "name-rev" "--name-only" commit)
1268 (goto-char (point-min))
1269 (= (forward-line 2) 1)
1270 (bolp)
1271 (buffer-substring-no-properties (point-min)
1272 (1- (point-max)))))))
1273 (and name (not (string= name "undefined")) name))))
1274
1275 (provide 'vc-git)
1276
1277 ;;; vc-git.el ends here