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