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