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