dynwind fixes
[bpt/emacs.git] / lisp / vc / vc-hg.el
CommitLineData
0d42eb3e 1;;; vc-hg.el --- VC backend for the mercurial version control system -*- lexical-binding: t -*-
61223448 2
ba318903 3;; Copyright (C) 2006-2014 Free Software Foundation, Inc.
248c6645 4
61223448 5;; Author: Ivan Kanis
34dc21db 6;; Maintainer: emacs-devel@gnu.org
9766adfb 7;; Keywords: vc tools
bd78fa1d 8;; Package: vc
a58b1e40
GM
9
10;; This file is part of GNU Emacs.
11
eb3fa2cf 12;; GNU Emacs is free software: you can redistribute it and/or modify
61223448 13;; it under the terms of the GNU General Public License as published by
eb3fa2cf
GM
14;; the Free Software Foundation, either version 3 of the License, or
15;; (at your option) any later version.
a58b1e40
GM
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
61223448 22;; You should have received a copy of the GNU General Public License
eb3fa2cf 23;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
61223448
DN
24
25;;; Commentary:
26
27;; This is a mercurial version control backend
28
248c6645
DN
29;;; Thanks:
30
31;;; Bugs:
32
33;;; Installation:
34
35;;; Todo:
36
ec4149ff 37;; 1) Implement the rest of the vc interface. See the comment at the
a07e665b
DN
38;; beginning of vc.el. The current status is:
39
40;; FUNCTION NAME STATUS
a6ea7ffc
DN
41;; BACKEND PROPERTIES
42;; * revision-granularity OK
43;; STATE-QUERYING FUNCTIONS
a07e665b
DN
44;; * registered (file) OK
45;; * state (file) OK
ec4149ff 46;; - state-heuristic (file) NOT NEEDED
a779ddf0
DN
47;; - dir-status (dir update-function) OK
48;; - dir-status-files (dir files ds uf) OK
3151c2ff
DN
49;; - dir-extra-headers (dir) OK
50;; - dir-printer (fileinfo) OK
ac3f4c6f 51;; * working-revision (file) OK
a07e665b 52;; - latest-on-branch-p (file) ??
70e2f6c7 53;; * checkout-model (files) OK
a272e668 54;; - workfile-unchanged-p (file) OK
a07e665b 55;; - mode-line-string (file) NOT NEEDED
a07e665b 56;; STATE-CHANGING FUNCTIONS
8cdd17b4 57;; * register (files &optional rev comment) OK
a6ea7ffc 58;; * create-repo () OK
ec4149ff 59;; - init-revision () NOT NEEDED
a07e665b
DN
60;; - responsible-p (file) OK
61;; - could-register (file) OK
62;; - receive-file (file rev) ?? PROBABLY NOT NEEDED
0add0959 63;; - unregister (file) OK
8cdd17b4 64;; * checkin (files rev comment) OK
ec4149ff 65;; * find-revision (file rev buffer) OK
a6ea7ffc 66;; * checkout (file &optional editable rev) OK
a07e665b 67;; * revert (file &optional contents-done) OK
62754d29 68;; - rollback (files) ?? PROBABLY NOT NEEDED
a07e665b
DN
69;; - merge (file rev1 rev2) NEEDED
70;; - merge-news (file) NEEDED
ec4149ff 71;; - steal-lock (file &optional revision) NOT NEEDED
a07e665b 72;; HISTORY FUNCTIONS
662c5698 73;; * print-log (files buffer &optional shortlog start-revision limit) OK
a07e665b 74;; - log-view-mode () OK
ec4149ff 75;; - show-log-entry (revision) NOT NEEDED, DEFAULT IS GOOD
a07e665b
DN
76;; - comment-history (file) NOT NEEDED
77;; - update-changelog (files) NOT NEEDED
8cdd17b4 78;; * diff (files &optional rev1 rev2 buffer) OK
54a2247d 79;; - revision-completion-table (files) OK?
a07e665b
DN
80;; - annotate-command (file buf &optional rev) OK
81;; - annotate-time () OK
ec4149ff 82;; - annotate-current-time () NOT NEEDED
a07e665b 83;; - annotate-extract-revision-at-line () OK
370fded4 84;; TAG SYSTEM
3112e400
SPM
85;; - create-tag (dir name branchp) OK
86;; - retrieve-tag (dir name update) OK FIXME UPDATE BUFFERS
a07e665b
DN
87;; MISCELLANEOUS
88;; - make-version-backups-p (file) ??
62754d29 89;; - repository-hostname (dirname) ??
ec4149ff
DN
90;; - previous-revision (file rev) OK
91;; - next-revision (file rev) OK
a07e665b
DN
92;; - check-headers () ??
93;; - clear-headers () ??
94;; - delete-file (file) TEST IT
95;; - rename-file (old new) OK
49596095 96;; - find-file-hook () added for bug#10709
61223448 97
ec4149ff 98;; 2) Implement Stefan Monnier's advice:
248c6645
DN
99;; vc-hg-registered and vc-hg-state
100;; Both of those functions should be super extra careful to fail gracefully in
a07e665b 101;; unexpected circumstances. The reason this is important is that any error
248c6645
DN
102;; there will prevent the user from even looking at the file :-(
103;; Ideally, just like in vc-arch and vc-cvs, checking that the file is under
104;; mercurial's control and extracting the current revision should be done
105;; without even using `hg' (this way even if you don't have `hg' installed,
106;; Emacs is able to tell you this file is under mercurial's control).
61223448 107
248c6645 108;;; History:
11a4edc2 109;;
61223448
DN
110
111;;; Code:
112
113(eval-when-compile
a464a6c7 114 (require 'cl-lib)
10c7e431
DN
115 (require 'vc)
116 (require 'vc-dir))
61223448 117
61223448
DN
118;;; Customization options
119
67b0de11
CY
120(defgroup vc-hg nil
121 "VC Mercurial (hg) backend."
122 :version "24.1"
123 :group 'vc)
124
61223448 125(defcustom vc-hg-global-switches nil
9201cc28 126 "Global switches to pass to any Hg command."
61223448
DN
127 :type '(choice (const :tag "None" nil)
128 (string :tag "Argument String")
1d9d1fcc 129 (repeat :tag "Argument List" :value ("") string))
a07e665b 130 :version "22.2"
67b0de11 131 :group 'vc-hg)
61223448 132
1d9d1fcc
GM
133(defcustom vc-hg-diff-switches t ; Hg doesn't support common args like -u
134 "String or list of strings specifying switches for Hg diff under VC.
135If nil, use the value of `vc-diff-switches'. If t, use no switches."
82ee74d6 136 :type '(choice (const :tag "Unspecified" nil)
72169e55
SS
137 (const :tag "None" t)
138 (string :tag "Argument String")
139 (repeat :tag "Argument List" :value ("") string))
82ee74d6 140 :version "23.1"
67b0de11 141 :group 'vc-hg)
82ee74d6 142
88bf1bec
SM
143(defcustom vc-hg-program "hg"
144 "Name of the Mercurial executable (excluding any arguments)."
145 :type 'string
67b0de11 146 :group 'vc-hg)
33f6cf7b
CY
147
148(defcustom vc-hg-root-log-format
90b15d91
SPM
149 `(,(concat "{rev}:{ifeq(branch, 'default','', '{branch}')}"
150 ":{bookmarks}:{tags}:{author|person}"
151 " {date|shortdate} {desc|firstline}\\n")
152 ,(concat "^\\(?:[+@o x|-]*\\)" ;Graph data.
153 "\\([0-9]+\\):\\([^:]*\\)"
154 ":\\([^:]*\\):\\([^:]*\\):\\(.*?\\)"
155 "[ \t]+\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)")
33f6cf7b 156 ((1 'log-view-message-face)
90b15d91
SPM
157 (2 'change-log-file)
158 (3 'change-log-list)
159 (4 'change-log-conditionals)
160 (5 'change-log-name)
161 (6 'change-log-date)))
bb7cdf58 162 "Mercurial log template for `vc-hg-print-log' short format.
33f6cf7b
CY
163This should be a list (TEMPLATE REGEXP KEYWORDS), where TEMPLATE
164is the \"--template\" argument string to pass to Mercurial,
165REGEXP is a regular expression matching the resulting Mercurial
166output, and KEYWORDS is a list of `font-lock-keywords' for
167highlighting the Log View buffer."
168 :type '(list string string (repeat sexp))
67b0de11 169 :group 'vc-hg
90b15d91 170 :version "24.5")
33f6cf7b 171
8cdd17b4
ER
172\f
173;;; Properties of the backend
174
659114fd
CY
175(defvar vc-hg-history nil)
176
70e2f6c7 177(defun vc-hg-revision-granularity () 'repository)
0d42eb3e 178(defun vc-hg-checkout-model (_files) 'implicit)
8cdd17b4 179
61223448
DN
180;;; State querying functions
181
11a4edc2
SM
182;;;###autoload (defun vc-hg-registered (file)
183;;;###autoload "Return non-nil if FILE is registered with hg."
184;;;###autoload (if (vc-find-root file ".hg") ; short cut
185;;;###autoload (progn
af314ba0 186;;;###autoload (load "vc-hg" nil t)
11a4edc2
SM
187;;;###autoload (vc-hg-registered file))))
188
6772c8e1 189;; Modeled after the similar function in vc-bzr.el
61223448 190(defun vc-hg-registered (file)
248c6645 191 "Return non-nil if FILE is registered with hg."
a6ea7ffc 192 (when (vc-hg-root file) ; short cut
6c47d819 193 (let ((state (vc-hg-state file))) ; expensive
f1e22ada 194 (and state (not (memq state '(ignored unregistered)))))))
61223448
DN
195
196(defun vc-hg-state (file)
248c6645 197 "Hg-specific version of `vc-state'."
62754d29 198 (let*
b33ac3b7 199 ((status nil)
cc63d28f 200 (default-directory (file-name-directory file))
b33ac3b7 201 (out
72169e55
SS
202 (with-output-to-string
203 (with-current-buffer
204 standard-output
205 (setq status
206 (condition-case nil
207 ;; Ignore all errors.
662c5698 208 (let ((process-environment
808ecc4e
CY
209 ;; Avoid localization of messages so we
210 ;; can parse the output.
681b88dd 211 (append (list "TERM=dumb" "LANGUAGE=C")
808ecc4e
CY
212 process-environment)))
213 (process-file
88bf1bec 214 vc-hg-program nil t nil
681b88dd
SM
215 "--config" "alias.status=status"
216 "--config" "defaults.status="
808ecc4e 217 "status" "-A" (file-relative-name file)))
72169e55
SS
218 ;; Some problem happened. E.g. We can't find an `hg'
219 ;; executable.
220 (error nil)))))))
b33ac3b7 221 (when (eq 0 status)
72169e55
SS
222 (when (null (string-match ".*: No such file or directory$" out))
223 (let ((state (aref out 0)))
224 (cond
225 ((eq state ?=) 'up-to-date)
226 ((eq state ?A) 'added)
227 ((eq state ?M) 'edited)
228 ((eq state ?I) 'ignored)
229 ((eq state ?R) 'removed)
230 ((eq state ?!) 'missing)
231 ((eq state ??) 'unregistered)
99d99081 232 ((eq state ?C) 'up-to-date) ;; Older mercurial versions use this.
72169e55 233 (t 'up-to-date)))))))
a6ea7ffc 234
ac3f4c6f
ER
235(defun vc-hg-working-revision (file)
236 "Hg-specific version of `vc-working-revision'."
f4be80b7
SPM
237 (or (ignore-errors
238 (with-output-to-string
239 (vc-hg-command standard-output 0 file
240 "parent" "--template" "{rev}")))
241 "0"))
61223448 242
61223448
DN
243;;; History functions
244
02532fbc
SS
245(defcustom vc-hg-log-switches nil
246 "String or list of strings specifying switches for hg log under VC."
247 :type '(choice (const :tag "None" nil)
248 (string :tag "Argument String")
249 (repeat :tag "Argument List" :value ("") string))
250 :group 'vc-hg)
251
712b9732
GM
252(autoload 'vc-setup-buffer "vc-dispatcher")
253
90b15d91
SPM
254(defvar vc-hg-log-graph nil
255 "If non-nil, use `--graph' in the short log output.")
256
86b5e14c 257(defun vc-hg-print-log (files buffer &optional shortlog start-revision limit)
bb7cdf58
GM
258 "Print commit log associated with FILES into specified BUFFER.
259If SHORTLOG is non-nil, use a short format based on `vc-hg-root-log-format'.
260If START-REVISION is non-nil, it is the newest revision to show.
261If LIMIT is non-nil, show no more than this many entries."
7c1912af
DN
262 ;; `vc-do-command' creates the buffer, but we need it before running
263 ;; the command.
264 (vc-setup-buffer buffer)
265 ;; If the buffer exists from a previous invocation it might be
266 ;; read-only.
267 (let ((inhibit-read-only t))
6653c6b7
DN
268 (with-current-buffer
269 buffer
32ba3abc 270 (apply 'vc-hg-command buffer 0 files "log"
3fb87bf5 271 (nconc
90b4237a 272 (when start-revision (list (format "-r%s:0" start-revision)))
6616006b 273 (when limit (list "-l" (format "%s" limit)))
90b15d91
SPM
274 (when shortlog `(,@(if vc-hg-log-graph '("--graph"))
275 "--template"
276 ,(car vc-hg-root-log-format)))
6616006b 277 vc-hg-log-switches)))))
61223448 278
d797e643
DN
279(defvar log-view-message-re)
280(defvar log-view-file-re)
281(defvar log-view-font-lock-keywords)
6653c6b7 282(defvar log-view-per-file-logs)
33f6cf7b 283(defvar log-view-expanded-log-entry-function)
d797e643 284
4211679b 285(define-derived-mode vc-hg-log-view-mode log-view-mode "Hg-Log-View"
f0230324 286 (require 'add-log) ;; we need the add-log faces
6653c6b7
DN
287 (set (make-local-variable 'log-view-file-re) "\\`a\\`")
288 (set (make-local-variable 'log-view-per-file-logs) nil)
d797e643 289 (set (make-local-variable 'log-view-message-re)
31527c56 290 (if (eq vc-log-view-type 'short)
33f6cf7b 291 (cadr vc-hg-root-log-format)
72169e55 292 "^changeset:[ \t]*\\([0-9]+\\):\\(.+\\)"))
33f6cf7b
CY
293 ;; Allow expanding short log entries
294 (when (eq vc-log-view-type 'short)
295 (setq truncate-lines t)
296 (set (make-local-variable 'log-view-expanded-log-entry-function)
297 'vc-hg-expanded-log-entry))
d797e643 298 (set (make-local-variable 'log-view-font-lock-keywords)
31527c56 299 (if (eq vc-log-view-type 'short)
33f6cf7b
CY
300 (list (cons (nth 1 vc-hg-root-log-format)
301 (nth 2 vc-hg-root-log-format)))
302 (append
303 log-view-font-lock-keywords
304 '(
305 ;; Handle the case:
306 ;; user: FirstName LastName <foo@bar>
307 ("^user:[ \t]+\\([^<(]+?\\)[ \t]*[(<]\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)[>)]"
308 (1 'change-log-name)
309 (2 'change-log-email))
310 ;; Handle the cases:
311 ;; user: foo@bar
312 ;; and
313 ;; user: foo
314 ("^user:[ \t]+\\([A-Za-z0-9_.+-]+\\(?:@[A-Za-z0-9_.-]+\\)?\\)"
315 (1 'change-log-email))
316 ("^date: \\(.+\\)" (1 'change-log-date))
317 ("^tag: +\\([^ ]+\\)$" (1 'highlight))
318 ("^summary:[ \t]+\\(.+\\)" (1 'log-view-message)))))))
d797e643 319
712b9732
GM
320(autoload 'vc-switches "vc")
321
8cdd17b4 322(defun vc-hg-diff (files &optional oldvers newvers buffer)
5b5afd50 323 "Get a difference report using hg between two revisions of FILES."
6653c6b7 324 (let* ((firstfile (car files))
72169e55 325 (working (and firstfile (vc-working-revision firstfile))))
ec4149ff
DN
326 (when (and (equal oldvers working) (not newvers))
327 (setq oldvers nil))
328 (when (and (not oldvers) newvers)
329 (setq oldvers working))
efa3639b 330 (apply #'vc-hg-command (or buffer "*vc-diff*") nil files "diff"
72169e55
SS
331 (append
332 (vc-switches 'hg 'diff)
333 (when oldvers
334 (if newvers
335 (list "-r" oldvers "-r" newvers)
336 (list "-r" oldvers)))))))
cdaf01cc 337
33f6cf7b
CY
338(defun vc-hg-expanded-log-entry (revision)
339 (with-temp-buffer
340 (vc-hg-command t nil nil "log" "-r" revision)
341 (goto-char (point-min))
342 (unless (eobp)
343 ;; Indent the expanded log entry.
344 (indent-region (point-min) (point-max) 2)
345 (goto-char (point-max))
346 (buffer-string))))
347
87d1a48e
SM
348(defun vc-hg-revision-table (files)
349 (let ((default-directory (file-name-directory (car files))))
34b7fb85 350 (with-temp-buffer
004a00f4 351 (vc-hg-command t nil files "log" "--template" "{rev} ")
62754d29 352 (split-string
34b7fb85
DN
353 (buffer-substring-no-properties (point-min) (point-max))))))
354
6772c8e1 355;; Modeled after the similar function in vc-cvs.el
87d1a48e 356(defun vc-hg-revision-completion-table (files)
0d42eb3e
SM
357 (letrec ((table (lazy-completion-table
358 table (lambda () (vc-hg-revision-table files)))))
eff23ff3 359 table))
34b7fb85 360
5b5afd50 361(defun vc-hg-annotate-command (file buffer &optional revision)
cdaf01cc 362 "Execute \"hg annotate\" on FILE, inserting the contents in BUFFER.
5b5afd50 363Optional arg REVISION is a revision to annotate from."
d1e4c403
DN
364 (vc-hg-command buffer 0 file "annotate" "-d" "-n" "--follow"
365 (when revision (concat "-r" revision))))
cdaf01cc 366
f8bd9ac6 367(declare-function vc-annotate-convert-time "vc-annotate" (time))
cdaf01cc 368
11a4edc2
SM
369;; The format for one line output by "hg annotate -d -n" looks like this:
370;;215 Wed Jun 20 21:22:58 2007 -0700: CONTENTS
371;; i.e: VERSION_NUMBER DATE: CONTENTS
4064ff25
DN
372;; If the user has set the "--follow" option, the output looks like:
373;;215 Wed Jun 20 21:22:58 2007 -0700 foo.c: CONTENTS
374;; i.e. VERSION_NUMBER DATE FILENAME: CONTENTS
375(defconst vc-hg-annotate-re
2d3fa3e5 376 "^[ \t]*\\([0-9]+\\) \\(.\\{30\\}\\)\\(?:\\(: \\)\\|\\(?: +\\([^:\n]+\\(?::\\(?:[^: \n][^:\n]*\\)?\\)*\\): \\)\\)")
cdaf01cc
DN
377
378(defun vc-hg-annotate-time ()
379 (when (looking-at vc-hg-annotate-re)
380 (goto-char (match-end 0))
11a4edc2 381 (vc-annotate-convert-time
cdaf01cc
DN
382 (date-to-time (match-string-no-properties 2)))))
383
384(defun vc-hg-annotate-extract-revision-at-line ()
385 (save-excursion
386 (beginning-of-line)
d1e4c403
DN
387 (when (looking-at vc-hg-annotate-re)
388 (if (match-beginning 3)
389 (match-string-no-properties 1)
390 (cons (match-string-no-properties 1)
3112e400
SPM
391 (expand-file-name (match-string-no-properties 4)
392 (vc-hg-root default-directory)))))))
393
394;;; Tag system
395
396(defun vc-hg-create-tag (dir name branchp)
397 "Attach the tag NAME to the state of the working copy."
398 (let ((default-directory dir))
399 (and (vc-hg-command nil 0 nil "status")
400 (vc-hg-command nil 0 nil (if branchp "bookmark" "tag") name))))
401
402(defun vc-hg-retrieve-tag (dir name update)
403 "Retrieve the version tagged by NAME of all registered files at or below DIR."
404 (let ((default-directory dir))
405 (vc-hg-command nil 0 nil "update" name)
406 ;; FIXME: update buffers if `update' is true
407 ;; TODO: update *vc-change-log* buffer so can see @ if --graph
408 ))
409
410;;; Miscellaneous
cdaf01cc 411
0d42eb3e 412(defun vc-hg-previous-revision (_file rev)
cdaf01cc
DN
413 (let ((newrev (1- (string-to-number rev))))
414 (when (>= newrev 0)
415 (number-to-string newrev))))
61223448 416
0d42eb3e 417(defun vc-hg-next-revision (_file rev)
a07e665b 418 (let ((newrev (1+ (string-to-number rev)))
72169e55
SS
419 (tip-revision
420 (with-temp-buffer
3ddbf803 421 (vc-hg-command t 0 nil "tip" "--style=default")
72169e55
SS
422 (goto-char (point-min))
423 (re-search-forward "^changeset:[ \t]*\\([0-9]+\\):")
424 (string-to-number (match-string-no-properties 1)))))
5b5afd50
ER
425 ;; We don't want to exceed the maximum possible revision number, ie
426 ;; the tip revision.
427 (when (<= newrev tip-revision)
a07e665b
DN
428 (number-to-string newrev))))
429
6772c8e1 430;; Modeled after the similar function in vc-bzr.el
a07e665b
DN
431(defun vc-hg-delete-file (file)
432 "Delete FILE and delete it in the hg repository."
433 (condition-case ()
434 (delete-file file)
435 (file-error nil))
34b7fb85 436 (vc-hg-command nil 0 file "remove" "--after" "--force"))
a07e665b 437
6772c8e1 438;; Modeled after the similar function in vc-bzr.el
a07e665b
DN
439(defun vc-hg-rename-file (old new)
440 "Rename file from OLD to NEW using `hg mv'."
bfd57731 441 (vc-hg-command nil 0 new "mv" old))
a07e665b 442
0d42eb3e 443(defun vc-hg-register (files &optional _rev _comment)
8cdd17b4 444 "Register FILES under hg.
248c6645
DN
445REV is ignored.
446COMMENT is ignored."
34b7fb85 447 (vc-hg-command nil 0 files "add"))
8cdd17b4
ER
448
449(defun vc-hg-create-repo ()
450 "Create a new Mercurial repository."
34b7fb85 451 (vc-hg-command nil 0 nil "init"))
248c6645 452
a07e665b
DN
453(defalias 'vc-hg-responsible-p 'vc-hg-root)
454
6772c8e1 455;; Modeled after the similar function in vc-bzr.el
a07e665b
DN
456(defun vc-hg-could-register (file)
457 "Return non-nil if FILE could be registered under hg."
458 (and (vc-hg-responsible-p file) ; shortcut
459 (condition-case ()
460 (with-temp-buffer
461 (vc-hg-command t nil file "add" "--dry-run"))
462 ;; The command succeeds with no output if file is
463 ;; registered.
464 (error))))
465
0add0959
SPM
466(defun vc-hg-unregister (file)
467 "Unregister FILE from hg."
468 (vc-hg-command nil 0 file "forget"))
a07e665b 469
e97a42c1
SM
470(declare-function log-edit-extract-headers "log-edit" (headers string))
471
0d42eb3e 472(defun vc-hg-checkin (files _rev comment)
4211679b 473 "Hg-specific version of `vc-backend-checkin'.
cdaf01cc 474REV is ignored."
3fb87bf5 475 (apply 'vc-hg-command nil 0 files
e97a42c1 476 (nconc (list "commit" "-m")
fab43c76
DN
477 (log-edit-extract-headers '(("Author" . "--user")
478 ("Date" . "--date"))
e97a42c1 479 comment))))
cdaf01cc 480
ac3f4c6f 481(defun vc-hg-find-revision (file rev buffer)
248c6645
DN
482 (let ((coding-system-for-read 'binary)
483 (coding-system-for-write 'binary))
248c6645 484 (if rev
72169e55 485 (vc-hg-command buffer 0 file "cat" "-r" rev)
34b7fb85 486 (vc-hg-command buffer 0 file "cat"))))
a07e665b 487
ab419665
XF
488(defun vc-hg-find-ignore-file (file)
489 "Return the root directory of the repository of FILE."
490 (expand-file-name ".hgignore"
491 (vc-hg-root file)))
7aa7fff0 492
6772c8e1 493;; Modeled after the similar function in vc-bzr.el
0d42eb3e 494(defun vc-hg-checkout (file &optional _editable rev)
a6ea7ffc
DN
495 "Retrieve a revision of FILE.
496EDITABLE is ignored.
497REV is the revision to check out into WORKFILE."
498 (let ((coding-system-for-read 'binary)
499 (coding-system-for-write 'binary))
500 (with-current-buffer (or (get-file-buffer file) (current-buffer))
501 (if rev
34b7fb85
DN
502 (vc-hg-command t 0 file "cat" "-r" rev)
503 (vc-hg-command t 0 file "cat")))))
248c6645 504
49596095
GM
505(defun vc-hg-resolve-when-done ()
506 "Call \"hg resolve -m\" if the conflict markers have been removed."
507 (save-excursion
508 (goto-char (point-min))
509 (unless (re-search-forward "^<<<<<<< " nil t)
510 (vc-hg-command nil 0 buffer-file-name "resolve" "-m")
511 ;; Remove the hook so that it is not called multiple times.
512 (remove-hook 'after-save-hook 'vc-hg-resolve-when-done t))))
513
514(defun vc-hg-find-file-hook ()
515 (when (and buffer-file-name
516 (file-exists-p (concat buffer-file-name ".orig"))
517 ;; Hg does not seem to have a "conflict" status, eg
518 ;; hg http://bz.selenic.com/show_bug.cgi?id=2724
519 (memq (vc-file-getprop buffer-file-name 'vc-state)
520 '(edited conflict))
521 ;; Maybe go on to check that "hg resolve -l" says "U"?
522 ;; If "hg resolve -l" says there's a conflict but there are no
523 ;; conflict markers, it's not clear what we should do.
524 (save-excursion
525 (goto-char (point-min))
526 (re-search-forward "^<<<<<<< " nil t)))
527 ;; Hg may not recognize "conflict" as a state, but we can do better.
528 (vc-file-setprop buffer-file-name 'vc-state 'conflict)
529 (smerge-start-session)
530 (add-hook 'after-save-hook 'vc-hg-resolve-when-done nil t)
531 (message "There are unresolved conflicts in this file")))
532
533
6772c8e1 534;; Modeled after the similar function in vc-bzr.el
a272e668
DN
535(defun vc-hg-workfile-unchanged-p (file)
536 (eq 'up-to-date (vc-hg-state file)))
537
6772c8e1 538;; Modeled after the similar function in vc-bzr.el
b33ac3b7
DN
539(defun vc-hg-revert (file &optional contents-done)
540 (unless contents-done
34b7fb85 541 (with-temp-buffer (vc-hg-command t 0 file "revert"))))
b33ac3b7 542
f0230324
DN
543;;; Hg specific functionality.
544
f0230324
DN
545(defvar vc-hg-extra-menu-map
546 (let ((map (make-sparse-keymap)))
f0230324
DN
547 map))
548
549(defun vc-hg-extra-menu () vc-hg-extra-menu-map)
550
25a4ea6d 551(defun vc-hg-extra-status-menu () vc-hg-extra-menu-map)
6656ecaa 552
4a827e0a 553(defvar log-view-vc-backend)
f0230324 554
a464a6c7 555(cl-defstruct (vc-hg-extra-fileinfo
7db924c0
DN
556 (:copier nil)
557 (:constructor vc-hg-create-extra-fileinfo (rename-state extra-name))
558 (:conc-name vc-hg-extra-fileinfo->))
559 rename-state ;; rename or copy state
fe4f8695 560 extra-name) ;; original name for copies and rename targets, new name for
7db924c0 561
13ad7457 562(declare-function vc-default-dir-printer "vc-dir" (backend fileentry))
f8bd9ac6 563
13ad7457 564(defun vc-hg-dir-printer (info)
7db924c0
DN
565 "Pretty-printer for the vc-dir-fileinfo structure."
566 (let ((extra (vc-dir-fileinfo->extra info)))
13ad7457 567 (vc-default-dir-printer 'Hg info)
7db924c0
DN
568 (when extra
569 (insert (propertize
72169e55 570 (format " (%s %s)"
a464a6c7
SM
571 (pcase (vc-hg-extra-fileinfo->rename-state extra)
572 (`copied "copied from")
573 (`renamed-from "renamed from")
574 (`renamed-to "renamed to"))
72169e55
SS
575 (vc-hg-extra-fileinfo->extra-name extra))
576 'face 'font-lock-comment-face)))))
7db924c0 577
c1b51374 578(defun vc-hg-after-dir-status (update-function)
0d42eb3e 579 (let ((file nil)
72169e55
SS
580 (translation '((?= . up-to-date)
581 (?C . up-to-date)
582 (?A . added)
583 (?R . removed)
584 (?M . edited)
585 (?I . ignored)
586 (?! . missing)
587 (? . copy-rename-line)
588 (?? . unregistered)))
589 (translated nil)
590 (result nil)
591 (last-added nil)
592 (last-line-copy nil))
5ab612e8 593 (goto-char (point-min))
8fcaf22f 594 (while (not (eobp))
72169e55
SS
595 (setq translated (cdr (assoc (char-after) translation)))
596 (setq file
597 (buffer-substring-no-properties (+ (point) 2)
598 (line-end-position)))
599 (cond ((not translated)
600 (setq last-line-copy nil))
601 ((eq translated 'up-to-date)
602 (setq last-line-copy nil))
603 ((eq translated 'copy-rename-line)
604 ;; For copied files the output looks like this:
605 ;; A COPIED_FILE_NAME
606 ;; ORIGINAL_FILE_NAME
607 (setf (nth 2 last-added)
608 (vc-hg-create-extra-fileinfo 'copied file))
609 (setq last-line-copy t))
610 ((and last-line-copy (eq translated 'removed))
611 ;; For renamed files the output looks like this:
612 ;; A NEW_FILE_NAME
613 ;; ORIGINAL_FILE_NAME
614 ;; R ORIGINAL_FILE_NAME
615 ;; We need to adjust the previous entry to not think it is a copy.
616 (setf (vc-hg-extra-fileinfo->rename-state (nth 2 last-added))
617 'renamed-from)
618 (push (list file translated
619 (vc-hg-create-extra-fileinfo
620 'renamed-to (nth 0 last-added))) result)
621 (setq last-line-copy nil))
622 (t
623 (setq last-added (list file translated nil))
624 (push last-added result)
625 (setq last-line-copy nil)))
626 (forward-line))
c1b51374 627 (funcall update-function result)))
5ab612e8 628
712b9732
GM
629;; Follows vc-hg-command (or vc-do-async-command), which uses vc-do-command
630;; from vc-dispatcher.
631(declare-function vc-exec-after "vc-dispatcher" (code))
632;; Follows vc-exec-after.
633(declare-function vc-set-async-update "vc-dispatcher" (process-buffer))
634
c1b51374 635(defun vc-hg-dir-status (dir update-function)
7db924c0 636 (vc-hg-command (current-buffer) 'async dir "status" "-C")
9c750eba
SM
637 (vc-run-delayed
638 (vc-hg-after-dir-status update-function)))
8fcaf22f 639
0d42eb3e 640(defun vc-hg-dir-status-files (dir files _default-state update-function)
a779ddf0 641 (apply 'vc-hg-command (current-buffer) 'async dir "status" "-C" files)
9c750eba
SM
642 (vc-run-delayed
643 (vc-hg-after-dir-status update-function)))
a779ddf0 644
13ad7457 645(defun vc-hg-dir-extra-header (name &rest commands)
fe4f8695
SS
646 (concat (propertize name 'face 'font-lock-type-face)
647 (propertize
648 (with-temp-buffer
649 (apply 'vc-hg-command (current-buffer) 0 nil commands)
650 (buffer-substring-no-properties (point-min) (1- (point-max))))
651 'face 'font-lock-variable-name-face)))
652
13ad7457 653(defun vc-hg-dir-extra-headers (dir)
fe4f8695
SS
654 "Generate extra status headers for a Mercurial tree."
655 (let ((default-directory dir))
656 (concat
13ad7457
DN
657 (vc-hg-dir-extra-header "Root : " "root") "\n"
658 (vc-hg-dir-extra-header "Branch : " "id" "-b") "\n"
659 (vc-hg-dir-extra-header "Tags : " "id" "-t") ; "\n"
fe4f8695 660 ;; these change after each commit
13ad7457
DN
661 ;; (vc-hg-dir-extra-header "Local num : " "id" "-n") "\n"
662 ;; (vc-hg-dir-extra-header "Global id : " "id" "-i")
fe4f8695
SS
663 )))
664
31527c56
DN
665(defun vc-hg-log-incoming (buffer remote-location)
666 (vc-hg-command buffer 1 nil "incoming" "-n" (unless (string= remote-location "")
667 remote-location)))
f0230324 668
31527c56
DN
669(defun vc-hg-log-outgoing (buffer remote-location)
670 (vc-hg-command buffer 1 nil "outgoing" "-n" (unless (string= remote-location "")
671 remote-location)))
f0230324 672
004a00f4
DN
673(declare-function log-view-get-marked "log-view" ())
674
f0230324
DN
675;; XXX maybe also add key bindings for these functions.
676(defun vc-hg-push ()
677 (interactive)
678 (let ((marked-list (log-view-get-marked)))
679 (if marked-list
3fb87bf5
SS
680 (apply #'vc-hg-command
681 nil 0 nil
682 "push"
72169e55 683 (apply 'nconc
3fb87bf5
SS
684 (mapcar (lambda (arg) (list "-r" arg)) marked-list)))
685 (error "No log entries selected for push"))))
f0230324 686
8a4e6db8
SS
687(defvar vc-hg-error-regexp-alist nil
688 ;; 'hg pull' does not list modified files, so, for now, the only
689 ;; benefit of `vc-compilation-mode' is that one can get rid of
690 ;; *vc-hg* buffer with 'q' or 'z'.
691 ;; TODO: call 'hg incoming' before pull/merge to get the list of
692 ;; modified files
693 "Value of `compilation-error-regexp-alist' in *vc-hg* buffers.")
694
712b9732
GM
695(autoload 'vc-do-async-command "vc-dispatcher")
696
659114fd 697(defun vc-hg-pull (prompt)
a2b6e5d6
CY
698 "Issue a Mercurial pull command.
699If called interactively with a set of marked Log View buffers,
700call \"hg pull -r REVS\" to pull in the specified revisions REVS.
701
702With a prefix argument or if PROMPT is non-nil, prompt for a
703specific Mercurial pull command. The default is \"hg pull -u\",
704which fetches changesets from the default remote repository and
705then attempts to update the working directory."
659114fd
CY
706 (interactive "P")
707 (let (marked-list)
a2b6e5d6
CY
708 ;; The `vc-hg-pull' command existed before the `pull' VC action
709 ;; was implemented. Keep it for backward compatibility.
659114fd
CY
710 (if (and (called-interactively-p 'interactive)
711 (setq marked-list (log-view-get-marked)))
712 (apply #'vc-hg-command
713 nil 0 nil
714 "pull"
715 (apply 'nconc
716 (mapcar (lambda (arg) (list "-r" arg))
717 marked-list)))
718 (let* ((root (vc-hg-root default-directory))
719 (buffer (format "*vc-hg : %s*" (expand-file-name root)))
720 (command "pull")
5ceaac0c 721 (hg-program vc-hg-program)
a2b6e5d6
CY
722 ;; Fixme: before updating the working copy to the latest
723 ;; state, should check if it's visiting an old revision.
659114fd
CY
724 (args '("-u")))
725 ;; If necessary, prompt for the exact command.
726 (when prompt
727 (setq args (split-string
5ceaac0c
GM
728 (read-shell-command "Run Hg (like this): "
729 (format "%s pull -u" hg-program)
659114fd
CY
730 'vc-hg-history)
731 " " t))
732 (setq hg-program (car args)
733 command (cadr args)
734 args (cddr args)))
735 (apply 'vc-do-async-command buffer root hg-program
a2b6e5d6 736 command args)
9c750eba
SM
737 (with-current-buffer buffer
738 (vc-run-delayed (vc-compilation-mode 'hg)))
a2b6e5d6 739 (vc-set-async-update buffer)))))
659114fd
CY
740
741(defun vc-hg-merge-branch ()
a2b6e5d6
CY
742 "Merge incoming changes into the current working directory.
743This runs the command \"hg merge\"."
659114fd
CY
744 (let* ((root (vc-hg-root default-directory))
745 (buffer (format "*vc-hg : %s*" (expand-file-name root))))
5ceaac0c 746 (apply 'vc-do-async-command buffer root vc-hg-program '("merge"))
9c750eba 747 (with-current-buffer buffer (vc-run-delayed (vc-compilation-mode 'hg)))
a2b6e5d6 748 (vc-set-async-update buffer)))
f0230324 749
61223448
DN
750;;; Internal functions
751
8cdd17b4 752(defun vc-hg-command (buffer okstatus file-or-list &rest flags)
61223448 753 "A wrapper around `vc-do-command' for use in vc-hg.el.
219ea611
GM
754This function differs from vc-do-command in that it invokes
755`vc-hg-program', and passes `vc-hg-global-switches' to it before FLAGS."
88bf1bec 756 (apply 'vc-do-command (or buffer "*vc*") okstatus vc-hg-program file-or-list
61223448
DN
757 (if (stringp vc-hg-global-switches)
758 (cons vc-hg-global-switches flags)
759 (append vc-hg-global-switches
760 flags))))
761
a07e665b
DN
762(defun vc-hg-root (file)
763 (vc-find-root file ".hg"))
764
61223448
DN
765(provide 'vc-hg)
766
248c6645 767;;; vc-hg.el ends here