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