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