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