changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > infra > home / .emacs.d/lib/mercurial.el

changeset 40: 2d74d85d7031
author: Richard Westhaver <ellis@rwest.io>
date: Wed, 05 Jun 2024 23:31:48 +0000
permissions: -rw-r--r--
description: add back official mercurial emacs packages
1 ;;; mercurial.el --- Emacs support for the Mercurial distributed SCM
2 
3 ;; Copyright (C) 2005, 2006 Bryan O'Sullivan
4 
5 ;; Author: Bryan O'Sullivan <bos@serpentine.com>
6 
7 ;; mercurial.el is free software; you can redistribute it and/or
8 ;; modify it under the terms of the GNU General Public License version
9 ;; 2 or any later version.
10 
11 ;; mercurial.el is distributed in the hope that it will be useful, but
12 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 ;; General Public License for more details.
15 
16 ;; You should have received a copy of the GNU General Public License
17 ;; along with mercurial.el, GNU Emacs, or XEmacs; see the file COPYING
18 ;; (`C-h C-l'). If not, see <http://www.gnu.org/licenses/>.
19 
20 ;;; Commentary:
21 
22 ;; mercurial.el builds upon Emacs's VC mode to provide flexible
23 ;; integration with the Mercurial distributed SCM tool.
24 
25 ;; To get going as quickly as possible, load mercurial.el into Emacs and
26 ;; type `C-c h h'; this runs hg-help-overview, which prints a helpful
27 ;; usage overview.
28 
29 ;; Much of the inspiration for mercurial.el comes from Rajesh
30 ;; Vaidheeswarran's excellent p4.el, which does an admirably thorough
31 ;; job for the commercial Perforce SCM product. In fact, substantial
32 ;; chunks of code are adapted from p4.el.
33 
34 ;; This code has been developed under XEmacs 21.5, and may not work as
35 ;; well under GNU Emacs (albeit tested under 21.4). Patches to
36 ;; enhance the portability of this code, fix bugs, and add features
37 ;; are most welcome.
38 
39 ;; As of version 22.3, GNU Emacs's VC mode has direct support for
40 ;; Mercurial, so this package may not prove as useful there.
41 
42 ;; Please send problem reports and suggestions to bos@serpentine.com.
43 
44 
45 ;;; Code:
46 
47 (eval-when-compile (require 'cl))
48 (require 'diff-mode)
49 (require 'easymenu)
50 (require 'executable)
51 (require 'vc)
52 
53 (defmacro hg-feature-cond (&rest clauses)
54  "Test CLAUSES for feature at compile time.
55 Each clause is (FEATURE BODY...)."
56  (dolist (x clauses)
57  (let ((feature (car x))
58  (body (cdr x)))
59  (when (or (eq feature t)
60  (featurep feature))
61  (return (cons 'progn body))))))
62 
63 
64 ;;; XEmacs has view-less, while GNU Emacs has view. Joy.
65 
66 (hg-feature-cond
67  (xemacs (require 'view-less))
68  (t (require 'view)))
69 
70 
71 ;;; Variables accessible through the custom system.
72 
73 (defgroup mercurial nil
74  "Mercurial distributed SCM."
75  :group 'tools)
76 
77 (defcustom hg-binary
78  (or (executable-find "hg")
79  (dolist (path '("~/bin/hg" "/usr/bin/hg" "/usr/local/bin/hg"))
80  (when (file-executable-p path)
81  (return path))))
82  "The path to Mercurial's hg executable."
83  :type '(file :must-match t)
84  :group 'mercurial)
85 
86 (defcustom hg-mode-hook nil
87  "Hook run when a buffer enters hg-mode."
88  :type 'sexp
89  :group 'mercurial)
90 
91 (defcustom hg-commit-mode-hook nil
92  "Hook run when a buffer is created to prepare a commit."
93  :type 'sexp
94  :group 'mercurial)
95 
96 (defcustom hg-pre-commit-hook nil
97  "Hook run before a commit is performed.
98 If you want to prevent the commit from proceeding, raise an error."
99  :type 'sexp
100  :group 'mercurial)
101 
102 (defcustom hg-log-mode-hook nil
103  "Hook run after a buffer is filled with log information."
104  :type 'sexp
105  :group 'mercurial)
106 
107 (defcustom hg-global-prefix "\C-ch"
108  "The global prefix for Mercurial keymap bindings."
109  :type 'sexp
110  :group 'mercurial)
111 
112 (defcustom hg-commit-allow-empty-message nil
113  "Whether to allow changes to be committed with empty descriptions."
114  :type 'boolean
115  :group 'mercurial)
116 
117 (defcustom hg-commit-allow-empty-file-list nil
118  "Whether to allow changes to be committed without any modified files."
119  :type 'boolean
120  :group 'mercurial)
121 
122 (defcustom hg-rev-completion-limit 100
123  "The maximum number of revisions that hg-read-rev will offer to complete.
124 This affects memory usage and performance when prompting for revisions
125 in a repository with a lot of history."
126  :type 'integer
127  :group 'mercurial)
128 
129 (defcustom hg-log-limit 50
130  "The maximum number of revisions that hg-log will display."
131  :type 'integer
132  :group 'mercurial)
133 
134 (defcustom hg-update-modeline t
135  "Whether to update the modeline with the status of a file after every save.
136 Set this to nil on platforms with poor process management, such as Windows."
137  :type 'boolean
138  :group 'mercurial)
139 
140 (defcustom hg-incoming-repository "default"
141  "The repository from which changes are pulled from by default.
142 This should be a symbolic repository name, since it is used for all
143 repository-related commands."
144  :type 'string
145  :group 'mercurial)
146 
147 (defcustom hg-outgoing-repository ""
148  "The repository to which changes are pushed to by default.
149 This should be a symbolic repository name, since it is used for all
150 repository-related commands."
151  :type 'string
152  :group 'mercurial)
153 
154 
155 ;;; Other variables.
156 
157 (defvar hg-mode nil
158  "Is this file managed by Mercurial?")
159 (make-variable-buffer-local 'hg-mode)
160 (put 'hg-mode 'permanent-local t)
161 
162 (defvar hg-status nil)
163 (make-variable-buffer-local 'hg-status)
164 (put 'hg-status 'permanent-local t)
165 
166 (defvar hg-prev-buffer nil)
167 (make-variable-buffer-local 'hg-prev-buffer)
168 (put 'hg-prev-buffer 'permanent-local t)
169 
170 (defvar hg-root nil)
171 (make-variable-buffer-local 'hg-root)
172 (put 'hg-root 'permanent-local t)
173 
174 (defvar hg-view-mode nil)
175 (make-variable-buffer-local 'hg-view-mode)
176 (put 'hg-view-mode 'permanent-local t)
177 
178 (defvar hg-view-file-name nil)
179 (make-variable-buffer-local 'hg-view-file-name)
180 (put 'hg-view-file-name 'permanent-local t)
181 
182 (defvar hg-output-buffer-name "*Hg*"
183  "The name to use for Mercurial output buffers.")
184 
185 (defvar hg-file-history nil)
186 (defvar hg-repo-history nil)
187 (defvar hg-rev-history nil)
188 (defvar hg-repo-completion-table nil) ; shut up warnings
189 
190 
191 ;;; Random constants.
192 
193 (defconst hg-commit-message-start
194  "--- Enter your commit message. Type `C-c C-c' to commit. ---\n")
195 
196 (defconst hg-commit-message-end
197  "--- Files in bold will be committed. Click to toggle selection. ---\n")
198 
199 (defconst hg-state-alist
200  '((?M . modified)
201  (?A . added)
202  (?R . removed)
203  (?! . deleted)
204  (?C . normal)
205  (?I . ignored)
206  (?? . nil)))
207 
208 ;;; hg-mode keymap.
209 
210 (defvar hg-prefix-map
211  (let ((map (make-sparse-keymap)))
212  (hg-feature-cond (xemacs (set-keymap-name map 'hg-prefix-map))) ; XEmacs
213  (set-keymap-parent map vc-prefix-map)
214  (define-key map "=" 'hg-diff)
215  (define-key map "c" 'hg-undo)
216  (define-key map "g" 'hg-annotate)
217  (define-key map "i" 'hg-add)
218  (define-key map "l" 'hg-log)
219  (define-key map "n" 'hg-commit-start)
220  ;; (define-key map "r" 'hg-update)
221  (define-key map "u" 'hg-revert-buffer)
222  (define-key map "~" 'hg-version-other-window)
223  map)
224  "This keymap overrides some default vc-mode bindings.")
225 
226 (defvar hg-mode-map
227  (let ((map (make-sparse-keymap)))
228  (define-key map "\C-xv" hg-prefix-map)
229  map))
230 
231 (add-minor-mode 'hg-mode 'hg-mode hg-mode-map)
232 
233 
234 ;;; Global keymap.
235 
236 (defvar hg-global-map
237  (let ((map (make-sparse-keymap)))
238  (define-key map "," 'hg-incoming)
239  (define-key map "." 'hg-outgoing)
240  (define-key map "<" 'hg-pull)
241  (define-key map "=" 'hg-diff-repo)
242  (define-key map ">" 'hg-push)
243  (define-key map "?" 'hg-help-overview)
244  (define-key map "A" 'hg-addremove)
245  (define-key map "U" 'hg-revert)
246  (define-key map "a" 'hg-add)
247  (define-key map "c" 'hg-commit-start)
248  (define-key map "f" 'hg-forget)
249  (define-key map "h" 'hg-help-overview)
250  (define-key map "i" 'hg-init)
251  (define-key map "l" 'hg-log-repo)
252  (define-key map "r" 'hg-root)
253  (define-key map "s" 'hg-status)
254  (define-key map "u" 'hg-update)
255  map))
256 
257 (global-set-key hg-global-prefix hg-global-map)
258 
259 ;;; View mode keymap.
260 
261 (defvar hg-view-mode-map
262  (let ((map (make-sparse-keymap)))
263  (hg-feature-cond (xemacs (set-keymap-name map 'hg-view-mode-map))) ; XEmacs
264  (define-key map (hg-feature-cond (xemacs [button2])
265  (t [mouse-2]))
266  'hg-buffer-mouse-clicked)
267  map))
268 
269 (add-minor-mode 'hg-view-mode "" hg-view-mode-map)
270 
271 
272 ;;; Commit mode keymaps.
273 
274 (defvar hg-commit-mode-map
275  (let ((map (make-sparse-keymap)))
276  (define-key map "\C-c\C-c" 'hg-commit-finish)
277  (define-key map "\C-c\C-k" 'hg-commit-kill)
278  (define-key map "\C-xv=" 'hg-diff-repo)
279  map))
280 
281 (defvar hg-commit-mode-file-map
282  (let ((map (make-sparse-keymap)))
283  (define-key map (hg-feature-cond (xemacs [button2])
284  (t [mouse-2]))
285  'hg-commit-mouse-clicked)
286  (define-key map " " 'hg-commit-toggle-file)
287  (define-key map "\r" 'hg-commit-toggle-file)
288  map))
289 
290 
291 ;;; Convenience functions.
292 
293 (defsubst hg-binary ()
294  (if hg-binary
295  hg-binary
296  (error "No `hg' executable found!")))
297 
298 (defsubst hg-replace-in-string (str regexp newtext &optional literal)
299  "Replace all matches in STR for REGEXP with NEWTEXT string.
300 Return the new string. Optional LITERAL non-nil means do a literal
301 replacement.
302 
303 This function bridges yet another pointless impedance gap between
304 XEmacs and GNU Emacs."
305  (hg-feature-cond
306  (xemacs (replace-in-string str regexp newtext literal))
307  (t (replace-regexp-in-string regexp newtext str nil literal))))
308 
309 (defsubst hg-strip (str)
310  "Strip leading and trailing blank lines from a string."
311  (hg-replace-in-string (hg-replace-in-string str "[\r\n][ \t\r\n]*\\'" "")
312  "\\`[ \t\r\n]*[\r\n]" ""))
313 
314 (defsubst hg-chomp (str)
315  "Strip trailing newlines from a string."
316  (hg-replace-in-string str "[\r\n]+\\'" ""))
317 
318 (defun hg-run-command (command &rest args)
319  "Run the shell command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT).
320 The list ARGS contains a list of arguments to pass to the command."
321  (let* (exit-code
322  (output
323  (with-output-to-string
324  (with-current-buffer
325  standard-output
326  (setq exit-code
327  (apply 'call-process command nil t nil args))))))
328  (cons exit-code output)))
329 
330 (defun hg-run (command &rest args)
331  "Run the Mercurial command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT)."
332  (apply 'hg-run-command (hg-binary) command args))
333 
334 (defun hg-run0 (command &rest args)
335  "Run the Mercurial command COMMAND, returning its output.
336 If the command does not exit with a zero status code, raise an error."
337  (let ((res (apply 'hg-run-command (hg-binary) command args)))
338  (if (not (eq (car res) 0))
339  (error "Mercurial command failed %s - exit code %s"
340  (cons command args)
341  (car res))
342  (cdr res))))
343 
344 (defmacro hg-do-across-repo (path &rest body)
345  (let ((root-name (make-symbol "root-"))
346  (buf-name (make-symbol "buf-")))
347  `(let ((,root-name (hg-root ,path)))
348  (save-excursion
349  (dolist (,buf-name (buffer-list))
350  (set-buffer ,buf-name)
351  (when (and hg-status (equal (hg-root buffer-file-name) ,root-name))
352  ,@body))))))
353 
354 (put 'hg-do-across-repo 'lisp-indent-function 1)
355 
356 (defun hg-sync-buffers (path)
357  "Sync buffers visiting PATH with their on-disk copies.
358 If PATH is not being visited, but is under the repository root, sync
359 all buffers visiting files in the repository."
360  (let ((buf (find-buffer-visiting path)))
361  (if buf
362  (with-current-buffer buf
363  (vc-buffer-sync))
364  (hg-do-across-repo path
365  (vc-buffer-sync)))))
366 
367 (defun hg-buffer-commands (pnt)
368  "Use the properties of a character to do something sensible."
369  (interactive "d")
370  (let ((rev (get-char-property pnt 'rev))
371  (file (get-char-property pnt 'file)))
372  (cond
373  (file
374  (find-file-other-window file))
375  (rev
376  (hg-diff hg-view-file-name rev rev))
377  ((message "I don't know how to do that yet")))))
378 
379 (defsubst hg-event-point (event)
380  "Return the character position of the mouse event EVENT."
381  (hg-feature-cond (xemacs (event-point event))
382  (t (posn-point (event-start event)))))
383 
384 (defsubst hg-event-window (event)
385  "Return the window over which mouse event EVENT occurred."
386  (hg-feature-cond (xemacs (event-window event))
387  (t (posn-window (event-start event)))))
388 
389 (defun hg-buffer-mouse-clicked (event)
390  "Translate the mouse clicks in a HG log buffer to character events.
391 These are then handed off to `hg-buffer-commands'.
392 
393 Handle frickin' frackin' gratuitous event-related incompatibilities."
394  (interactive "e")
395  (select-window (hg-event-window event))
396  (hg-buffer-commands (hg-event-point event)))
397 
398 (defsubst hg-abbrev-file-name (file)
399  "Portable wrapper around abbreviate-file-name."
400  (hg-feature-cond (xemacs (abbreviate-file-name file t))
401  (t (abbreviate-file-name file))))
402 
403 (defun hg-read-file-name (&optional prompt default)
404  "Read a file or directory name, or a pattern, to use with a command."
405  (save-excursion
406  (while hg-prev-buffer
407  (set-buffer hg-prev-buffer))
408  (let ((path (or default
409  (buffer-file-name)
410  (expand-file-name default-directory))))
411  (if (or (not path) current-prefix-arg)
412  (expand-file-name
413  (eval (list* 'read-file-name
414  (format "File, directory or pattern%s: "
415  (or prompt ""))
416  (and path (file-name-directory path))
417  nil nil
418  (and path (file-name-nondirectory path))
419  (hg-feature-cond
420  (xemacs (cons (quote 'hg-file-history) nil))
421  (t nil)))))
422  path))))
423 
424 (defun hg-read-number (&optional prompt default)
425  "Read a integer value."
426  (save-excursion
427  (if (or (not default) current-prefix-arg)
428  (string-to-number
429  (eval (list* 'read-string
430  (or prompt "")
431  (if default (cons (format "%d" default) nil) nil))))
432  default)))
433 
434 (defun hg-read-config ()
435  "Return an alist of (key . value) pairs of Mercurial config data.
436 Each key is of the form (section . name)."
437  (let (items)
438  (dolist (line (split-string (hg-chomp (hg-run0 "debugconfig")) "\n") items)
439  (string-match "^\\([^=]*\\)=\\(.*\\)" line)
440  (let* ((left (substring line (match-beginning 1) (match-end 1)))
441  (right (substring line (match-beginning 2) (match-end 2)))
442  (key (split-string left "\\."))
443  (value (hg-replace-in-string right "\\\\n" "\n" t)))
444  (setq items (cons (cons (cons (car key) (cadr key)) value) items))))))
445 
446 (defun hg-config-section (section config)
447  "Return an alist of (name . value) pairs for SECTION of CONFIG."
448  (let (items)
449  (dolist (item config items)
450  (when (equal (caar item) section)
451  (setq items (cons (cons (cdar item) (cdr item)) items))))))
452 
453 (defun hg-string-starts-with (sub str)
454  "Indicate whether string STR starts with the substring or character SUB."
455  (if (not (stringp sub))
456  (and (> (length str) 0) (equal (elt str 0) sub))
457  (let ((sub-len (length sub)))
458  (and (<= sub-len (length str))
459  (string= sub (substring str 0 sub-len))))))
460 
461 (defun hg-complete-repo (string predicate all)
462  "Attempt to complete a repository name.
463 We complete on either symbolic names from Mercurial's config or real
464 directory names from the file system. We do not penalize URLs."
465  (or (if all
466  (all-completions string hg-repo-completion-table predicate)
467  (try-completion string hg-repo-completion-table predicate))
468  (let* ((str (expand-file-name string))
469  (dir (file-name-directory str))
470  (file (file-name-nondirectory str)))
471  (if all
472  (let (completions)
473  (dolist (name (delete "./" (file-name-all-completions file dir))
474  completions)
475  (let ((path (concat dir name)))
476  (when (file-directory-p path)
477  (setq completions (cons name completions))))))
478  (let ((comp (file-name-completion file dir)))
479  (if comp
480  (hg-abbrev-file-name (concat dir comp))))))))
481 
482 (defun hg-read-repo-name (&optional prompt initial-contents default)
483  "Read the location of a repository."
484  (save-excursion
485  (while hg-prev-buffer
486  (set-buffer hg-prev-buffer))
487  (let (hg-repo-completion-table)
488  (if current-prefix-arg
489  (progn
490  (dolist (path (hg-config-section "paths" (hg-read-config)))
491  (setq hg-repo-completion-table
492  (cons (cons (car path) t) hg-repo-completion-table))
493  (unless (hg-string-starts-with (hg-feature-cond
494  (xemacs directory-sep-char)
495  (t ?/))
496  (cdr path))
497  (setq hg-repo-completion-table
498  (cons (cons (cdr path) t) hg-repo-completion-table))))
499  (completing-read (format "Repository%s: " (or prompt ""))
500  'hg-complete-repo
501  nil
502  nil
503  initial-contents
504  'hg-repo-history
505  default))
506  default))))
507 
508 (defun hg-read-rev (&optional prompt default)
509  "Read a revision or tag, offering completions."
510  (save-excursion
511  (while hg-prev-buffer
512  (set-buffer hg-prev-buffer))
513  (let ((rev (or default "tip")))
514  (if current-prefix-arg
515  (let ((revs (split-string
516  (hg-chomp
517  (hg-run0 "-q" "log" "-l"
518  (format "%d" hg-rev-completion-limit)))
519  "[\n:]")))
520  (dolist (line (split-string (hg-chomp (hg-run0 "tags")) "\n"))
521  (setq revs (cons (car (split-string line "\\s-")) revs)))
522  (completing-read (format "Revision%s (%s): "
523  (or prompt "")
524  (or default "tip"))
525  (mapcar (lambda (x) (cons x x)) revs)
526  nil
527  nil
528  nil
529  'hg-rev-history
530  (or default "tip")))
531  rev))))
532 
533 (defun hg-parents-for-mode-line (root)
534  "Format the parents of the working directory for the mode line."
535  (let ((parents (split-string (hg-chomp
536  (hg-run0 "--cwd" root "parents" "--template"
537  "{rev}\n")) "\n")))
538  (mapconcat 'identity parents "+")))
539 
540 (defun hg-buffers-visiting-repo (&optional path)
541  "Return a list of buffers visiting the repository containing PATH."
542  (let ((root-name (hg-root (or path (buffer-file-name))))
543  bufs)
544  (save-excursion
545  (dolist (buf (buffer-list) bufs)
546  (set-buffer buf)
547  (let ((name (buffer-file-name)))
548  (when (and hg-status name (equal (hg-root name) root-name))
549  (setq bufs (cons buf bufs))))))))
550 
551 (defun hg-update-mode-lines (path)
552  "Update the mode lines of all buffers visiting the same repository as PATH."
553  (let* ((root (hg-root path))
554  (parents (hg-parents-for-mode-line root)))
555  (save-excursion
556  (dolist (info (hg-path-status
557  root
558  (mapcar
559  (function
560  (lambda (buf)
561  (substring (buffer-file-name buf) (length root))))
562  (hg-buffers-visiting-repo root))))
563  (let* ((name (car info))
564  (status (cdr info))
565  (buf (find-buffer-visiting (concat root name))))
566  (when buf
567  (set-buffer buf)
568  (hg-mode-line-internal status parents)))))))
569 
570 
571 ;;; View mode bits.
572 
573 (defun hg-exit-view-mode (buf)
574  "Exit from hg-view-mode.
575 We delete the current window if entering hg-view-mode split the
576 current frame."
577  (when (and (eq buf (current-buffer))
578  (> (length (window-list)) 1))
579  (delete-window))
580  (when (buffer-live-p buf)
581  (kill-buffer buf)))
582 
583 (defun hg-view-mode (prev-buffer &optional file-name)
584  (goto-char (point-min))
585  (set-buffer-modified-p nil)
586  (toggle-read-only t)
587  (hg-feature-cond (xemacs (view-minor-mode prev-buffer 'hg-exit-view-mode))
588  (t (view-mode-enter nil 'hg-exit-view-mode)))
589  (setq hg-view-mode t)
590  (setq truncate-lines t)
591  (when file-name
592  (setq hg-view-file-name
593  (hg-abbrev-file-name file-name))))
594 
595 (defun hg-file-status (file)
596  "Return status of FILE, or nil if FILE does not exist or is unmanaged."
597  (let* ((s (hg-run "status" file))
598  (exit (car s))
599  (output (cdr s)))
600  (if (= exit 0)
601  (let ((state (and (>= (length output) 2)
602  (= (aref output 1) ? )
603  (assq (aref output 0) hg-state-alist))))
604  (if state
605  (cdr state)
606  'normal)))))
607 
608 (defun hg-path-status (root paths)
609  "Return status of PATHS in repo ROOT as an alist.
610 Each entry is a pair (FILE-NAME . STATUS)."
611  (let ((s (apply 'hg-run "--cwd" root "status" "-marduc" paths))
612  result)
613  (dolist (entry (split-string (hg-chomp (cdr s)) "\n") (nreverse result))
614  (let (state name)
615  (cond ((= (aref entry 1) ? )
616  (setq state (assq (aref entry 0) hg-state-alist)
617  name (substring entry 2)))
618  ((string-match "\\(.*\\): " entry)
619  (setq name (match-string 1 entry))))
620  (setq result (cons (cons name state) result))))))
621 
622 (defmacro hg-view-output (args &rest body)
623  "Execute BODY in a clean buffer, then quickly display that buffer.
624 If the buffer contains one line, its contents are displayed in the
625 minibuffer. Otherwise, the buffer is displayed in view-mode.
626 ARGS is of the form (BUFFER-NAME &optional FILE), where BUFFER-NAME is
627 the name of the buffer to create, and FILE is the name of the file
628 being viewed."
629  (let ((prev-buf (make-symbol "prev-buf-"))
630  (v-b-name (car args))
631  (v-m-rest (cdr args)))
632  `(let ((view-buf-name ,v-b-name)
633  (,prev-buf (current-buffer)))
634  (get-buffer-create view-buf-name)
635  (kill-buffer view-buf-name)
636  (get-buffer-create view-buf-name)
637  (set-buffer view-buf-name)
638  (save-excursion
639  ,@body)
640  (case (count-lines (point-min) (point-max))
641  ((0)
642  (kill-buffer view-buf-name)
643  (message "(No output)"))
644  ((1)
645  (let ((msg (hg-chomp (buffer-substring (point-min) (point-max)))))
646  (kill-buffer view-buf-name)
647  (message "%s" msg)))
648  (t
649  (pop-to-buffer view-buf-name)
650  (setq hg-prev-buffer ,prev-buf)
651  (hg-view-mode ,prev-buf ,@v-m-rest))))))
652 
653 (put 'hg-view-output 'lisp-indent-function 1)
654 
655 ;;; Context save and restore across revert and other operations.
656 
657 (defun hg-position-context (pos)
658  "Return information to help find the given position again."
659  (let* ((end (min (point-max) (+ pos 98))))
660  (list pos
661  (buffer-substring (max (point-min) (- pos 2)) end)
662  (- end pos))))
663 
664 (defun hg-buffer-context ()
665  "Return information to help restore a user's editing context.
666 This is useful across reverts and merges, where a context is likely
667 to have moved a little, but not really changed."
668  (let ((point-context (hg-position-context (point)))
669  (mark-context (let ((mark (mark-marker)))
670  (and mark
671  ;; make sure active mark
672  (marker-buffer mark)
673  (marker-position mark)
674  (hg-position-context mark)))))
675  (list point-context mark-context)))
676 
677 (defun hg-find-context (ctx)
678  "Attempt to find a context in the given buffer.
679 Always returns a valid, hopefully sane, position."
680  (let ((pos (nth 0 ctx))
681  (str (nth 1 ctx))
682  (fixup (nth 2 ctx)))
683  (save-excursion
684  (goto-char (max (point-min) (- pos 15000)))
685  (if (and (not (equal str ""))
686  (search-forward str nil t))
687  (- (point) fixup)
688  (max pos (point-min))))))
689 
690 (defun hg-restore-context (ctx)
691  "Attempt to restore the user's editing context."
692  (let ((point-context (nth 0 ctx))
693  (mark-context (nth 1 ctx)))
694  (goto-char (hg-find-context point-context))
695  (when mark-context
696  (set-mark (hg-find-context mark-context)))))
697 
698 
699 ;;; Hooks.
700 
701 (defun hg-mode-line-internal (status parents)
702  (setq hg-status status
703  hg-mode (and status (concat " Hg:"
704  parents
705  (cdr (assq status
706  '((normal . "")
707  (removed . "r")
708  (added . "a")
709  (deleted . "!")
710  (modified . "m"))))))))
711 
712 (defun hg-mode-line (&optional force)
713  "Update the modeline with the current status of a file.
714 An update occurs if optional argument FORCE is non-nil,
715 hg-update-modeline is non-nil, or we have not yet checked the state of
716 the file."
717  (let ((root (hg-root)))
718  (when (and root (or force hg-update-modeline (not hg-mode)))
719  (let ((status (hg-file-status buffer-file-name))
720  (parents (hg-parents-for-mode-line root)))
721  (hg-mode-line-internal status parents)
722  status))))
723 
724 (defun hg-mode (&optional toggle)
725  "Minor mode for Mercurial distributed SCM integration.
726 
727 The Mercurial mode user interface is based on that of VC mode, so if
728 you're already familiar with VC, the same keybindings and functions
729 will generally work.
730 
731 Below is a list of many common SCM tasks. In the list, `G/L\'
732 indicates whether a key binding is global (G) to a repository or
733 local (L) to a file. Many commands take a prefix argument.
734 
735 SCM Task G/L Key Binding Command Name
736 -------- --- ----------- ------------
737 Help overview (what you are reading) G C-c h h hg-help-overview
738 
739 Tell Mercurial to manage a file G C-c h a hg-add
740 Commit changes to current file only L C-x v n hg-commit-start
741 Undo changes to file since commit L C-x v u hg-revert-buffer
742 
743 Diff file vs last checkin L C-x v = hg-diff
744 
745 View file change history L C-x v l hg-log
746 View annotated file L C-x v a hg-annotate
747 
748 Diff repo vs last checkin G C-c h = hg-diff-repo
749 View status of files in repo G C-c h s hg-status
750 Commit all changes G C-c h c hg-commit-start
751 
752 Undo all changes since last commit G C-c h U hg-revert
753 View repo change history G C-c h l hg-log-repo
754 
755 See changes that can be pulled G C-c h , hg-incoming
756 Pull changes G C-c h < hg-pull
757 Update working directory after pull G C-c h u hg-update
758 See changes that can be pushed G C-c h . hg-outgoing
759 Push changes G C-c h > hg-push"
760  (unless vc-make-backup-files
761  (set (make-local-variable 'backup-inhibited) t))
762  (run-hooks 'hg-mode-hook))
763 
764 (defun hg-find-file-hook ()
765  (ignore-errors
766  (when (hg-mode-line)
767  (hg-mode))))
768 
769 (add-hook 'find-file-hooks 'hg-find-file-hook)
770 
771 (defun hg-after-save-hook ()
772  (ignore-errors
773  (let ((old-status hg-status))
774  (hg-mode-line)
775  (if (and (not old-status) hg-status)
776  (hg-mode)))))
777 
778 (add-hook 'after-save-hook 'hg-after-save-hook)
779 
780 
781 ;;; User interface functions.
782 
783 (defun hg-help-overview ()
784  "This is an overview of the Mercurial SCM mode for Emacs.
785 
786 You can find the source code, license (GPLv2+), and credits for this
787 code by typing `M-x find-library mercurial RET'."
788  (interactive)
789  (hg-view-output ("Mercurial Help Overview")
790  (insert (documentation 'hg-help-overview))
791  (let ((pos (point)))
792  (insert (documentation 'hg-mode))
793  (goto-char pos)
794  (end-of-line 1)
795  (delete-region pos (point)))
796  (let ((hg-root-dir (hg-root)))
797  (if (not hg-root-dir)
798  (error "error: %s: directory is not part of a Mercurial repository."
799  default-directory)
800  (cd hg-root-dir)))))
801 
802 (defun hg-fix-paths ()
803  "Fix paths reported by some Mercurial commands."
804  (save-excursion
805  (goto-char (point-min))
806  (while (re-search-forward " \\.\\.." nil t)
807  (replace-match " " nil nil))))
808 
809 (defun hg-add (path)
810  "Add PATH to the Mercurial repository on the next commit.
811 With a prefix argument, prompt for the path to add."
812  (interactive (list (hg-read-file-name " to add")))
813  (let ((buf (current-buffer))
814  (update (equal buffer-file-name path)))
815  (hg-view-output (hg-output-buffer-name)
816  (apply 'call-process (hg-binary) nil t nil (list "add" path))
817  (hg-fix-paths)
818  (goto-char (point-min))
819  (cd (hg-root path)))
820  (when update
821  (unless vc-make-backup-files
822  (set (make-local-variable 'backup-inhibited) t))
823  (with-current-buffer buf
824  (hg-mode-line)))))
825 
826 (defun hg-addremove ()
827  (interactive)
828  (error "not implemented"))
829 
830 (defun hg-annotate ()
831  (interactive)
832  (error "not implemented"))
833 
834 (defun hg-commit-toggle-file (pos)
835  "Toggle whether or not the file at POS will be committed."
836  (interactive "d")
837  (save-excursion
838  (goto-char pos)
839  (let (face
840  (inhibit-read-only t)
841  bol)
842  (beginning-of-line)
843  (setq bol (+ (point) 4))
844  (setq face (get-text-property bol 'face))
845  (end-of-line)
846  (if (eq face 'bold)
847  (progn
848  (remove-text-properties bol (point) '(face nil))
849  (message "%s will not be committed"
850  (buffer-substring bol (point))))
851  (add-text-properties bol (point) '(face bold))
852  (message "%s will be committed"
853  (buffer-substring bol (point)))))))
854 
855 (defun hg-commit-mouse-clicked (event)
856  "Toggle whether or not the file at POS will be committed."
857  (interactive "@e")
858  (hg-commit-toggle-file (hg-event-point event)))
859 
860 (defun hg-commit-kill ()
861  "Kill the commit currently being prepared."
862  (interactive)
863  (when (or (not (buffer-modified-p)) (y-or-n-p "Really kill this commit? "))
864  (let ((buf hg-prev-buffer))
865  (kill-buffer nil)
866  (switch-to-buffer buf))))
867 
868 (defun hg-commit-finish ()
869  "Finish preparing a commit, and perform the actual commit.
870 The hook hg-pre-commit-hook is run before anything else is done. If
871 the commit message is empty and hg-commit-allow-empty-message is nil,
872 an error is raised. If the list of files to commit is empty and
873 hg-commit-allow-empty-file-list is nil, an error is raised."
874  (interactive)
875  (let ((root hg-root))
876  (save-excursion
877  (run-hooks 'hg-pre-commit-hook)
878  (goto-char (point-min))
879  (search-forward hg-commit-message-start)
880  (let (message files)
881  (let ((start (point)))
882  (goto-char (point-max))
883  (search-backward hg-commit-message-end)
884  (setq message (hg-strip (buffer-substring start (point)))))
885  (when (and (= (length message) 0)
886  (not hg-commit-allow-empty-message))
887  (error "Cannot proceed - commit message is empty"))
888  (forward-line 1)
889  (beginning-of-line)
890  (while (< (point) (point-max))
891  (let ((pos (+ (point) 4)))
892  (end-of-line)
893  (when (eq (get-text-property pos 'face) 'bold)
894  (end-of-line)
895  (setq files (cons (buffer-substring pos (point)) files))))
896  (forward-line 1))
897  (when (and (= (length files) 0)
898  (not hg-commit-allow-empty-file-list))
899  (error "Cannot proceed - no files to commit"))
900  (setq message (concat message "\n"))
901  (apply 'hg-run0 "--cwd" hg-root "commit" "-m" message files))
902  (let ((buf hg-prev-buffer))
903  (kill-buffer nil)
904  (switch-to-buffer buf))
905  (hg-update-mode-lines root))))
906 
907 (defun hg-commit-mode ()
908  "Mode for describing a commit of changes to a Mercurial repository.
909 This involves two actions: describing the changes with a commit
910 message, and choosing the files to commit.
911 
912 To describe the commit, simply type some text in the designated area.
913 
914 By default, all modified, added and removed files are selected for
915 committing. Files that will be committed are displayed in bold face\;
916 those that will not are displayed in normal face.
917 
918 To toggle whether a file will be committed, move the cursor over a
919 particular file and hit space or return. Alternatively, middle click
920 on the file.
921 
922 Key bindings
923 ------------
924 \\[hg-commit-finish] proceed with commit
925 \\[hg-commit-kill] kill commit
926 
927 \\[hg-diff-repo] view diff of pending changes"
928  (interactive)
929  (use-local-map hg-commit-mode-map)
930  (set-syntax-table text-mode-syntax-table)
931  (setq local-abbrev-table text-mode-abbrev-table
932  major-mode 'hg-commit-mode
933  mode-name "Hg-Commit")
934  (set-buffer-modified-p nil)
935  (setq buffer-undo-list nil)
936  (run-hooks 'text-mode-hook 'hg-commit-mode-hook))
937 
938 (defun hg-commit-start ()
939  "Prepare a commit of changes to the repository containing the current file."
940  (interactive)
941  (while hg-prev-buffer
942  (set-buffer hg-prev-buffer))
943  (let ((root (hg-root))
944  (prev-buffer (current-buffer))
945  modified-files)
946  (unless root
947  (error "Cannot commit outside a repository!"))
948  (hg-sync-buffers root)
949  (setq modified-files (hg-chomp (hg-run0 "--cwd" root "status" "-arm")))
950  (when (and (= (length modified-files) 0)
951  (not hg-commit-allow-empty-file-list))
952  (error "No pending changes to commit"))
953  (let* ((buf-name (format "*Mercurial: Commit %s*" root)))
954  (pop-to-buffer (get-buffer-create buf-name))
955  (when (= (point-min) (point-max))
956  (set (make-local-variable 'hg-root) root)
957  (setq hg-prev-buffer prev-buffer)
958  (insert "\n")
959  (let ((bol (point)))
960  (insert hg-commit-message-end)
961  (add-text-properties bol (point) '(face bold-italic)))
962  (let ((file-area (point)))
963  (insert modified-files)
964  (goto-char file-area)
965  (while (< (point) (point-max))
966  (let ((bol (point)))
967  (forward-char 1)
968  (insert " ")
969  (end-of-line)
970  (add-text-properties (+ bol 4) (point)
971  '(face bold mouse-face highlight)))
972  (forward-line 1))
973  (goto-char file-area)
974  (add-text-properties (point) (point-max)
975  `(keymap ,hg-commit-mode-file-map))
976  (goto-char (point-min))
977  (insert hg-commit-message-start)
978  (add-text-properties (point-min) (point) '(face bold-italic))
979  (insert "\n\n")
980  (forward-line -1)
981  (save-excursion
982  (goto-char (point-max))
983  (search-backward hg-commit-message-end)
984  (add-text-properties (match-beginning 0) (point-max)
985  '(read-only t))
986  (goto-char (point-min))
987  (search-forward hg-commit-message-start)
988  (add-text-properties (match-beginning 0) (match-end 0)
989  '(read-only t)))
990  (hg-commit-mode)
991  (cd root))))))
992 
993 (defun hg-diff (path &optional rev1 rev2)
994  "Show the differences between REV1 and REV2 of PATH.
995 When called interactively, the default behaviour is to treat REV1 as
996 the \"parent\" revision, REV2 as the current edited version of the file, and
997 PATH as the file edited in the current buffer.
998 With a prefix argument, prompt for all of these."
999  (interactive (list (hg-read-file-name " to diff")
1000  (let ((rev1 (hg-read-rev " to start with" 'parent)))
1001  (and (not (eq rev1 'parent)) rev1))
1002  (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
1003  (and (not (eq rev2 'working-dir)) rev2))))
1004  (hg-sync-buffers path)
1005  (let ((a-path (hg-abbrev-file-name path))
1006  ;; none revision is specified explicitly
1007  (none (and (not rev1) (not rev2)))
1008  ;; only one revision is specified explicitly
1009  (one (or (and (or (equal rev1 rev2) (not rev2)) rev1)
1010  (and (not rev1) rev2)))
1011  diff)
1012  (hg-view-output ((cond
1013  (none
1014  (format "Mercurial: Diff against parent of %s" a-path))
1015  (one
1016  (format "Mercurial: Diff of rev %s of %s" one a-path))
1017  (t
1018  (format "Mercurial: Diff from rev %s to %s of %s"
1019  rev1 rev2 a-path))))
1020  (cond
1021  (none
1022  (call-process (hg-binary) nil t nil "diff" path))
1023  (one
1024  (call-process (hg-binary) nil t nil "diff" "-r" one path))
1025  (t
1026  (call-process (hg-binary) nil t nil "diff" "-r" rev1 "-r" rev2 path)))
1027  (diff-mode)
1028  (setq diff (not (= (point-min) (point-max))))
1029  (font-lock-fontify-buffer)
1030  (cd (hg-root path)))
1031  diff))
1032 
1033 (defun hg-diff-repo (path &optional rev1 rev2)
1034  "Show the differences between REV1 and REV2 of repository containing PATH.
1035 When called interactively, the default behaviour is to treat REV1 as
1036 the \"parent\" revision, REV2 as the current edited version of the file, and
1037 PATH as the `hg-root' of the current buffer.
1038 With a prefix argument, prompt for all of these."
1039  (interactive (list (hg-read-file-name " to diff")
1040  (let ((rev1 (hg-read-rev " to start with" 'parent)))
1041  (and (not (eq rev1 'parent)) rev1))
1042  (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
1043  (and (not (eq rev2 'working-dir)) rev2))))
1044  (hg-diff (hg-root path) rev1 rev2))
1045 
1046 (defun hg-forget (path)
1047  "Lose track of PATH, which has been added, but not yet committed.
1048 This will prevent the file from being incorporated into the Mercurial
1049 repository on the next commit.
1050 With a prefix argument, prompt for the path to forget."
1051  (interactive (list (hg-read-file-name " to forget")))
1052  (let ((buf (current-buffer))
1053  (update (equal buffer-file-name path)))
1054  (hg-view-output (hg-output-buffer-name)
1055  (apply 'call-process (hg-binary) nil t nil (list "forget" path))
1056  ;; "hg forget" shows pathes relative NOT TO ROOT BUT TO REPOSITORY
1057  (hg-fix-paths)
1058  (goto-char (point-min))
1059  (cd (hg-root path)))
1060  (when update
1061  (with-current-buffer buf
1062  (when (local-variable-p 'backup-inhibited)
1063  (kill-local-variable 'backup-inhibited))
1064  (hg-mode-line)))))
1065 
1066 (defun hg-incoming (&optional repo)
1067  "Display changesets present in REPO that are not present locally."
1068  (interactive (list (hg-read-repo-name " where changes would come from")))
1069  (hg-view-output ((format "Mercurial: Incoming from %s to %s"
1070  (hg-abbrev-file-name (hg-root))
1071  (hg-abbrev-file-name
1072  (or repo hg-incoming-repository))))
1073  (call-process (hg-binary) nil t nil "incoming"
1074  (or repo hg-incoming-repository))
1075  (hg-log-mode)
1076  (cd (hg-root))))
1077 
1078 (defun hg-init ()
1079  (interactive)
1080  (error "not implemented"))
1081 
1082 (defun hg-log-mode ()
1083  "Mode for viewing a Mercurial change log."
1084  (goto-char (point-min))
1085  (when (looking-at "^searching for changes.*$")
1086  (delete-region (match-beginning 0) (match-end 0)))
1087  (run-hooks 'hg-log-mode-hook))
1088 
1089 (defun hg-log (path &optional rev1 rev2 log-limit)
1090  "Display the revision history of PATH.
1091 History is displayed between REV1 and REV2.
1092 Number of displayed changesets is limited to LOG-LIMIT.
1093 REV1 defaults to the tip, while REV2 defaults to 0.
1094 LOG-LIMIT defaults to `hg-log-limit'.
1095 With a prefix argument, prompt for each parameter."
1096  (interactive (list (hg-read-file-name " to log")
1097  (hg-read-rev " to start with"
1098  "tip")
1099  (hg-read-rev " to end with"
1100  "0")
1101  (hg-read-number "Output limited to: "
1102  hg-log-limit)))
1103  (let ((a-path (hg-abbrev-file-name path))
1104  (r1 (or rev1 "tip"))
1105  (r2 (or rev2 "0"))
1106  (limit (format "%d" (or log-limit hg-log-limit))))
1107  (hg-view-output ((if (equal r1 r2)
1108  (format "Mercurial: Log of rev %s of %s" rev1 a-path)
1109  (format
1110  "Mercurial: at most %s log(s) from rev %s to %s of %s"
1111  limit r1 r2 a-path)))
1112  (eval (list* 'call-process (hg-binary) nil t nil
1113  "log"
1114  "-r" (format "%s:%s" r1 r2)
1115  "-l" limit
1116  (if (> (length path) (length (hg-root path)))
1117  (cons path nil)
1118  nil)))
1119  (hg-log-mode)
1120  (cd (hg-root path)))))
1121 
1122 (defun hg-log-repo (path &optional rev1 rev2 log-limit)
1123  "Display the revision history of the repository containing PATH.
1124 History is displayed between REV1 and REV2.
1125 Number of displayed changesets is limited to LOG-LIMIT,
1126 REV1 defaults to the tip, while REV2 defaults to 0.
1127 LOG-LIMIT defaults to `hg-log-limit'.
1128 With a prefix argument, prompt for each parameter."
1129  (interactive (list (hg-read-file-name " to log")
1130  (hg-read-rev " to start with"
1131  "tip")
1132  (hg-read-rev " to end with"
1133  "0")
1134  (hg-read-number "Output limited to: "
1135  hg-log-limit)))
1136  (hg-log (hg-root path) rev1 rev2 log-limit))
1137 
1138 (defun hg-outgoing (&optional repo)
1139  "Display changesets present locally that are not present in REPO."
1140  (interactive (list (hg-read-repo-name " where changes would go to" nil
1141  hg-outgoing-repository)))
1142  (hg-view-output ((format "Mercurial: Outgoing from %s to %s"
1143  (hg-abbrev-file-name (hg-root))
1144  (hg-abbrev-file-name
1145  (or repo hg-outgoing-repository))))
1146  (call-process (hg-binary) nil t nil "outgoing"
1147  (or repo hg-outgoing-repository))
1148  (hg-log-mode)
1149  (cd (hg-root))))
1150 
1151 (defun hg-pull (&optional repo)
1152  "Pull changes from repository REPO.
1153 This does not update the working directory."
1154  (interactive (list (hg-read-repo-name " to pull from")))
1155  (hg-view-output ((format "Mercurial: Pull to %s from %s"
1156  (hg-abbrev-file-name (hg-root))
1157  (hg-abbrev-file-name
1158  (or repo hg-incoming-repository))))
1159  (call-process (hg-binary) nil t nil "pull"
1160  (or repo hg-incoming-repository))
1161  (cd (hg-root))))
1162 
1163 (defun hg-push (&optional repo)
1164  "Push changes to repository REPO."
1165  (interactive (list (hg-read-repo-name " to push to")))
1166  (hg-view-output ((format "Mercurial: Push from %s to %s"
1167  (hg-abbrev-file-name (hg-root))
1168  (hg-abbrev-file-name
1169  (or repo hg-outgoing-repository))))
1170  (call-process (hg-binary) nil t nil "push"
1171  (or repo hg-outgoing-repository))
1172  (cd (hg-root))))
1173 
1174 (defun hg-revert-buffer-internal ()
1175  (let ((ctx (hg-buffer-context)))
1176  (message "Reverting %s..." buffer-file-name)
1177  (hg-run0 "revert" buffer-file-name)
1178  (revert-buffer t t t)
1179  (hg-restore-context ctx)
1180  (hg-mode-line)
1181  (message "Reverting %s...done" buffer-file-name)))
1182 
1183 (defun hg-revert-buffer ()
1184  "Revert current buffer's file back to the latest committed version.
1185 If the file has not changed, nothing happens. Otherwise, this
1186 displays a diff and asks for confirmation before reverting."
1187  (interactive)
1188  (let ((vc-suppress-confirm nil)
1189  (obuf (current-buffer))
1190  diff)
1191  (vc-buffer-sync)
1192  (unwind-protect
1193  (setq diff (hg-diff buffer-file-name))
1194  (when diff
1195  (unless (yes-or-no-p "Discard changes? ")
1196  (error "Revert cancelled")))
1197  (when diff
1198  (let ((buf (current-buffer)))
1199  (delete-window (selected-window))
1200  (kill-buffer buf))))
1201  (set-buffer obuf)
1202  (when diff
1203  (hg-revert-buffer-internal))))
1204 
1205 (defun hg-root (&optional path)
1206  "Return the root of the repository that contains the given path.
1207 If the path is outside a repository, return nil.
1208 When called interactively, the root is printed. A prefix argument
1209 prompts for a path to check."
1210  (interactive (list (hg-read-file-name)))
1211  (if (or path (not hg-root))
1212  (let ((root (do ((prev nil dir)
1213  (dir (file-name-directory
1214  (or
1215  path
1216  buffer-file-name
1217  (expand-file-name default-directory)))
1218  (file-name-directory (directory-file-name dir))))
1219  ((equal prev dir))
1220  (when (file-directory-p (concat dir ".hg"))
1221  (return dir)))))
1222  (when (interactive-p)
1223  (if root
1224  (message "The root of this repository is `%s'." root)
1225  (message "The path `%s' is not in a Mercurial repository."
1226  (hg-abbrev-file-name path))))
1227  root)
1228  hg-root))
1229 
1230 (defun hg-cwd (&optional path)
1231  "Return the current directory of PATH within the repository."
1232  (do ((stack nil (cons (file-name-nondirectory
1233  (directory-file-name dir))
1234  stack))
1235  (prev nil dir)
1236  (dir (file-name-directory (or path buffer-file-name
1237  (expand-file-name default-directory)))
1238  (file-name-directory (directory-file-name dir))))
1239  ((equal prev dir))
1240  (when (file-directory-p (concat dir ".hg"))
1241  (let ((cwd (mapconcat 'identity stack "/")))
1242  (unless (equal cwd "")
1243  (return (file-name-as-directory cwd)))))))
1244 
1245 (defun hg-status (path)
1246  "Print revision control status of a file or directory.
1247 With prefix argument, prompt for the path to give status for.
1248 Names are displayed relative to the repository root."
1249  (interactive (list (hg-read-file-name " for status" (hg-root))))
1250  (let ((root (hg-root)))
1251  (hg-view-output ((format "Mercurial: Status of %s in %s"
1252  (let ((name (substring (expand-file-name path)
1253  (length root))))
1254  (if (> (length name) 0)
1255  name
1256  "*"))
1257  (hg-abbrev-file-name root)))
1258  (apply 'call-process (hg-binary) nil t nil
1259  (list "--cwd" root "status" path))
1260  (cd (hg-root path)))))
1261 
1262 (defun hg-undo ()
1263  (interactive)
1264  (error "not implemented"))
1265 
1266 (defun hg-update ()
1267  (interactive)
1268  (error "not implemented"))
1269 
1270 (defun hg-version-other-window (rev)
1271  "Visit version REV of the current file in another window.
1272 If the current file is named `F', the version is named `F.~REV~'.
1273 If `F.~REV~' already exists, use it instead of checking it out again."
1274  (interactive "sVersion to visit (default is workfile version): ")
1275  (let* ((file buffer-file-name)
1276  (version (if (string-equal rev "")
1277  "tip"
1278  rev))
1279  (automatic-backup (vc-version-backup-file-name file version))
1280  (manual-backup (vc-version-backup-file-name file version 'manual)))
1281  (unless (file-exists-p manual-backup)
1282  (if (file-exists-p automatic-backup)
1283  (rename-file automatic-backup manual-backup nil)
1284  (hg-run0 "-q" "cat" "-r" version "-o" manual-backup file)))
1285  (find-file-other-window manual-backup)))
1286 
1287 
1288 (provide 'mercurial)
1289 
1290 
1291 ;;; Local Variables:
1292 ;;; prompt-to-byte-compile: nil
1293 ;;; end: