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