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