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