##// END OF EJS Templates
mercurial.el: fix misleading indent of code....
Vadim Gelfer -
r2518:d78ae783 default
parent child Browse files
Show More
@@ -1,1179 +1,1179 b''
1 1 ;;; mercurial.el --- Emacs support for the Mercurial distributed SCM
2 2
3 3 ;; Copyright (C) 2005 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 (buffer-file-name) default-directory)))
384 384 (if (or (not path) current-prefix-arg)
385 385 (expand-file-name
386 386 (eval (list* 'read-file-name
387 387 (format "File, directory or pattern%s: "
388 388 (or prompt ""))
389 389 (and path (file-name-directory path))
390 390 nil nil
391 391 (and path (file-name-nondirectory path))
392 392 (if hg-running-xemacs
393 393 (cons (quote 'hg-file-history) nil)
394 394 nil))))
395 395 path))))
396 396
397 397 (defun hg-read-number (&optional prompt default)
398 398 "Read a integer value."
399 399 (save-excursion
400 400 (if (or (not default) current-prefix-arg)
401 401 (string-to-number
402 402 (eval (list* 'read-string
403 403 (or prompt "")
404 404 (if default (cons (format "%d" default) nil) nil))))
405 405 default)))
406 406
407 407 (defun hg-read-config ()
408 408 "Return an alist of (key . value) pairs of Mercurial config data.
409 409 Each key is of the form (section . name)."
410 410 (let (items)
411 411 (dolist (line (split-string (hg-chomp (hg-run0 "debugconfig")) "\n") items)
412 412 (string-match "^\\([^=]*\\)=\\(.*\\)" line)
413 413 (let* ((left (substring line (match-beginning 1) (match-end 1)))
414 414 (right (substring line (match-beginning 2) (match-end 2)))
415 415 (key (split-string left "\\."))
416 416 (value (hg-replace-in-string right "\\\\n" "\n" t)))
417 417 (setq items (cons (cons (cons (car key) (cadr key)) value) items))))))
418 418
419 419 (defun hg-config-section (section config)
420 420 "Return an alist of (name . value) pairs for SECTION of CONFIG."
421 421 (let (items)
422 422 (dolist (item config items)
423 423 (when (equal (caar item) section)
424 424 (setq items (cons (cons (cdar item) (cdr item)) items))))))
425 425
426 426 (defun hg-string-starts-with (sub str)
427 427 "Indicate whether string STR starts with the substring or character SUB."
428 428 (if (not (stringp sub))
429 429 (and (> (length str) 0) (equal (elt str 0) sub))
430 430 (let ((sub-len (length sub)))
431 431 (and (<= sub-len (length str))
432 432 (string= sub (substring str 0 sub-len))))))
433 433
434 434 (defun hg-complete-repo (string predicate all)
435 435 "Attempt to complete a repository name.
436 436 We complete on either symbolic names from Mercurial's config or real
437 437 directory names from the file system. We do not penalise URLs."
438 438 (or (if all
439 439 (all-completions string hg-repo-completion-table predicate)
440 440 (try-completion string hg-repo-completion-table predicate))
441 441 (let* ((str (expand-file-name string))
442 442 (dir (file-name-directory str))
443 443 (file (file-name-nondirectory str)))
444 444 (if all
445 445 (let (completions)
446 446 (dolist (name (delete "./" (file-name-all-completions file dir))
447 447 completions)
448 448 (let ((path (concat dir name)))
449 449 (when (file-directory-p path)
450 450 (setq completions (cons name completions))))))
451 451 (let ((comp (file-name-completion file dir)))
452 452 (if comp
453 453 (hg-abbrev-file-name (concat dir comp))))))))
454 454
455 455 (defun hg-read-repo-name (&optional prompt initial-contents default)
456 456 "Read the location of a repository."
457 457 (save-excursion
458 458 (while hg-prev-buffer
459 459 (set-buffer hg-prev-buffer))
460 460 (let (hg-repo-completion-table)
461 461 (if current-prefix-arg
462 462 (progn
463 463 (dolist (path (hg-config-section "paths" (hg-read-config)))
464 464 (setq hg-repo-completion-table
465 465 (cons (cons (car path) t) hg-repo-completion-table))
466 466 (unless (hg-string-starts-with directory-sep-char (cdr path))
467 467 (setq hg-repo-completion-table
468 468 (cons (cons (cdr path) t) hg-repo-completion-table))))
469 469 (completing-read (format "Repository%s: " (or prompt ""))
470 470 'hg-complete-repo
471 471 nil
472 472 nil
473 473 initial-contents
474 474 'hg-repo-history
475 475 default))
476 476 default))))
477 477
478 478 (defun hg-read-rev (&optional prompt default)
479 479 "Read a revision or tag, offering completions."
480 480 (save-excursion
481 481 (while hg-prev-buffer
482 482 (set-buffer hg-prev-buffer))
483 483 (let ((rev (or default "tip")))
484 484 (if current-prefix-arg
485 485 (let ((revs (split-string
486 486 (hg-chomp
487 487 (hg-run0 "-q" "log" "-r"
488 488 (format "-%d:tip" hg-rev-completion-limit)))
489 489 "[\n:]")))
490 490 (dolist (line (split-string (hg-chomp (hg-run0 "tags")) "\n"))
491 491 (setq revs (cons (car (split-string line "\\s-")) revs)))
492 492 (completing-read (format "Revision%s (%s): "
493 493 (or prompt "")
494 494 (or default "tip"))
495 495 (map 'list 'cons revs revs)
496 496 nil
497 497 nil
498 498 nil
499 499 'hg-rev-history
500 500 (or default "tip")))
501 501 rev))))
502 502
503 503 (defmacro hg-do-across-repo (path &rest body)
504 504 (let ((root-name (gensym "root-"))
505 505 (buf-name (gensym "buf-")))
506 506 `(let ((,root-name (hg-root ,path)))
507 507 (save-excursion
508 508 (dolist (,buf-name (buffer-list))
509 509 (set-buffer ,buf-name)
510 510 (when (and hg-status (equal (hg-root buffer-file-name) ,root-name))
511 511 ,@body))))))
512 512
513 513 (put 'hg-do-across-repo 'lisp-indent-function 1)
514 514
515 515
516 516 ;;; View mode bits.
517 517
518 518 (defun hg-exit-view-mode (buf)
519 519 "Exit from hg-view-mode.
520 520 We delete the current window if entering hg-view-mode split the
521 521 current frame."
522 522 (when (and (eq buf (current-buffer))
523 523 (> (length (window-list)) 1))
524 524 (delete-window))
525 525 (when (buffer-live-p buf)
526 526 (kill-buffer buf)))
527 527
528 528 (defun hg-view-mode (prev-buffer &optional file-name)
529 529 (goto-char (point-min))
530 530 (set-buffer-modified-p nil)
531 531 (toggle-read-only t)
532 532 (view-minor-mode prev-buffer 'hg-exit-view-mode)
533 533 (use-local-map hg-view-mode-map)
534 534 (setq truncate-lines t)
535 535 (when file-name
536 536 (set (make-local-variable 'hg-view-file-name)
537 537 (hg-abbrev-file-name file-name))))
538 538
539 539 (defun hg-file-status (file)
540 540 "Return status of FILE, or nil if FILE does not exist or is unmanaged."
541 541 (let* ((s (hg-run "status" file))
542 542 (exit (car s))
543 543 (output (cdr s)))
544 544 (if (= exit 0)
545 545 (let ((state (assoc (substring output 0 (min (length output) 2))
546 546 '(("M " . modified)
547 547 ("A " . added)
548 548 ("R " . removed)
549 549 ("? " . nil)))))
550 550 (if state
551 551 (cdr state)
552 552 'normal)))))
553 553
554 554 (defun hg-tip ()
555 555 (split-string (hg-chomp (hg-run0 "-q" "tip")) ":"))
556 556
557 557 (defmacro hg-view-output (args &rest body)
558 558 "Execute BODY in a clean buffer, then quickly display that buffer.
559 559 If the buffer contains one line, its contents are displayed in the
560 560 minibuffer. Otherwise, the buffer is displayed in view-mode.
561 561 ARGS is of the form (BUFFER-NAME &optional FILE), where BUFFER-NAME is
562 562 the name of the buffer to create, and FILE is the name of the file
563 563 being viewed."
564 564 (let ((prev-buf (gensym "prev-buf-"))
565 565 (v-b-name (car args))
566 566 (v-m-rest (cdr args)))
567 567 `(let ((view-buf-name ,v-b-name)
568 568 (,prev-buf (current-buffer)))
569 569 (get-buffer-create view-buf-name)
570 570 (kill-buffer view-buf-name)
571 571 (get-buffer-create view-buf-name)
572 572 (set-buffer view-buf-name)
573 573 (save-excursion
574 574 ,@body)
575 575 (case (count-lines (point-min) (point-max))
576 576 ((0)
577 577 (kill-buffer view-buf-name)
578 578 (message "(No output)"))
579 579 ((1)
580 580 (let ((msg (hg-chomp (buffer-substring (point-min) (point-max)))))
581 581 (kill-buffer view-buf-name)
582 582 (message "%s" msg)))
583 583 (t
584 584 (pop-to-buffer view-buf-name)
585 585 (setq hg-prev-buffer ,prev-buf)
586 586 (hg-view-mode ,prev-buf ,@v-m-rest))))))
587 587
588 588 (put 'hg-view-output 'lisp-indent-function 1)
589 589
590 590 ;;; Context save and restore across revert.
591 591
592 592 (defun hg-position-context (pos)
593 593 "Return information to help find the given position again."
594 594 (let* ((end (min (point-max) (+ pos 98))))
595 595 (list pos
596 596 (buffer-substring (max (point-min) (- pos 2)) end)
597 597 (- end pos))))
598 598
599 599 (defun hg-buffer-context ()
600 600 "Return information to help restore a user's editing context.
601 601 This is useful across reverts and merges, where a context is likely
602 602 to have moved a little, but not really changed."
603 603 (let ((point-context (hg-position-context (point)))
604 604 (mark-context (let ((mark (mark-marker)))
605 605 (and mark (hg-position-context mark)))))
606 606 (list point-context mark-context)))
607 607
608 608 (defun hg-find-context (ctx)
609 609 "Attempt to find a context in the given buffer.
610 610 Always returns a valid, hopefully sane, position."
611 611 (let ((pos (nth 0 ctx))
612 612 (str (nth 1 ctx))
613 613 (fixup (nth 2 ctx)))
614 614 (save-excursion
615 615 (goto-char (max (point-min) (- pos 15000)))
616 616 (if (and (not (equal str ""))
617 617 (search-forward str nil t))
618 618 (- (point) fixup)
619 619 (max pos (point-min))))))
620 620
621 621 (defun hg-restore-context (ctx)
622 622 "Attempt to restore the user's editing context."
623 623 (let ((point-context (nth 0 ctx))
624 624 (mark-context (nth 1 ctx)))
625 625 (goto-char (hg-find-context point-context))
626 626 (when mark-context
627 627 (set-mark (hg-find-context mark-context)))))
628 628
629 629
630 630 ;;; Hooks.
631 631
632 632 (defun hg-mode-line (&optional force)
633 633 "Update the modeline with the current status of a file.
634 634 An update occurs if optional argument FORCE is non-nil,
635 635 hg-update-modeline is non-nil, or we have not yet checked the state of
636 636 the file."
637 637 (when (and (hg-root) (or force hg-update-modeline (not hg-mode)))
638 638 (let ((status (hg-file-status buffer-file-name)))
639 639 (setq hg-status status
640 640 hg-mode (and status (concat " Hg:"
641 641 (car (hg-tip))
642 642 (cdr (assq status
643 643 '((normal . "")
644 644 (removed . "r")
645 645 (added . "a")
646 646 (modified . "m")))))))
647 647 status)))
648 648
649 649 (defun hg-mode (&optional toggle)
650 650 "Minor mode for Mercurial distributed SCM integration.
651 651
652 652 The Mercurial mode user interface is based on that of VC mode, so if
653 653 you're already familiar with VC, the same keybindings and functions
654 654 will generally work.
655 655
656 656 Below is a list of many common SCM tasks. In the list, `G/L\'
657 657 indicates whether a key binding is global (G) to a repository or local
658 658 (L) to a file. Many commands take a prefix argument.
659 659
660 660 SCM Task G/L Key Binding Command Name
661 661 -------- --- ----------- ------------
662 662 Help overview (what you are reading) G C-c h h hg-help-overview
663 663
664 664 Tell Mercurial to manage a file G C-c h a hg-add
665 665 Commit changes to current file only L C-x v n hg-commit-start
666 666 Undo changes to file since commit L C-x v u hg-revert-buffer
667 667
668 668 Diff file vs last checkin L C-x v = hg-diff
669 669
670 670 View file change history L C-x v l hg-log
671 671 View annotated file L C-x v a hg-annotate
672 672
673 673 Diff repo vs last checkin G C-c h = hg-diff-repo
674 674 View status of files in repo G C-c h s hg-status
675 675 Commit all changes G C-c h c hg-commit-start
676 676
677 677 Undo all changes since last commit G C-c h U hg-revert
678 678 View repo change history G C-c h l hg-log-repo
679 679
680 680 See changes that can be pulled G C-c h , hg-incoming
681 681 Pull changes G C-c h < hg-pull
682 682 Update working directory after pull G C-c h u hg-update
683 683 See changes that can be pushed G C-c h . hg-outgoing
684 684 Push changes G C-c h > hg-push"
685 685 (unless vc-make-backup-files
686 686 (set (make-local-variable 'backup-inhibited) t))
687 (run-hooks 'hg-mode-hook))
687 (run-hooks 'hg-mode-hook))
688 688
689 689 (defun hg-find-file-hook ()
690 690 (when (hg-mode-line)
691 691 (hg-mode)))
692 692
693 693 (add-hook 'find-file-hooks 'hg-find-file-hook)
694 694
695 695 (defun hg-after-save-hook ()
696 696 (let ((old-status hg-status))
697 697 (hg-mode-line)
698 698 (if (and (not old-status) hg-status)
699 699 (hg-mode))))
700 700
701 701 (add-hook 'after-save-hook 'hg-after-save-hook)
702 702
703 703
704 704 ;;; User interface functions.
705 705
706 706 (defun hg-help-overview ()
707 707 "This is an overview of the Mercurial SCM mode for Emacs.
708 708
709 709 You can find the source code, license (GPL v2), and credits for this
710 710 code by typing `M-x find-library mercurial RET'."
711 711 (interactive)
712 712 (hg-view-output ("Mercurial Help Overview")
713 713 (insert (documentation 'hg-help-overview))
714 714 (let ((pos (point)))
715 715 (insert (documentation 'hg-mode))
716 716 (goto-char pos)
717 717 (end-of-line 1)
718 718 (delete-region pos (point)))
719 719 (cd (hg-root))))
720 720
721 721 (defun hg-add (path)
722 722 "Add PATH to the Mercurial repository on the next commit.
723 723 With a prefix argument, prompt for the path to add."
724 724 (interactive (list (hg-read-file-name " to add")))
725 725 (let ((buf (current-buffer))
726 726 (update (equal buffer-file-name path)))
727 727 (hg-view-output (hg-output-buffer-name)
728 728 (apply 'call-process (hg-binary) nil t nil (list "add" path))
729 729 ;; "hg add" shows pathes relative NOT TO ROOT BUT TO REPOSITORY
730 730 (replace-regexp " \\.\\.." " " nil 0 (buffer-size))
731 731 (goto-char 0)
732 732 (cd (hg-root path)))
733 733 (when update
734 734 (unless vc-make-backup-files
735 735 (set (make-local-variable 'backup-inhibited) t))
736 736 (with-current-buffer buf
737 737 (hg-mode-line)))))
738 738
739 739 (defun hg-addremove ()
740 740 (interactive)
741 741 (error "not implemented"))
742 742
743 743 (defun hg-annotate ()
744 744 (interactive)
745 745 (error "not implemented"))
746 746
747 747 (defun hg-commit-toggle-file (pos)
748 748 "Toggle whether or not the file at POS will be committed."
749 749 (interactive "d")
750 750 (save-excursion
751 751 (goto-char pos)
752 752 (let ((face (get-text-property pos 'face))
753 753 (inhibit-read-only t)
754 754 bol)
755 755 (beginning-of-line)
756 756 (setq bol (+ (point) 4))
757 757 (end-of-line)
758 758 (if (eq face 'bold)
759 759 (progn
760 760 (remove-text-properties bol (point) '(face nil))
761 761 (message "%s will not be committed"
762 762 (buffer-substring bol (point))))
763 763 (add-text-properties bol (point) '(face bold))
764 764 (message "%s will be committed"
765 765 (buffer-substring bol (point)))))))
766 766
767 767 (defun hg-commit-mouse-clicked (event)
768 768 "Toggle whether or not the file at POS will be committed."
769 769 (interactive "@e")
770 770 (hg-commit-toggle-file (hg-event-point event)))
771 771
772 772 (defun hg-commit-kill ()
773 773 "Kill the commit currently being prepared."
774 774 (interactive)
775 775 (when (or (not (buffer-modified-p)) (y-or-n-p "Really kill this commit? "))
776 776 (let ((buf hg-prev-buffer))
777 777 (kill-buffer nil)
778 778 (switch-to-buffer buf))))
779 779
780 780 (defun hg-commit-finish ()
781 781 "Finish preparing a commit, and perform the actual commit.
782 782 The hook hg-pre-commit-hook is run before anything else is done. If
783 783 the commit message is empty and hg-commit-allow-empty-message is nil,
784 784 an error is raised. If the list of files to commit is empty and
785 785 hg-commit-allow-empty-file-list is nil, an error is raised."
786 786 (interactive)
787 787 (let ((root hg-root))
788 788 (save-excursion
789 789 (run-hooks 'hg-pre-commit-hook)
790 790 (goto-char (point-min))
791 791 (search-forward hg-commit-message-start)
792 792 (let (message files)
793 793 (let ((start (point)))
794 794 (goto-char (point-max))
795 795 (search-backward hg-commit-message-end)
796 796 (setq message (hg-strip (buffer-substring start (point)))))
797 797 (when (and (= (length message) 0)
798 798 (not hg-commit-allow-empty-message))
799 799 (error "Cannot proceed - commit message is empty"))
800 800 (forward-line 1)
801 801 (beginning-of-line)
802 802 (while (< (point) (point-max))
803 803 (let ((pos (+ (point) 4)))
804 804 (end-of-line)
805 805 (when (eq (get-text-property pos 'face) 'bold)
806 806 (end-of-line)
807 807 (setq files (cons (buffer-substring pos (point)) files))))
808 808 (forward-line 1))
809 809 (when (and (= (length files) 0)
810 810 (not hg-commit-allow-empty-file-list))
811 811 (error "Cannot proceed - no files to commit"))
812 812 (setq message (concat message "\n"))
813 813 (apply 'hg-run0 "--cwd" hg-root "commit" "-m" message files))
814 814 (let ((buf hg-prev-buffer))
815 815 (kill-buffer nil)
816 816 (switch-to-buffer buf))
817 817 (hg-do-across-repo root
818 818 (hg-mode-line)))))
819 819
820 820 (defun hg-commit-mode ()
821 821 "Mode for describing a commit of changes to a Mercurial repository.
822 822 This involves two actions: describing the changes with a commit
823 823 message, and choosing the files to commit.
824 824
825 825 To describe the commit, simply type some text in the designated area.
826 826
827 827 By default, all modified, added and removed files are selected for
828 828 committing. Files that will be committed are displayed in bold face\;
829 829 those that will not are displayed in normal face.
830 830
831 831 To toggle whether a file will be committed, move the cursor over a
832 832 particular file and hit space or return. Alternatively, middle click
833 833 on the file.
834 834
835 835 Key bindings
836 836 ------------
837 837 \\[hg-commit-finish] proceed with commit
838 838 \\[hg-commit-kill] kill commit
839 839
840 840 \\[hg-diff-repo] view diff of pending changes"
841 841 (interactive)
842 842 (use-local-map hg-commit-mode-map)
843 843 (set-syntax-table text-mode-syntax-table)
844 844 (setq local-abbrev-table text-mode-abbrev-table
845 845 major-mode 'hg-commit-mode
846 846 mode-name "Hg-Commit")
847 847 (set-buffer-modified-p nil)
848 848 (setq buffer-undo-list nil)
849 849 (run-hooks 'text-mode-hook 'hg-commit-mode-hook))
850 850
851 851 (defun hg-commit-start ()
852 852 "Prepare a commit of changes to the repository containing the current file."
853 853 (interactive)
854 854 (while hg-prev-buffer
855 855 (set-buffer hg-prev-buffer))
856 856 (let ((root (hg-root))
857 857 (prev-buffer (current-buffer))
858 858 modified-files)
859 859 (unless root
860 860 (error "Cannot commit outside a repository!"))
861 861 (hg-sync-buffers root)
862 862 (setq modified-files (hg-chomp (hg-run0 "--cwd" root "status" "-arm")))
863 863 (when (and (= (length modified-files) 0)
864 864 (not hg-commit-allow-empty-file-list))
865 865 (error "No pending changes to commit"))
866 866 (let* ((buf-name (format "*Mercurial: Commit %s*" root)))
867 867 (pop-to-buffer (get-buffer-create buf-name))
868 868 (when (= (point-min) (point-max))
869 869 (set (make-local-variable 'hg-root) root)
870 870 (setq hg-prev-buffer prev-buffer)
871 871 (insert "\n")
872 872 (let ((bol (point)))
873 873 (insert hg-commit-message-end)
874 874 (add-text-properties bol (point) '(face bold-italic)))
875 875 (let ((file-area (point)))
876 876 (insert modified-files)
877 877 (goto-char file-area)
878 878 (while (< (point) (point-max))
879 879 (let ((bol (point)))
880 880 (forward-char 1)
881 881 (insert " ")
882 882 (end-of-line)
883 883 (add-text-properties (+ bol 4) (point)
884 884 '(face bold mouse-face highlight)))
885 885 (forward-line 1))
886 886 (goto-char file-area)
887 887 (add-text-properties (point) (point-max)
888 888 `(keymap ,hg-commit-mode-file-map))
889 889 (goto-char (point-min))
890 890 (insert hg-commit-message-start)
891 891 (add-text-properties (point-min) (point) '(face bold-italic))
892 892 (insert "\n\n")
893 893 (forward-line -1)
894 894 (save-excursion
895 895 (goto-char (point-max))
896 896 (search-backward hg-commit-message-end)
897 897 (add-text-properties (match-beginning 0) (point-max)
898 898 '(read-only t))
899 899 (goto-char (point-min))
900 900 (search-forward hg-commit-message-start)
901 901 (add-text-properties (match-beginning 0) (match-end 0)
902 902 '(read-only t)))
903 903 (hg-commit-mode)
904 904 (cd root))))))
905 905
906 906 (defun hg-diff (path &optional rev1 rev2)
907 907 "Show the differences between REV1 and REV2 of PATH.
908 908 When called interactively, the default behaviour is to treat REV1 as
909 909 the \"parent\" revision, REV2 as the current edited version of the file, and
910 910 PATH as the file edited in the current buffer.
911 911 With a prefix argument, prompt for all of these."
912 912 (interactive (list (hg-read-file-name " to diff")
913 913 (let ((rev1 (hg-read-rev " to start with" 'parent)))
914 914 (and (not (eq rev1 'parent)) rev1))
915 915 (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
916 916 (and (not (eq rev2 'working-dir)) rev2))))
917 917 (hg-sync-buffers path)
918 918 (let ((a-path (hg-abbrev-file-name path))
919 919 ;; none revision is specified explicitly
920 920 (none (and (not rev1) (not rev2)))
921 921 ;; only one revision is specified explicitly
922 922 (one (or (and (or (equal rev1 rev2) (not rev2)) rev1)
923 923 (and (not rev1) rev2)))
924 924 diff)
925 925 (hg-view-output ((cond
926 926 (none
927 927 (format "Mercurial: Diff against parent of %s" a-path))
928 928 (one
929 929 (format "Mercurial: Diff of rev %s of %s" one a-path))
930 930 (t
931 931 (format "Mercurial: Diff from rev %s to %s of %s"
932 932 rev1 rev2 a-path))))
933 933 (cond
934 934 (none
935 935 (call-process (hg-binary) nil t nil "diff" path))
936 936 (one
937 937 (call-process (hg-binary) nil t nil "diff" "-r" one path))
938 938 (t
939 939 (call-process (hg-binary) nil t nil "diff" "-r" rev1 "-r" rev2 path)))
940 940 (diff-mode)
941 941 (setq diff (not (= (point-min) (point-max))))
942 942 (font-lock-fontify-buffer)
943 943 (cd (hg-root path)))
944 944 diff))
945 945
946 946 (defun hg-diff-repo (path &optional rev1 rev2)
947 947 "Show the differences between REV1 and REV2 of repository containing PATH.
948 948 When called interactively, the default behaviour is to treat REV1 as
949 949 the \"parent\" revision, REV2 as the current edited version of the file, and
950 950 PATH as the `hg-root' of the current buffer.
951 951 With a prefix argument, prompt for all of these."
952 952 (interactive (list (hg-read-file-name " to diff")
953 953 (let ((rev1 (hg-read-rev " to start with" 'parent)))
954 954 (and (not (eq rev1 'parent)) rev1))
955 955 (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
956 956 (and (not (eq rev2 'working-dir)) rev2))))
957 957 (hg-diff (hg-root path) rev1 rev2))
958 958
959 959 (defun hg-forget (path)
960 960 "Lose track of PATH, which has been added, but not yet committed.
961 961 This will prevent the file from being incorporated into the Mercurial
962 962 repository on the next commit.
963 963 With a prefix argument, prompt for the path to forget."
964 964 (interactive (list (hg-read-file-name " to forget")))
965 965 (let ((buf (current-buffer))
966 966 (update (equal buffer-file-name path)))
967 967 (hg-view-output (hg-output-buffer-name)
968 968 (apply 'call-process (hg-binary) nil t nil (list "forget" path))
969 969 ;; "hg forget" shows pathes relative NOT TO ROOT BUT TO REPOSITORY
970 970 (replace-regexp " \\.\\.." " " nil 0 (buffer-size))
971 971 (goto-char 0)
972 972 (cd (hg-root path)))
973 973 (when update
974 974 (with-current-buffer buf
975 975 (set (make-local-variable 'backup-inhibited) nil)
976 976 (hg-mode-line)))))
977 977
978 978 (defun hg-incoming (&optional repo)
979 979 "Display changesets present in REPO that are not present locally."
980 980 (interactive (list (hg-read-repo-name " where changes would come from")))
981 981 (hg-view-output ((format "Mercurial: Incoming from %s to %s"
982 982 (hg-abbrev-file-name (hg-root))
983 983 (hg-abbrev-file-name
984 984 (or repo hg-incoming-repository))))
985 985 (call-process (hg-binary) nil t nil "incoming"
986 986 (or repo hg-incoming-repository))
987 987 (hg-log-mode)
988 988 (cd (hg-root))))
989 989
990 990 (defun hg-init ()
991 991 (interactive)
992 992 (error "not implemented"))
993 993
994 994 (defun hg-log-mode ()
995 995 "Mode for viewing a Mercurial change log."
996 996 (goto-char (point-min))
997 997 (when (looking-at "^searching for changes.*$")
998 998 (delete-region (match-beginning 0) (match-end 0)))
999 999 (run-hooks 'hg-log-mode-hook))
1000 1000
1001 1001 (defun hg-log (path &optional rev1 rev2 log-limit)
1002 1002 "Display the revision history of PATH.
1003 1003 History is displayed between REV1 and REV2.
1004 1004 Number of displayed changesets is limited to LOG-LIMIT.
1005 1005 REV1 defaults to the tip, while
1006 1006 REV2 defaults to `hg-rev-completion-limit' changes from the tip revision.
1007 1007 LOG-LIMIT defaults to `hg-log-limit'.
1008 1008 With a prefix argument, prompt for each parameter."
1009 1009 (interactive (list (hg-read-file-name " to log")
1010 1010 (hg-read-rev " to start with"
1011 1011 "tip")
1012 1012 (hg-read-rev " to end with"
1013 1013 (format "%d" (- hg-rev-completion-limit)))
1014 1014 (hg-read-number "Output limited to: "
1015 1015 hg-log-limit)))
1016 1016 (let ((a-path (hg-abbrev-file-name path))
1017 1017 (r1 (or rev1 (format "-%d" hg-rev-completion-limit)))
1018 1018 (r2 (or rev2 rev1 "tip"))
1019 1019 (limit (format "%d" (or log-limit hg-log-limit))))
1020 1020 (hg-view-output ((if (equal r1 r2)
1021 1021 (format "Mercurial: Log of rev %s of %s" rev1 a-path)
1022 1022 (format
1023 1023 "Mercurial: at most %s log(s) from rev %s to %s of %s"
1024 1024 limit r1 r2 a-path)))
1025 1025 (eval (list* 'call-process (hg-binary) nil t nil
1026 1026 "log"
1027 1027 "-r" (format "%s:%s" r1 r2)
1028 1028 "-l" limit
1029 1029 (if (> (length path) (length (hg-root path)))
1030 1030 (cons path nil)
1031 1031 nil)))
1032 1032 (hg-log-mode)
1033 1033 (cd (hg-root path)))))
1034 1034
1035 1035 (defun hg-log-repo (path &optional rev1 rev2 log-limit)
1036 1036 "Display the revision history of the repository containing PATH.
1037 1037 History is displayed between REV1 and REV2.
1038 1038 Number of displayed changesets is limited to LOG-LIMIT,
1039 1039 REV1 defaults to the tip, while
1040 1040 REV2 defaults to `hg-rev-completion-limit' changes from the tip revision.
1041 1041 LOG-LIMIT defaults to `hg-log-limit'.
1042 1042 With a prefix argument, prompt for each parameter."
1043 1043 (interactive (list (hg-read-file-name " to log")
1044 1044 (hg-read-rev " to start with"
1045 1045 "tip")
1046 1046 (hg-read-rev " to end with"
1047 1047 (format "%d" (- hg-rev-completion-limit)))
1048 1048 (hg-read-number "Output limited to: "
1049 1049 hg-log-limit)))
1050 1050 (hg-log (hg-root path) rev1 rev2 log-limit))
1051 1051
1052 1052 (defun hg-outgoing (&optional repo)
1053 1053 "Display changesets present locally that are not present in REPO."
1054 1054 (interactive (list (hg-read-repo-name " where changes would go to" nil
1055 1055 hg-outgoing-repository)))
1056 1056 (hg-view-output ((format "Mercurial: Outgoing from %s to %s"
1057 1057 (hg-abbrev-file-name (hg-root))
1058 1058 (hg-abbrev-file-name
1059 1059 (or repo hg-outgoing-repository))))
1060 1060 (call-process (hg-binary) nil t nil "outgoing"
1061 1061 (or repo hg-outgoing-repository))
1062 1062 (hg-log-mode)
1063 1063 (cd (hg-root))))
1064 1064
1065 1065 (defun hg-pull (&optional repo)
1066 1066 "Pull changes from repository REPO.
1067 1067 This does not update the working directory."
1068 1068 (interactive (list (hg-read-repo-name " to pull from")))
1069 1069 (hg-view-output ((format "Mercurial: Pull to %s from %s"
1070 1070 (hg-abbrev-file-name (hg-root))
1071 1071 (hg-abbrev-file-name
1072 1072 (or repo hg-incoming-repository))))
1073 1073 (call-process (hg-binary) nil t nil "pull"
1074 1074 (or repo hg-incoming-repository))
1075 1075 (cd (hg-root))))
1076 1076
1077 1077 (defun hg-push (&optional repo)
1078 1078 "Push changes to repository REPO."
1079 1079 (interactive (list (hg-read-repo-name " to push to")))
1080 1080 (hg-view-output ((format "Mercurial: Push from %s to %s"
1081 1081 (hg-abbrev-file-name (hg-root))
1082 1082 (hg-abbrev-file-name
1083 1083 (or repo hg-outgoing-repository))))
1084 1084 (call-process (hg-binary) nil t nil "push"
1085 1085 (or repo hg-outgoing-repository))
1086 1086 (cd (hg-root))))
1087 1087
1088 1088 (defun hg-revert-buffer-internal ()
1089 1089 (let ((ctx (hg-buffer-context)))
1090 1090 (message "Reverting %s..." buffer-file-name)
1091 1091 (hg-run0 "revert" buffer-file-name)
1092 1092 (revert-buffer t t t)
1093 1093 (hg-restore-context ctx)
1094 1094 (hg-mode-line)
1095 1095 (message "Reverting %s...done" buffer-file-name)))
1096 1096
1097 1097 (defun hg-revert-buffer ()
1098 1098 "Revert current buffer's file back to the latest committed version.
1099 1099 If the file has not changed, nothing happens. Otherwise, this
1100 1100 displays a diff and asks for confirmation before reverting."
1101 1101 (interactive)
1102 1102 (let ((vc-suppress-confirm nil)
1103 1103 (obuf (current-buffer))
1104 1104 diff)
1105 1105 (vc-buffer-sync)
1106 1106 (unwind-protect
1107 1107 (setq diff (hg-diff buffer-file-name))
1108 1108 (when diff
1109 1109 (unless (yes-or-no-p "Discard changes? ")
1110 1110 (error "Revert cancelled")))
1111 1111 (when diff
1112 1112 (let ((buf (current-buffer)))
1113 1113 (delete-window (selected-window))
1114 1114 (kill-buffer buf))))
1115 1115 (set-buffer obuf)
1116 1116 (when diff
1117 1117 (hg-revert-buffer-internal))))
1118 1118
1119 1119 (defun hg-root (&optional path)
1120 1120 "Return the root of the repository that contains the given path.
1121 1121 If the path is outside a repository, return nil.
1122 1122 When called interactively, the root is printed. A prefix argument
1123 1123 prompts for a path to check."
1124 1124 (interactive (list (hg-read-file-name)))
1125 1125 (if (or path (not hg-root))
1126 1126 (let ((root (do ((prev nil dir)
1127 1127 (dir (file-name-directory
1128 1128 (or
1129 1129 path
1130 1130 buffer-file-name
1131 1131 (expand-file-name default-directory)))
1132 1132 (file-name-directory (directory-file-name dir))))
1133 1133 ((equal prev dir))
1134 1134 (when (file-directory-p (concat dir ".hg"))
1135 1135 (return dir)))))
1136 1136 (when (interactive-p)
1137 1137 (if root
1138 1138 (message "The root of this repository is `%s'." root)
1139 1139 (message "The path `%s' is not in a Mercurial repository."
1140 1140 (hg-abbrev-file-name path))))
1141 1141 root)
1142 1142 hg-root))
1143 1143
1144 1144 (defun hg-status (path)
1145 1145 "Print revision control status of a file or directory.
1146 1146 With prefix argument, prompt for the path to give status for.
1147 1147 Names are displayed relative to the repository root."
1148 1148 (interactive (list (hg-read-file-name " for status" (hg-root))))
1149 1149 (let ((root (hg-root)))
1150 1150 (hg-view-output ((format "Mercurial: Status of %s in %s"
1151 1151 (let ((name (substring (expand-file-name path)
1152 1152 (length root))))
1153 1153 (if (> (length name) 0)
1154 1154 name
1155 1155 "*"))
1156 1156 (hg-abbrev-file-name root)))
1157 1157 (apply 'call-process (hg-binary) nil t nil
1158 1158 (list "--cwd" root "status" path))
1159 1159 (cd (hg-root path)))))
1160 1160
1161 1161 (defun hg-undo ()
1162 1162 (interactive)
1163 1163 (error "not implemented"))
1164 1164
1165 1165 (defun hg-update ()
1166 1166 (interactive)
1167 1167 (error "not implemented"))
1168 1168
1169 1169 (defun hg-version-other-window ()
1170 1170 (interactive)
1171 1171 (error "not implemented"))
1172 1172
1173 1173
1174 1174 (provide 'mercurial)
1175 1175
1176 1176
1177 1177 ;;; Local Variables:
1178 1178 ;;; prompt-to-byte-compile: nil
1179 1179 ;;; end:
General Comments 0
You need to be logged in to leave comments. Login now