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