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