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