Show More
@@ -0,0 +1,447 b'' | |||||
|
1 | ;;; mercurial.el --- Emacs support for the Mercurial distributed SCM | |||
|
2 | ||||
|
3 | ;; Copyright (C) 2005 Bryan O'Sullivan | |||
|
4 | ||||
|
5 | ;; Author: Bryan O'Sullivan <bos@serpentine.com> | |||
|
6 | ||||
|
7 | ;; $Id$ | |||
|
8 | ||||
|
9 | ;; mercurial.el ("this file") is free software; you can redistribute | |||
|
10 | ;; it and/or modify it under the terms of version 2 of the GNU General | |||
|
11 | ;; Public License as published by the Free Software Foundation. | |||
|
12 | ||||
|
13 | ;; This file is distributed in the hope that it will be useful, but | |||
|
14 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
|
16 | ;; General Public License for more details. | |||
|
17 | ||||
|
18 | ;; You should have received a copy of the GNU General Public License | |||
|
19 | ;; along with this file, GNU Emacs, or XEmacs; see the file COPYING | |||
|
20 | ;; (`C-h C-l'). If not, write to the Free Software Foundation, Inc., | |||
|
21 | ;; 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |||
|
22 | ||||
|
23 | ;;; Commentary: | |||
|
24 | ||||
|
25 | ;; This mode builds upon Emacs's VC mode to provide flexible | |||
|
26 | ;; integration with the Mercurial distributed SCM tool. | |||
|
27 | ||||
|
28 | ;; To get going as quickly as possible, load this file into Emacs and | |||
|
29 | ;; type `C-c h h'; this runs hg-help-overview, which prints a helpful | |||
|
30 | ;; usage overview. | |||
|
31 | ||||
|
32 | ;; Much of the inspiration for mercurial.el comes from Rajesh | |||
|
33 | ;; Vaidheeswarran's excellent p4.el, which does an admirably thorough | |||
|
34 | ;; job for the commercial Perforce SCM product. In fact, substantial | |||
|
35 | ;; chunks of code are adapted from p4.el. | |||
|
36 | ||||
|
37 | ;; This code has been developed under XEmacs 21.5, and may will not | |||
|
38 | ;; work as well under GNU Emacs (albeit tested under 21.2). Patches | |||
|
39 | ;; to enhance the portability of this code, fix bugs, and add features | |||
|
40 | ;; are most welcome. You can clone a Mercurial repository for this | |||
|
41 | ;; package from http://www.serpentine.com/hg/hg-emacs | |||
|
42 | ||||
|
43 | ;; Please send problem reports and suggestions to bos@serpentine.com. | |||
|
44 | ||||
|
45 | ||||
|
46 | ;;; Code: | |||
|
47 | ||||
|
48 | (require 'advice) | |||
|
49 | (require 'cl) | |||
|
50 | (require 'diff-mode) | |||
|
51 | (require 'easymenu) | |||
|
52 | (require 'vc) | |||
|
53 | ||||
|
54 | ||||
|
55 | ;;; XEmacs has view-less, while GNU Emacs has view. Joy. | |||
|
56 | ||||
|
57 | (condition-case nil | |||
|
58 | (require 'view-less) | |||
|
59 | (error nil)) | |||
|
60 | (condition-case nil | |||
|
61 | (require 'view) | |||
|
62 | (error nil)) | |||
|
63 | ||||
|
64 | ||||
|
65 | ;;; Variables accessible through the custom system. | |||
|
66 | ||||
|
67 | (defgroup hg nil | |||
|
68 | "Mercurial distributed SCM." | |||
|
69 | :group 'tools) | |||
|
70 | ||||
|
71 | (defcustom hg-binary | |||
|
72 | (dolist (path '("~/bin/hg" | |||
|
73 | "/usr/bin/hg" | |||
|
74 | "/usr/local/bin/hg")) | |||
|
75 | (when (file-executable-p path) | |||
|
76 | (return path))) | |||
|
77 | "The path to Mercurial's hg executable." | |||
|
78 | :type '(file :must-match t) | |||
|
79 | :group 'hg) | |||
|
80 | ||||
|
81 | (defcustom hg-mode-hook nil | |||
|
82 | "Hook run when a buffer enters hg-mode." | |||
|
83 | :type 'sexp | |||
|
84 | :group 'hg) | |||
|
85 | ||||
|
86 | (defcustom hg-global-prefix "\C-ch" | |||
|
87 | "The global prefix for Mercurial keymap bindings." | |||
|
88 | :type 'sexp | |||
|
89 | :group 'hg) | |||
|
90 | ||||
|
91 | ||||
|
92 | ;;; Other variables. | |||
|
93 | ||||
|
94 | (defconst hg-running-xemacs (string-match "XEmacs" emacs-version) | |||
|
95 | "Is mercurial.el running under XEmacs?") | |||
|
96 | ||||
|
97 | (defvar hg-mode nil | |||
|
98 | "Is this file managed by Mercurial?") | |||
|
99 | ||||
|
100 | (defvar hg-output-buffer-name "*Hg*" | |||
|
101 | "The name to use for Mercurial output buffers.") | |||
|
102 | ||||
|
103 | (defvar hg-file-name-history nil) | |||
|
104 | ||||
|
105 | ||||
|
106 | ;;; hg-mode keymap. | |||
|
107 | ||||
|
108 | (defvar hg-prefix-map | |||
|
109 | (let ((map (copy-keymap vc-prefix-map))) | |||
|
110 | (set-keymap-name map 'hg-prefix-map) | |||
|
111 | map) | |||
|
112 | "This keymap overrides some default vc-mode bindings.") | |||
|
113 | (fset 'hg-prefix-map hg-prefix-map) | |||
|
114 | (define-key hg-prefix-map "=" 'hg-diff-file) | |||
|
115 | (define-key hg-prefix-map "c" 'hg-undo) | |||
|
116 | (define-key hg-prefix-map "g" 'hg-annotate) | |||
|
117 | (define-key hg-prefix-map "l" 'hg-log-file) | |||
|
118 | ;; (define-key hg-prefix-map "r" 'hg-update) | |||
|
119 | (define-key hg-prefix-map "u" 'hg-revert-file) | |||
|
120 | (define-key hg-prefix-map "~" 'hg-version-other-window) | |||
|
121 | ||||
|
122 | (defvar hg-mode-map (make-sparse-keymap)) | |||
|
123 | (define-key hg-mode-map "\C-xv" 'hg-prefix-map) | |||
|
124 | ||||
|
125 | ||||
|
126 | ;;; Global keymap. | |||
|
127 | ||||
|
128 | (global-set-key "\C-xvi" 'hg-add-file) | |||
|
129 | ||||
|
130 | (defvar hg-global-map (make-sparse-keymap)) | |||
|
131 | (fset 'hg-global-map hg-global-map) | |||
|
132 | (global-set-key hg-global-prefix 'hg-global-map) | |||
|
133 | (define-key hg-global-map "," 'hg-incoming) | |||
|
134 | (define-key hg-global-map "." 'hg-outgoing) | |||
|
135 | (define-key hg-global-map "<" 'hg-pull) | |||
|
136 | (define-key hg-global-map "=" 'hg-diff) | |||
|
137 | (define-key hg-global-map ">" 'hg-push) | |||
|
138 | (define-key hg-global-map "?" 'hg-help-overview) | |||
|
139 | (define-key hg-global-map "A" 'hg-addremove) | |||
|
140 | (define-key hg-global-map "U" 'hg-revert) | |||
|
141 | (define-key hg-global-map "a" 'hg-add) | |||
|
142 | (define-key hg-global-map "c" 'hg-commit) | |||
|
143 | (define-key hg-global-map "h" 'hg-help-overview) | |||
|
144 | (define-key hg-global-map "i" 'hg-init) | |||
|
145 | (define-key hg-global-map "l" 'hg-log) | |||
|
146 | (define-key hg-global-map "r" 'hg-root) | |||
|
147 | (define-key hg-global-map "s" 'hg-status) | |||
|
148 | (define-key hg-global-map "u" 'hg-update) | |||
|
149 | ||||
|
150 | ||||
|
151 | ;;; View mode keymap. | |||
|
152 | ||||
|
153 | (defvar hg-view-mode-map | |||
|
154 | (let ((map (copy-keymap (if (boundp 'view-minor-mode-map) | |||
|
155 | view-minor-mode-map | |||
|
156 | view-mode-map)))) | |||
|
157 | (set-keymap-name map 'hg-view-mode-map) | |||
|
158 | map)) | |||
|
159 | (fset 'hg-view-mode-map hg-view-mode-map) | |||
|
160 | (define-key hg-view-mode-map | |||
|
161 | (if hg-running-xemacs [button2] [mouse-2]) | |||
|
162 | 'hg-buffer-mouse-clicked) | |||
|
163 | ||||
|
164 | ||||
|
165 | ;;; Convenience functions. | |||
|
166 | ||||
|
167 | (defun hg-binary () | |||
|
168 | (if hg-binary | |||
|
169 | hg-binary | |||
|
170 | (error "No `hg' executable found!"))) | |||
|
171 | ||||
|
172 | (defun hg-replace-in-string (str regexp newtext &optional literal) | |||
|
173 | "Replace all matches in STR for REGEXP with NEWTEXT string. | |||
|
174 | Return the new string. Optional LITERAL non-nil means do a literal | |||
|
175 | replacement. | |||
|
176 | ||||
|
177 | This function bridges yet another pointless impedance gap between | |||
|
178 | XEmacs and GNU Emacs." | |||
|
179 | (if (fboundp 'replace-in-string) | |||
|
180 | (replace-in-string str regexp newtext literal) | |||
|
181 | (replace-regexp-in-string regexp newtext str nil literal))) | |||
|
182 | ||||
|
183 | (defun hg-chomp (str) | |||
|
184 | "Strip trailing newlines from a string." | |||
|
185 | (hg-replace-in-string str "[\r\n]+$" "")) | |||
|
186 | ||||
|
187 | (defun hg-run-command (command &rest args) | |||
|
188 | "Run the shell command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT). | |||
|
189 | The list ARGS contains a list of arguments to pass to the command." | |||
|
190 | (let* (exit-code | |||
|
191 | (output | |||
|
192 | (with-output-to-string | |||
|
193 | (with-current-buffer | |||
|
194 | standard-output | |||
|
195 | (setq exit-code | |||
|
196 | (apply 'call-process command nil t nil args)))))) | |||
|
197 | (cons exit-code output))) | |||
|
198 | ||||
|
199 | (defun hg-run (command &rest args) | |||
|
200 | "Run the Mercurial command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT)." | |||
|
201 | (apply 'hg-run-command (hg-binary) command args)) | |||
|
202 | ||||
|
203 | (defun hg-run0 (command &rest args) | |||
|
204 | "Run the Mercurial command COMMAND, returning its output. | |||
|
205 | If the command does not exit with a zero status code, raise an error." | |||
|
206 | (let ((res (apply 'hg-run-command (hg-binary) command args))) | |||
|
207 | (if (not (eq (car res) 0)) | |||
|
208 | (error "Mercurial command failed %s - exit code %s" | |||
|
209 | (cons command args) | |||
|
210 | (car res)) | |||
|
211 | (cdr res)))) | |||
|
212 | ||||
|
213 | (defun hg-buffer-commands (pnt) | |||
|
214 | "Use the properties of a character to do something sensible." | |||
|
215 | (interactive "d") | |||
|
216 | (let ((rev (get-char-property pnt 'rev)) | |||
|
217 | (file (get-char-property pnt 'file)) | |||
|
218 | (date (get-char-property pnt 'date)) | |||
|
219 | (user (get-char-property pnt 'user)) | |||
|
220 | (host (get-char-property pnt 'host)) | |||
|
221 | (prev-buf (current-buffer))) | |||
|
222 | (cond | |||
|
223 | (file | |||
|
224 | (find-file-other-window file)) | |||
|
225 | (rev | |||
|
226 | (hg-diff hg-view-file-name rev rev prev-buf)) | |||
|
227 | ((message "I don't know how to do that yet"))))) | |||
|
228 | ||||
|
229 | (defun hg-buffer-mouse-clicked (event) | |||
|
230 | "Translate the mouse clicks in a HG log buffer to character events. | |||
|
231 | These are then handed off to `hg-buffer-commands'. | |||
|
232 | ||||
|
233 | Handle frickin' frackin' gratuitous event-related incompatibilities." | |||
|
234 | (interactive "e") | |||
|
235 | (if hg-running-xemacs | |||
|
236 | (progn | |||
|
237 | (select-window (event-window event)) | |||
|
238 | (hg-buffer-commands (event-point event))) | |||
|
239 | (select-window (posn-window (event-end event))) | |||
|
240 | (hg-buffer-commands (posn-point (event-start event))))) | |||
|
241 | ||||
|
242 | (unless (fboundp 'view-minor-mode) | |||
|
243 | (defun view-minor-mode (prev-buffer exit-func) | |||
|
244 | (view-mode))) | |||
|
245 | ||||
|
246 | (defun hg-abbrev-file-name (file) | |||
|
247 | (if hg-running-xemacs | |||
|
248 | (abbreviate-file-name file t) | |||
|
249 | (abbreviate-file-name file))) | |||
|
250 | ||||
|
251 | ||||
|
252 | ;;; View mode bits. | |||
|
253 | ||||
|
254 | (defun hg-exit-view-mode (buf) | |||
|
255 | "Exit from hg-view-mode. | |||
|
256 | We delete the current window if entering hg-view-mode split the | |||
|
257 | current frame." | |||
|
258 | (when (and (eq buf (current-buffer)) | |||
|
259 | (> (length (window-list)) 1)) | |||
|
260 | (delete-window)) | |||
|
261 | (when (buffer-live-p buf) | |||
|
262 | (kill-buffer buf))) | |||
|
263 | ||||
|
264 | (defun hg-view-mode (prev-buffer &optional file-name) | |||
|
265 | (goto-char (point-min)) | |||
|
266 | (set-buffer-modified-p nil) | |||
|
267 | (toggle-read-only t) | |||
|
268 | (view-minor-mode prev-buffer 'hg-exit-view-mode) | |||
|
269 | (use-local-map hg-view-mode-map) | |||
|
270 | (setq truncate-lines t) | |||
|
271 | (when file-name | |||
|
272 | (set (make-local-variable 'hg-view-file-name) | |||
|
273 | (hg-abbrev-file-name file-name)))) | |||
|
274 | ||||
|
275 | (defmacro hg-view-output (args &rest body) | |||
|
276 | "Execute BODY in a clean buffer, then switch that buffer to view-mode. | |||
|
277 | ARGS is of the form (BUFFER-NAME &optional FILE), where BUFFER-NAME is | |||
|
278 | the name of the buffer to create, and FILE is the name of the file | |||
|
279 | being viewed." | |||
|
280 | (let ((prev-buf (gensym "prev-buf-")) | |||
|
281 | (v-b-name (car args)) | |||
|
282 | (v-m-rest (cdr args))) | |||
|
283 | `(let ((view-buf-name ,v-b-name) | |||
|
284 | (,prev-buf (current-buffer))) | |||
|
285 | (get-buffer-create view-buf-name) | |||
|
286 | (kill-buffer view-buf-name) | |||
|
287 | (pop-to-buffer view-buf-name) | |||
|
288 | (save-excursion | |||
|
289 | ,@body) | |||
|
290 | (hg-view-mode ,prev-buf ,@v-m-rest)))) | |||
|
291 | ||||
|
292 | (put 'hg-view-output 'lisp-indent-function 1) | |||
|
293 | ||||
|
294 | ||||
|
295 | ;;; User interface functions. | |||
|
296 | ||||
|
297 | (defun hg-help-overview () | |||
|
298 | "This is an overview of the Mercurial SCM mode for Emacs. | |||
|
299 | ||||
|
300 | You can find the source code, license (GPL v2), and credits for this | |||
|
301 | code by typing `M-x find-library mercurial RET'. | |||
|
302 | ||||
|
303 | The Mercurial mode user interface is based on that of the older VC | |||
|
304 | mode, so if you're already familiar with VC, the same keybindings and | |||
|
305 | functions will generally work. | |||
|
306 | ||||
|
307 | Below is a list of common SCM tasks, with the key bindings needed to | |||
|
308 | perform them, and the command names. This list is not exhaustive. | |||
|
309 | ||||
|
310 | In the list below, `G/L' indicates whether a key binding is global (G) | |||
|
311 | or local (L). Global keybindings work on any file inside a Mercurial | |||
|
312 | repository. Local keybindings only apply to files under the control | |||
|
313 | of Mercurial. Many commands take a prefix argument. | |||
|
314 | ||||
|
315 | ||||
|
316 | SCM Task G/L Key Binding Command Name | |||
|
317 | -------- --- ----------- ------------ | |||
|
318 | Help overview (what you are reading) G C-c h h hg-help-overview | |||
|
319 | ||||
|
320 | Tell Mercurial to manage a file G C-x v i hg-add-file | |||
|
321 | Commit changes to current file only L C-x C-q vc-toggle-read-only | |||
|
322 | Undo changes to file since commit L C-x v u hg-revert-file | |||
|
323 | ||||
|
324 | Diff file vs last checkin L C-x v = hg-diff-file | |||
|
325 | ||||
|
326 | View file change history L C-x v l hg-log-file | |||
|
327 | View annotated file L C-x v a hg-annotate | |||
|
328 | ||||
|
329 | Diff repo vs last checkin G C-c h = hg-diff | |||
|
330 | View status of files in repo G C-c h s hg-status | |||
|
331 | Commit all changes G C-c h c hg-commit | |||
|
332 | ||||
|
333 | Undo all changes since last commit G C-c h U hg-revert | |||
|
334 | View repo change history G C-c h l hg-log | |||
|
335 | ||||
|
336 | See changes that can be pulled G C-c h , hg-incoming | |||
|
337 | Pull changes G C-c h < hg-pull | |||
|
338 | Update working directory after pull G C-c h u hg-update | |||
|
339 | See changes that can be pushed G C-c h . hg-outgoing | |||
|
340 | Push changes G C-c h > hg-push" | |||
|
341 | (interactive) | |||
|
342 | (hg-view-output ("Mercurial Help Overview") | |||
|
343 | (insert (documentation 'hg-help-overview)))) | |||
|
344 | ||||
|
345 | (defun hg-add () | |||
|
346 | (interactive) | |||
|
347 | (error "not implemented")) | |||
|
348 | ||||
|
349 | (defun hg-add-file () | |||
|
350 | (interactive) | |||
|
351 | (error "not implemented")) | |||
|
352 | ||||
|
353 | (defun hg-addremove () | |||
|
354 | (interactive) | |||
|
355 | (error "not implemented")) | |||
|
356 | ||||
|
357 | (defun hg-annotate () | |||
|
358 | (interactive) | |||
|
359 | (error "not implemented")) | |||
|
360 | ||||
|
361 | (defun hg-commit () | |||
|
362 | (interactive) | |||
|
363 | (error "not implemented")) | |||
|
364 | ||||
|
365 | (defun hg-diff () | |||
|
366 | (interactive) | |||
|
367 | (error "not implemented")) | |||
|
368 | ||||
|
369 | (defun hg-diff-file () | |||
|
370 | (interactive) | |||
|
371 | (error "not implemented")) | |||
|
372 | ||||
|
373 | (defun hg-incoming () | |||
|
374 | (interactive) | |||
|
375 | (error "not implemented")) | |||
|
376 | ||||
|
377 | (defun hg-init () | |||
|
378 | (interactive) | |||
|
379 | (error "not implemented")) | |||
|
380 | ||||
|
381 | (defun hg-log-file () | |||
|
382 | (interactive) | |||
|
383 | (error "not implemented")) | |||
|
384 | ||||
|
385 | (defun hg-log () | |||
|
386 | (interactive) | |||
|
387 | (error "not implemented")) | |||
|
388 | ||||
|
389 | (defun hg-outgoing () | |||
|
390 | (interactive) | |||
|
391 | (error "not implemented")) | |||
|
392 | ||||
|
393 | (defun hg-pull () | |||
|
394 | (interactive) | |||
|
395 | (error "not implemented")) | |||
|
396 | ||||
|
397 | (defun hg-push () | |||
|
398 | (interactive) | |||
|
399 | (error "not implemented")) | |||
|
400 | ||||
|
401 | (defun hg-revert () | |||
|
402 | (interactive) | |||
|
403 | (error "not implemented")) | |||
|
404 | ||||
|
405 | (defun hg-revert-file () | |||
|
406 | (interactive) | |||
|
407 | (error "not implemented")) | |||
|
408 | ||||
|
409 | (defun hg-root (&optional path) | |||
|
410 | (interactive) | |||
|
411 | (unless path | |||
|
412 | (setq path (if (and (interactive-p) current-prefix-arg) | |||
|
413 | (expand-file-name (read-file-name "Path name: ")) | |||
|
414 | (or (buffer-file-name) "(none)")))) | |||
|
415 | (let ((root (do ((prev nil dir) | |||
|
416 | (dir (file-name-directory path) | |||
|
417 | (file-name-directory (directory-file-name dir)))) | |||
|
418 | ((equal prev dir)) | |||
|
419 | (when (file-directory-p (concat dir ".hg")) | |||
|
420 | (return dir))))) | |||
|
421 | (when (interactive-p) | |||
|
422 | (if root | |||
|
423 | (message "The root of this repository is `%s'." root) | |||
|
424 | (message "The path `%s' is not in a Mercurial repository." | |||
|
425 | (abbreviate-file-name path t)))) | |||
|
426 | root)) | |||
|
427 | ||||
|
428 | (defun hg-status () | |||
|
429 | (interactive) | |||
|
430 | (error "not implemented")) | |||
|
431 | ||||
|
432 | (defun hg-undo () | |||
|
433 | (interactive) | |||
|
434 | (error "not implemented")) | |||
|
435 | ||||
|
436 | (defun hg-version-other-window () | |||
|
437 | (interactive) | |||
|
438 | (error "not implemented")) | |||
|
439 | ||||
|
440 | ||||
|
441 | (provide 'mercurial) | |||
|
442 | ||||
|
443 | ||||
|
444 | ;;; Local Variables: | |||
|
445 | ;;; mode: emacs-lisp | |||
|
446 | ;;; prompt-to-byte-compile: nil | |||
|
447 | ;;; end: |
General Comments 0
You need to be logged in to leave comments.
Login now