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