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