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