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