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