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