##// END OF EJS Templates
mercurial.el: don't raise an error from find-file-hook...
NIIMI Satoshi -
r4413:b008deae default
parent child Browse files
Show More
@@ -1,1267 +1,1268 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 320 (defmacro hg-do-across-repo (path &rest body)
321 321 (let ((root-name (gensym "root-"))
322 322 (buf-name (gensym "buf-")))
323 323 `(let ((,root-name (hg-root ,path)))
324 324 (save-excursion
325 325 (dolist (,buf-name (buffer-list))
326 326 (set-buffer ,buf-name)
327 327 (when (and hg-status (equal (hg-root buffer-file-name) ,root-name))
328 328 ,@body))))))
329 329
330 330 (put 'hg-do-across-repo 'lisp-indent-function 1)
331 331
332 332 (defun hg-sync-buffers (path)
333 333 "Sync buffers visiting PATH with their on-disk copies.
334 334 If PATH is not being visited, but is under the repository root, sync
335 335 all buffers visiting files in the repository."
336 336 (let ((buf (find-buffer-visiting path)))
337 337 (if buf
338 338 (with-current-buffer buf
339 339 (vc-buffer-sync))
340 340 (hg-do-across-repo path
341 341 (vc-buffer-sync)))))
342 342
343 343 (defun hg-buffer-commands (pnt)
344 344 "Use the properties of a character to do something sensible."
345 345 (interactive "d")
346 346 (let ((rev (get-char-property pnt 'rev))
347 347 (file (get-char-property pnt 'file))
348 348 (date (get-char-property pnt 'date))
349 349 (user (get-char-property pnt 'user))
350 350 (host (get-char-property pnt 'host))
351 351 (prev-buf (current-buffer)))
352 352 (cond
353 353 (file
354 354 (find-file-other-window file))
355 355 (rev
356 356 (hg-diff hg-view-file-name rev rev prev-buf))
357 357 ((message "I don't know how to do that yet")))))
358 358
359 359 (defsubst hg-event-point (event)
360 360 "Return the character position of the mouse event EVENT."
361 361 (if hg-running-xemacs
362 362 (event-point event)
363 363 (posn-point (event-start event))))
364 364
365 365 (defsubst hg-event-window (event)
366 366 "Return the window over which mouse event EVENT occurred."
367 367 (if hg-running-xemacs
368 368 (event-window event)
369 369 (posn-window (event-start event))))
370 370
371 371 (defun hg-buffer-mouse-clicked (event)
372 372 "Translate the mouse clicks in a HG log buffer to character events.
373 373 These are then handed off to `hg-buffer-commands'.
374 374
375 375 Handle frickin' frackin' gratuitous event-related incompatibilities."
376 376 (interactive "e")
377 377 (select-window (hg-event-window event))
378 378 (hg-buffer-commands (hg-event-point event)))
379 379
380 380 (unless (fboundp 'view-minor-mode)
381 381 (defun view-minor-mode (prev-buffer exit-func)
382 382 (view-mode)))
383 383
384 384 (defsubst hg-abbrev-file-name (file)
385 385 "Portable wrapper around abbreviate-file-name."
386 386 (if hg-running-xemacs
387 387 (abbreviate-file-name file t)
388 388 (abbreviate-file-name file)))
389 389
390 390 (defun hg-read-file-name (&optional prompt default)
391 391 "Read a file or directory name, or a pattern, to use with a command."
392 392 (save-excursion
393 393 (while hg-prev-buffer
394 394 (set-buffer hg-prev-buffer))
395 395 (let ((path (or default
396 396 (buffer-file-name)
397 397 (expand-file-name default-directory))))
398 398 (if (or (not path) current-prefix-arg)
399 399 (expand-file-name
400 400 (eval (list* 'read-file-name
401 401 (format "File, directory or pattern%s: "
402 402 (or prompt ""))
403 403 (and path (file-name-directory path))
404 404 nil nil
405 405 (and path (file-name-nondirectory path))
406 406 (if hg-running-xemacs
407 407 (cons (quote 'hg-file-history) nil)
408 408 nil))))
409 409 path))))
410 410
411 411 (defun hg-read-number (&optional prompt default)
412 412 "Read a integer value."
413 413 (save-excursion
414 414 (if (or (not default) current-prefix-arg)
415 415 (string-to-number
416 416 (eval (list* 'read-string
417 417 (or prompt "")
418 418 (if default (cons (format "%d" default) nil) nil))))
419 419 default)))
420 420
421 421 (defun hg-read-config ()
422 422 "Return an alist of (key . value) pairs of Mercurial config data.
423 423 Each key is of the form (section . name)."
424 424 (let (items)
425 425 (dolist (line (split-string (hg-chomp (hg-run0 "debugconfig")) "\n") items)
426 426 (string-match "^\\([^=]*\\)=\\(.*\\)" line)
427 427 (let* ((left (substring line (match-beginning 1) (match-end 1)))
428 428 (right (substring line (match-beginning 2) (match-end 2)))
429 429 (key (split-string left "\\."))
430 430 (value (hg-replace-in-string right "\\\\n" "\n" t)))
431 431 (setq items (cons (cons (cons (car key) (cadr key)) value) items))))))
432 432
433 433 (defun hg-config-section (section config)
434 434 "Return an alist of (name . value) pairs for SECTION of CONFIG."
435 435 (let (items)
436 436 (dolist (item config items)
437 437 (when (equal (caar item) section)
438 438 (setq items (cons (cons (cdar item) (cdr item)) items))))))
439 439
440 440 (defun hg-string-starts-with (sub str)
441 441 "Indicate whether string STR starts with the substring or character SUB."
442 442 (if (not (stringp sub))
443 443 (and (> (length str) 0) (equal (elt str 0) sub))
444 444 (let ((sub-len (length sub)))
445 445 (and (<= sub-len (length str))
446 446 (string= sub (substring str 0 sub-len))))))
447 447
448 448 (defun hg-complete-repo (string predicate all)
449 449 "Attempt to complete a repository name.
450 450 We complete on either symbolic names from Mercurial's config or real
451 451 directory names from the file system. We do not penalise URLs."
452 452 (or (if all
453 453 (all-completions string hg-repo-completion-table predicate)
454 454 (try-completion string hg-repo-completion-table predicate))
455 455 (let* ((str (expand-file-name string))
456 456 (dir (file-name-directory str))
457 457 (file (file-name-nondirectory str)))
458 458 (if all
459 459 (let (completions)
460 460 (dolist (name (delete "./" (file-name-all-completions file dir))
461 461 completions)
462 462 (let ((path (concat dir name)))
463 463 (when (file-directory-p path)
464 464 (setq completions (cons name completions))))))
465 465 (let ((comp (file-name-completion file dir)))
466 466 (if comp
467 467 (hg-abbrev-file-name (concat dir comp))))))))
468 468
469 469 (defun hg-read-repo-name (&optional prompt initial-contents default)
470 470 "Read the location of a repository."
471 471 (save-excursion
472 472 (while hg-prev-buffer
473 473 (set-buffer hg-prev-buffer))
474 474 (let (hg-repo-completion-table)
475 475 (if current-prefix-arg
476 476 (progn
477 477 (dolist (path (hg-config-section "paths" (hg-read-config)))
478 478 (setq hg-repo-completion-table
479 479 (cons (cons (car path) t) hg-repo-completion-table))
480 480 (unless (hg-string-starts-with directory-sep-char (cdr path))
481 481 (setq hg-repo-completion-table
482 482 (cons (cons (cdr path) t) hg-repo-completion-table))))
483 483 (completing-read (format "Repository%s: " (or prompt ""))
484 484 'hg-complete-repo
485 485 nil
486 486 nil
487 487 initial-contents
488 488 'hg-repo-history
489 489 default))
490 490 default))))
491 491
492 492 (defun hg-read-rev (&optional prompt default)
493 493 "Read a revision or tag, offering completions."
494 494 (save-excursion
495 495 (while hg-prev-buffer
496 496 (set-buffer hg-prev-buffer))
497 497 (let ((rev (or default "tip")))
498 498 (if current-prefix-arg
499 499 (let ((revs (split-string
500 500 (hg-chomp
501 501 (hg-run0 "-q" "log" "-r"
502 502 (format "-%d:tip" hg-rev-completion-limit)))
503 503 "[\n:]")))
504 504 (dolist (line (split-string (hg-chomp (hg-run0 "tags")) "\n"))
505 505 (setq revs (cons (car (split-string line "\\s-")) revs)))
506 506 (completing-read (format "Revision%s (%s): "
507 507 (or prompt "")
508 508 (or default "tip"))
509 509 (map 'list 'cons revs revs)
510 510 nil
511 511 nil
512 512 nil
513 513 'hg-rev-history
514 514 (or default "tip")))
515 515 rev))))
516 516
517 517 (defun hg-parents-for-mode-line (root)
518 518 "Format the parents of the working directory for the mode line."
519 519 (let ((parents (split-string (hg-chomp
520 520 (hg-run0 "--cwd" root "parents" "--template"
521 521 "{rev}\n")) "\n")))
522 522 (mapconcat 'identity parents "+")))
523 523
524 524 (defun hg-buffers-visiting-repo (&optional path)
525 525 "Return a list of buffers visiting the repository containing PATH."
526 526 (let ((root-name (hg-root (or path (buffer-file-name))))
527 527 bufs)
528 528 (save-excursion
529 529 (dolist (buf (buffer-list) bufs)
530 530 (set-buffer buf)
531 531 (let ((name (buffer-file-name)))
532 532 (when (and hg-status name (equal (hg-root name) root-name))
533 533 (setq bufs (cons buf bufs))))))))
534 534
535 535 (defun hg-update-mode-lines (path)
536 536 "Update the mode lines of all buffers visiting the same repository as PATH."
537 537 (let* ((root (hg-root path))
538 538 (parents (hg-parents-for-mode-line root)))
539 539 (save-excursion
540 540 (dolist (info (hg-path-status
541 541 root
542 542 (mapcar
543 543 (function
544 544 (lambda (buf)
545 545 (substring (buffer-file-name buf) (length root))))
546 546 (hg-buffers-visiting-repo root))))
547 547 (let* ((name (car info))
548 548 (status (cdr info))
549 549 (buf (find-buffer-visiting (concat root name))))
550 550 (when buf
551 551 (set-buffer buf)
552 552 (hg-mode-line-internal status parents)))))))
553 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 (when (hg-mode-line)
754 (hg-mode)))
753 (ignore-errors
754 (when (hg-mode-line)
755 (hg-mode))))
755 756
756 757 (add-hook 'find-file-hooks 'hg-find-file-hook)
757 758
758 759 (defun hg-after-save-hook ()
759 760 (let ((old-status hg-status))
760 761 (hg-mode-line)
761 762 (if (and (not old-status) hg-status)
762 763 (hg-mode))))
763 764
764 765 (add-hook 'after-save-hook 'hg-after-save-hook)
765 766
766 767
767 768 ;;; User interface functions.
768 769
769 770 (defun hg-help-overview ()
770 771 "This is an overview of the Mercurial SCM mode for Emacs.
771 772
772 773 You can find the source code, license (GPL v2), and credits for this
773 774 code by typing `M-x find-library mercurial RET'."
774 775 (interactive)
775 776 (hg-view-output ("Mercurial Help Overview")
776 777 (insert (documentation 'hg-help-overview))
777 778 (let ((pos (point)))
778 779 (insert (documentation 'hg-mode))
779 780 (goto-char pos)
780 781 (end-of-line 1)
781 782 (delete-region pos (point)))
782 783 (let ((hg-root-dir (hg-root)))
783 784 (if (not hg-root-dir)
784 785 (error "error: %s: directory is not part of a Mercurial repository."
785 786 default-directory)
786 787 (cd hg-root-dir)))))
787 788
788 789 (defun hg-fix-paths ()
789 790 "Fix paths reported by some Mercurial commands."
790 791 (save-excursion
791 792 (goto-char (point-min))
792 793 (while (re-search-forward " \\.\\.." nil t)
793 794 (replace-match " " nil nil))))
794 795
795 796 (defun hg-add (path)
796 797 "Add PATH to the Mercurial repository on the next commit.
797 798 With a prefix argument, prompt for the path to add."
798 799 (interactive (list (hg-read-file-name " to add")))
799 800 (let ((buf (current-buffer))
800 801 (update (equal buffer-file-name path)))
801 802 (hg-view-output (hg-output-buffer-name)
802 803 (apply 'call-process (hg-binary) nil t nil (list "add" path))
803 804 (hg-fix-paths)
804 805 (goto-char (point-min))
805 806 (cd (hg-root path)))
806 807 (when update
807 808 (unless vc-make-backup-files
808 809 (set (make-local-variable 'backup-inhibited) t))
809 810 (with-current-buffer buf
810 811 (hg-mode-line)))))
811 812
812 813 (defun hg-addremove ()
813 814 (interactive)
814 815 (error "not implemented"))
815 816
816 817 (defun hg-annotate ()
817 818 (interactive)
818 819 (error "not implemented"))
819 820
820 821 (defun hg-commit-toggle-file (pos)
821 822 "Toggle whether or not the file at POS will be committed."
822 823 (interactive "d")
823 824 (save-excursion
824 825 (goto-char pos)
825 826 (let ((face (get-text-property pos 'face))
826 827 (inhibit-read-only t)
827 828 bol)
828 829 (beginning-of-line)
829 830 (setq bol (+ (point) 4))
830 831 (end-of-line)
831 832 (if (eq face 'bold)
832 833 (progn
833 834 (remove-text-properties bol (point) '(face nil))
834 835 (message "%s will not be committed"
835 836 (buffer-substring bol (point))))
836 837 (add-text-properties bol (point) '(face bold))
837 838 (message "%s will be committed"
838 839 (buffer-substring bol (point)))))))
839 840
840 841 (defun hg-commit-mouse-clicked (event)
841 842 "Toggle whether or not the file at POS will be committed."
842 843 (interactive "@e")
843 844 (hg-commit-toggle-file (hg-event-point event)))
844 845
845 846 (defun hg-commit-kill ()
846 847 "Kill the commit currently being prepared."
847 848 (interactive)
848 849 (when (or (not (buffer-modified-p)) (y-or-n-p "Really kill this commit? "))
849 850 (let ((buf hg-prev-buffer))
850 851 (kill-buffer nil)
851 852 (switch-to-buffer buf))))
852 853
853 854 (defun hg-commit-finish ()
854 855 "Finish preparing a commit, and perform the actual commit.
855 856 The hook hg-pre-commit-hook is run before anything else is done. If
856 857 the commit message is empty and hg-commit-allow-empty-message is nil,
857 858 an error is raised. If the list of files to commit is empty and
858 859 hg-commit-allow-empty-file-list is nil, an error is raised."
859 860 (interactive)
860 861 (let ((root hg-root))
861 862 (save-excursion
862 863 (run-hooks 'hg-pre-commit-hook)
863 864 (goto-char (point-min))
864 865 (search-forward hg-commit-message-start)
865 866 (let (message files)
866 867 (let ((start (point)))
867 868 (goto-char (point-max))
868 869 (search-backward hg-commit-message-end)
869 870 (setq message (hg-strip (buffer-substring start (point)))))
870 871 (when (and (= (length message) 0)
871 872 (not hg-commit-allow-empty-message))
872 873 (error "Cannot proceed - commit message is empty"))
873 874 (forward-line 1)
874 875 (beginning-of-line)
875 876 (while (< (point) (point-max))
876 877 (let ((pos (+ (point) 4)))
877 878 (end-of-line)
878 879 (when (eq (get-text-property pos 'face) 'bold)
879 880 (end-of-line)
880 881 (setq files (cons (buffer-substring pos (point)) files))))
881 882 (forward-line 1))
882 883 (when (and (= (length files) 0)
883 884 (not hg-commit-allow-empty-file-list))
884 885 (error "Cannot proceed - no files to commit"))
885 886 (setq message (concat message "\n"))
886 887 (apply 'hg-run0 "--cwd" hg-root "commit" "-m" message files))
887 888 (let ((buf hg-prev-buffer))
888 889 (kill-buffer nil)
889 890 (switch-to-buffer buf))
890 891 (hg-update-mode-lines root))))
891 892
892 893 (defun hg-commit-mode ()
893 894 "Mode for describing a commit of changes to a Mercurial repository.
894 895 This involves two actions: describing the changes with a commit
895 896 message, and choosing the files to commit.
896 897
897 898 To describe the commit, simply type some text in the designated area.
898 899
899 900 By default, all modified, added and removed files are selected for
900 901 committing. Files that will be committed are displayed in bold face\;
901 902 those that will not are displayed in normal face.
902 903
903 904 To toggle whether a file will be committed, move the cursor over a
904 905 particular file and hit space or return. Alternatively, middle click
905 906 on the file.
906 907
907 908 Key bindings
908 909 ------------
909 910 \\[hg-commit-finish] proceed with commit
910 911 \\[hg-commit-kill] kill commit
911 912
912 913 \\[hg-diff-repo] view diff of pending changes"
913 914 (interactive)
914 915 (use-local-map hg-commit-mode-map)
915 916 (set-syntax-table text-mode-syntax-table)
916 917 (setq local-abbrev-table text-mode-abbrev-table
917 918 major-mode 'hg-commit-mode
918 919 mode-name "Hg-Commit")
919 920 (set-buffer-modified-p nil)
920 921 (setq buffer-undo-list nil)
921 922 (run-hooks 'text-mode-hook 'hg-commit-mode-hook))
922 923
923 924 (defun hg-commit-start ()
924 925 "Prepare a commit of changes to the repository containing the current file."
925 926 (interactive)
926 927 (while hg-prev-buffer
927 928 (set-buffer hg-prev-buffer))
928 929 (let ((root (hg-root))
929 930 (prev-buffer (current-buffer))
930 931 modified-files)
931 932 (unless root
932 933 (error "Cannot commit outside a repository!"))
933 934 (hg-sync-buffers root)
934 935 (setq modified-files (hg-chomp (hg-run0 "--cwd" root "status" "-arm")))
935 936 (when (and (= (length modified-files) 0)
936 937 (not hg-commit-allow-empty-file-list))
937 938 (error "No pending changes to commit"))
938 939 (let* ((buf-name (format "*Mercurial: Commit %s*" root)))
939 940 (pop-to-buffer (get-buffer-create buf-name))
940 941 (when (= (point-min) (point-max))
941 942 (set (make-local-variable 'hg-root) root)
942 943 (setq hg-prev-buffer prev-buffer)
943 944 (insert "\n")
944 945 (let ((bol (point)))
945 946 (insert hg-commit-message-end)
946 947 (add-text-properties bol (point) '(face bold-italic)))
947 948 (let ((file-area (point)))
948 949 (insert modified-files)
949 950 (goto-char file-area)
950 951 (while (< (point) (point-max))
951 952 (let ((bol (point)))
952 953 (forward-char 1)
953 954 (insert " ")
954 955 (end-of-line)
955 956 (add-text-properties (+ bol 4) (point)
956 957 '(face bold mouse-face highlight)))
957 958 (forward-line 1))
958 959 (goto-char file-area)
959 960 (add-text-properties (point) (point-max)
960 961 `(keymap ,hg-commit-mode-file-map))
961 962 (goto-char (point-min))
962 963 (insert hg-commit-message-start)
963 964 (add-text-properties (point-min) (point) '(face bold-italic))
964 965 (insert "\n\n")
965 966 (forward-line -1)
966 967 (save-excursion
967 968 (goto-char (point-max))
968 969 (search-backward hg-commit-message-end)
969 970 (add-text-properties (match-beginning 0) (point-max)
970 971 '(read-only t))
971 972 (goto-char (point-min))
972 973 (search-forward hg-commit-message-start)
973 974 (add-text-properties (match-beginning 0) (match-end 0)
974 975 '(read-only t)))
975 976 (hg-commit-mode)
976 977 (cd root))))))
977 978
978 979 (defun hg-diff (path &optional rev1 rev2)
979 980 "Show the differences between REV1 and REV2 of PATH.
980 981 When called interactively, the default behaviour is to treat REV1 as
981 982 the \"parent\" revision, REV2 as the current edited version of the file, and
982 983 PATH as the file edited in the current buffer.
983 984 With a prefix argument, prompt for all of these."
984 985 (interactive (list (hg-read-file-name " to diff")
985 986 (let ((rev1 (hg-read-rev " to start with" 'parent)))
986 987 (and (not (eq rev1 'parent)) rev1))
987 988 (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
988 989 (and (not (eq rev2 'working-dir)) rev2))))
989 990 (hg-sync-buffers path)
990 991 (let ((a-path (hg-abbrev-file-name path))
991 992 ;; none revision is specified explicitly
992 993 (none (and (not rev1) (not rev2)))
993 994 ;; only one revision is specified explicitly
994 995 (one (or (and (or (equal rev1 rev2) (not rev2)) rev1)
995 996 (and (not rev1) rev2)))
996 997 diff)
997 998 (hg-view-output ((cond
998 999 (none
999 1000 (format "Mercurial: Diff against parent of %s" a-path))
1000 1001 (one
1001 1002 (format "Mercurial: Diff of rev %s of %s" one a-path))
1002 1003 (t
1003 1004 (format "Mercurial: Diff from rev %s to %s of %s"
1004 1005 rev1 rev2 a-path))))
1005 1006 (cond
1006 1007 (none
1007 1008 (call-process (hg-binary) nil t nil "diff" path))
1008 1009 (one
1009 1010 (call-process (hg-binary) nil t nil "diff" "-r" one path))
1010 1011 (t
1011 1012 (call-process (hg-binary) nil t nil "diff" "-r" rev1 "-r" rev2 path)))
1012 1013 (diff-mode)
1013 1014 (setq diff (not (= (point-min) (point-max))))
1014 1015 (font-lock-fontify-buffer)
1015 1016 (cd (hg-root path)))
1016 1017 diff))
1017 1018
1018 1019 (defun hg-diff-repo (path &optional rev1 rev2)
1019 1020 "Show the differences between REV1 and REV2 of repository containing PATH.
1020 1021 When called interactively, the default behaviour is to treat REV1 as
1021 1022 the \"parent\" revision, REV2 as the current edited version of the file, and
1022 1023 PATH as the `hg-root' of the current buffer.
1023 1024 With a prefix argument, prompt for all of these."
1024 1025 (interactive (list (hg-read-file-name " to diff")
1025 1026 (let ((rev1 (hg-read-rev " to start with" 'parent)))
1026 1027 (and (not (eq rev1 'parent)) rev1))
1027 1028 (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
1028 1029 (and (not (eq rev2 'working-dir)) rev2))))
1029 1030 (hg-diff (hg-root path) rev1 rev2))
1030 1031
1031 1032 (defun hg-forget (path)
1032 1033 "Lose track of PATH, which has been added, but not yet committed.
1033 1034 This will prevent the file from being incorporated into the Mercurial
1034 1035 repository on the next commit.
1035 1036 With a prefix argument, prompt for the path to forget."
1036 1037 (interactive (list (hg-read-file-name " to forget")))
1037 1038 (let ((buf (current-buffer))
1038 1039 (update (equal buffer-file-name path)))
1039 1040 (hg-view-output (hg-output-buffer-name)
1040 1041 (apply 'call-process (hg-binary) nil t nil (list "forget" path))
1041 1042 ;; "hg forget" shows pathes relative NOT TO ROOT BUT TO REPOSITORY
1042 1043 (hg-fix-paths)
1043 1044 (goto-char (point-min))
1044 1045 (cd (hg-root path)))
1045 1046 (when update
1046 1047 (with-current-buffer buf
1047 1048 (when (local-variable-p 'backup-inhibited)
1048 1049 (kill-local-variable 'backup-inhibited))
1049 1050 (hg-mode-line)))))
1050 1051
1051 1052 (defun hg-incoming (&optional repo)
1052 1053 "Display changesets present in REPO that are not present locally."
1053 1054 (interactive (list (hg-read-repo-name " where changes would come from")))
1054 1055 (hg-view-output ((format "Mercurial: Incoming from %s to %s"
1055 1056 (hg-abbrev-file-name (hg-root))
1056 1057 (hg-abbrev-file-name
1057 1058 (or repo hg-incoming-repository))))
1058 1059 (call-process (hg-binary) nil t nil "incoming"
1059 1060 (or repo hg-incoming-repository))
1060 1061 (hg-log-mode)
1061 1062 (cd (hg-root))))
1062 1063
1063 1064 (defun hg-init ()
1064 1065 (interactive)
1065 1066 (error "not implemented"))
1066 1067
1067 1068 (defun hg-log-mode ()
1068 1069 "Mode for viewing a Mercurial change log."
1069 1070 (goto-char (point-min))
1070 1071 (when (looking-at "^searching for changes.*$")
1071 1072 (delete-region (match-beginning 0) (match-end 0)))
1072 1073 (run-hooks 'hg-log-mode-hook))
1073 1074
1074 1075 (defun hg-log (path &optional rev1 rev2 log-limit)
1075 1076 "Display the revision history of PATH.
1076 1077 History is displayed between REV1 and REV2.
1077 1078 Number of displayed changesets is limited to LOG-LIMIT.
1078 1079 REV1 defaults to the tip, while
1079 1080 REV2 defaults to `hg-rev-completion-limit' changes from the tip revision.
1080 1081 LOG-LIMIT defaults to `hg-log-limit'.
1081 1082 With a prefix argument, prompt for each parameter."
1082 1083 (interactive (list (hg-read-file-name " to log")
1083 1084 (hg-read-rev " to start with"
1084 1085 "tip")
1085 1086 (hg-read-rev " to end with"
1086 1087 (format "%d" (- hg-rev-completion-limit)))
1087 1088 (hg-read-number "Output limited to: "
1088 1089 hg-log-limit)))
1089 1090 (let ((a-path (hg-abbrev-file-name path))
1090 1091 (r1 (or rev1 (format "-%d" hg-rev-completion-limit)))
1091 1092 (r2 (or rev2 rev1 "tip"))
1092 1093 (limit (format "%d" (or log-limit hg-log-limit))))
1093 1094 (hg-view-output ((if (equal r1 r2)
1094 1095 (format "Mercurial: Log of rev %s of %s" rev1 a-path)
1095 1096 (format
1096 1097 "Mercurial: at most %s log(s) from rev %s to %s of %s"
1097 1098 limit r1 r2 a-path)))
1098 1099 (eval (list* 'call-process (hg-binary) nil t nil
1099 1100 "log"
1100 1101 "-r" (format "%s:%s" r1 r2)
1101 1102 "-l" limit
1102 1103 (if (> (length path) (length (hg-root path)))
1103 1104 (cons path nil)
1104 1105 nil)))
1105 1106 (hg-log-mode)
1106 1107 (cd (hg-root path)))))
1107 1108
1108 1109 (defun hg-log-repo (path &optional rev1 rev2 log-limit)
1109 1110 "Display the revision history of the repository containing PATH.
1110 1111 History is displayed between REV1 and REV2.
1111 1112 Number of displayed changesets is limited to LOG-LIMIT,
1112 1113 REV1 defaults to the tip, while
1113 1114 REV2 defaults to `hg-rev-completion-limit' changes from the tip revision.
1114 1115 LOG-LIMIT defaults to `hg-log-limit'.
1115 1116 With a prefix argument, prompt for each parameter."
1116 1117 (interactive (list (hg-read-file-name " to log")
1117 1118 (hg-read-rev " to start with"
1118 1119 "tip")
1119 1120 (hg-read-rev " to end with"
1120 1121 (format "%d" (- hg-rev-completion-limit)))
1121 1122 (hg-read-number "Output limited to: "
1122 1123 hg-log-limit)))
1123 1124 (hg-log (hg-root path) rev1 rev2 log-limit))
1124 1125
1125 1126 (defun hg-outgoing (&optional repo)
1126 1127 "Display changesets present locally that are not present in REPO."
1127 1128 (interactive (list (hg-read-repo-name " where changes would go to" nil
1128 1129 hg-outgoing-repository)))
1129 1130 (hg-view-output ((format "Mercurial: Outgoing from %s to %s"
1130 1131 (hg-abbrev-file-name (hg-root))
1131 1132 (hg-abbrev-file-name
1132 1133 (or repo hg-outgoing-repository))))
1133 1134 (call-process (hg-binary) nil t nil "outgoing"
1134 1135 (or repo hg-outgoing-repository))
1135 1136 (hg-log-mode)
1136 1137 (cd (hg-root))))
1137 1138
1138 1139 (defun hg-pull (&optional repo)
1139 1140 "Pull changes from repository REPO.
1140 1141 This does not update the working directory."
1141 1142 (interactive (list (hg-read-repo-name " to pull from")))
1142 1143 (hg-view-output ((format "Mercurial: Pull to %s from %s"
1143 1144 (hg-abbrev-file-name (hg-root))
1144 1145 (hg-abbrev-file-name
1145 1146 (or repo hg-incoming-repository))))
1146 1147 (call-process (hg-binary) nil t nil "pull"
1147 1148 (or repo hg-incoming-repository))
1148 1149 (cd (hg-root))))
1149 1150
1150 1151 (defun hg-push (&optional repo)
1151 1152 "Push changes to repository REPO."
1152 1153 (interactive (list (hg-read-repo-name " to push to")))
1153 1154 (hg-view-output ((format "Mercurial: Push from %s to %s"
1154 1155 (hg-abbrev-file-name (hg-root))
1155 1156 (hg-abbrev-file-name
1156 1157 (or repo hg-outgoing-repository))))
1157 1158 (call-process (hg-binary) nil t nil "push"
1158 1159 (or repo hg-outgoing-repository))
1159 1160 (cd (hg-root))))
1160 1161
1161 1162 (defun hg-revert-buffer-internal ()
1162 1163 (let ((ctx (hg-buffer-context)))
1163 1164 (message "Reverting %s..." buffer-file-name)
1164 1165 (hg-run0 "revert" buffer-file-name)
1165 1166 (revert-buffer t t t)
1166 1167 (hg-restore-context ctx)
1167 1168 (hg-mode-line)
1168 1169 (message "Reverting %s...done" buffer-file-name)))
1169 1170
1170 1171 (defun hg-revert-buffer ()
1171 1172 "Revert current buffer's file back to the latest committed version.
1172 1173 If the file has not changed, nothing happens. Otherwise, this
1173 1174 displays a diff and asks for confirmation before reverting."
1174 1175 (interactive)
1175 1176 (let ((vc-suppress-confirm nil)
1176 1177 (obuf (current-buffer))
1177 1178 diff)
1178 1179 (vc-buffer-sync)
1179 1180 (unwind-protect
1180 1181 (setq diff (hg-diff buffer-file-name))
1181 1182 (when diff
1182 1183 (unless (yes-or-no-p "Discard changes? ")
1183 1184 (error "Revert cancelled")))
1184 1185 (when diff
1185 1186 (let ((buf (current-buffer)))
1186 1187 (delete-window (selected-window))
1187 1188 (kill-buffer buf))))
1188 1189 (set-buffer obuf)
1189 1190 (when diff
1190 1191 (hg-revert-buffer-internal))))
1191 1192
1192 1193 (defun hg-root (&optional path)
1193 1194 "Return the root of the repository that contains the given path.
1194 1195 If the path is outside a repository, return nil.
1195 1196 When called interactively, the root is printed. A prefix argument
1196 1197 prompts for a path to check."
1197 1198 (interactive (list (hg-read-file-name)))
1198 1199 (if (or path (not hg-root))
1199 1200 (let ((root (do ((prev nil dir)
1200 1201 (dir (file-name-directory
1201 1202 (or
1202 1203 path
1203 1204 buffer-file-name
1204 1205 (expand-file-name default-directory)))
1205 1206 (file-name-directory (directory-file-name dir))))
1206 1207 ((equal prev dir))
1207 1208 (when (file-directory-p (concat dir ".hg"))
1208 1209 (return dir)))))
1209 1210 (when (interactive-p)
1210 1211 (if root
1211 1212 (message "The root of this repository is `%s'." root)
1212 1213 (message "The path `%s' is not in a Mercurial repository."
1213 1214 (hg-abbrev-file-name path))))
1214 1215 root)
1215 1216 hg-root))
1216 1217
1217 1218 (defun hg-cwd (&optional path)
1218 1219 "Return the current directory of PATH within the repository."
1219 1220 (do ((stack nil (cons (file-name-nondirectory
1220 1221 (directory-file-name dir))
1221 1222 stack))
1222 1223 (prev nil dir)
1223 1224 (dir (file-name-directory (or path buffer-file-name
1224 1225 (expand-file-name default-directory)))
1225 1226 (file-name-directory (directory-file-name dir))))
1226 1227 ((equal prev dir))
1227 1228 (when (file-directory-p (concat dir ".hg"))
1228 1229 (let ((cwd (mapconcat 'identity stack "/")))
1229 1230 (unless (equal cwd "")
1230 1231 (return (file-name-as-directory cwd)))))))
1231 1232
1232 1233 (defun hg-status (path)
1233 1234 "Print revision control status of a file or directory.
1234 1235 With prefix argument, prompt for the path to give status for.
1235 1236 Names are displayed relative to the repository root."
1236 1237 (interactive (list (hg-read-file-name " for status" (hg-root))))
1237 1238 (let ((root (hg-root)))
1238 1239 (hg-view-output ((format "Mercurial: Status of %s in %s"
1239 1240 (let ((name (substring (expand-file-name path)
1240 1241 (length root))))
1241 1242 (if (> (length name) 0)
1242 1243 name
1243 1244 "*"))
1244 1245 (hg-abbrev-file-name root)))
1245 1246 (apply 'call-process (hg-binary) nil t nil
1246 1247 (list "--cwd" root "status" path))
1247 1248 (cd (hg-root path)))))
1248 1249
1249 1250 (defun hg-undo ()
1250 1251 (interactive)
1251 1252 (error "not implemented"))
1252 1253
1253 1254 (defun hg-update ()
1254 1255 (interactive)
1255 1256 (error "not implemented"))
1256 1257
1257 1258 (defun hg-version-other-window ()
1258 1259 (interactive)
1259 1260 (error "not implemented"))
1260 1261
1261 1262
1262 1263 (provide 'mercurial)
1263 1264
1264 1265
1265 1266 ;;; Local Variables:
1266 1267 ;;; prompt-to-byte-compile: nil
1267 1268 ;;; end:
General Comments 0
You need to be logged in to leave comments. Login now