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