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