##// END OF EJS Templates
Merge spelling fixes
Bryan O'Sullivan -
r17537:31f32a96 merge default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,1293 +1,1293 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, 2006 Bryan O'Sullivan
3 ;; Copyright (C) 2005, 2006 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 the GNU General Public License version
8 ;; modify it under the terms of the GNU General Public License version
9 ;; 2 or any later version.
9 ;; 2 or any later version.
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, see <http://www.gnu.org/licenses/>.
18 ;; (`C-h C-l'). If not, see <http://www.gnu.org/licenses/>.
19
19
20 ;;; Commentary:
20 ;;; Commentary:
21
21
22 ;; mercurial.el builds upon Emacs's VC mode to provide flexible
22 ;; mercurial.el builds upon Emacs's VC mode to provide flexible
23 ;; integration with the Mercurial distributed SCM tool.
23 ;; integration with the Mercurial distributed SCM tool.
24
24
25 ;; To get going as quickly as possible, load mercurial.el into Emacs and
25 ;; To get going as quickly as possible, load mercurial.el into Emacs and
26 ;; type `C-c h h'; this runs hg-help-overview, which prints a helpful
26 ;; type `C-c h h'; this runs hg-help-overview, which prints a helpful
27 ;; usage overview.
27 ;; usage overview.
28
28
29 ;; Much of the inspiration for mercurial.el comes from Rajesh
29 ;; Much of the inspiration for mercurial.el comes from Rajesh
30 ;; Vaidheeswarran's excellent p4.el, which does an admirably thorough
30 ;; Vaidheeswarran's excellent p4.el, which does an admirably thorough
31 ;; job for the commercial Perforce SCM product. In fact, substantial
31 ;; job for the commercial Perforce SCM product. In fact, substantial
32 ;; chunks of code are adapted from p4.el.
32 ;; chunks of code are adapted from p4.el.
33
33
34 ;; This code has been developed under XEmacs 21.5, and may not work as
34 ;; This code has been developed under XEmacs 21.5, and may not work as
35 ;; well under GNU Emacs (albeit tested under 21.4). Patches to
35 ;; well under GNU Emacs (albeit tested under 21.4). Patches to
36 ;; enhance the portability of this code, fix bugs, and add features
36 ;; enhance the portability of this code, fix bugs, and add features
37 ;; are most welcome.
37 ;; are most welcome.
38
38
39 ;; As of version 22.3, GNU Emacs's VC mode has direct support for
39 ;; As of version 22.3, GNU Emacs's VC mode has direct support for
40 ;; Mercurial, so this package may not prove as useful there.
40 ;; Mercurial, so this package may not prove as useful there.
41
41
42 ;; Please send problem reports and suggestions to bos@serpentine.com.
42 ;; Please send problem reports and suggestions to bos@serpentine.com.
43
43
44
44
45 ;;; Code:
45 ;;; Code:
46
46
47 (eval-when-compile (require 'cl))
47 (eval-when-compile (require 'cl))
48 (require 'diff-mode)
48 (require 'diff-mode)
49 (require 'easymenu)
49 (require 'easymenu)
50 (require 'executable)
50 (require 'executable)
51 (require 'vc)
51 (require 'vc)
52
52
53 (defmacro hg-feature-cond (&rest clauses)
53 (defmacro hg-feature-cond (&rest clauses)
54 "Test CLAUSES for feature at compile time.
54 "Test CLAUSES for feature at compile time.
55 Each clause is (FEATURE BODY...)."
55 Each clause is (FEATURE BODY...)."
56 (dolist (x clauses)
56 (dolist (x clauses)
57 (let ((feature (car x))
57 (let ((feature (car x))
58 (body (cdr x)))
58 (body (cdr x)))
59 (when (or (eq feature t)
59 (when (or (eq feature t)
60 (featurep feature))
60 (featurep feature))
61 (return (cons 'progn body))))))
61 (return (cons 'progn body))))))
62
62
63
63
64 ;;; XEmacs has view-less, while GNU Emacs has view. Joy.
64 ;;; XEmacs has view-less, while GNU Emacs has view. Joy.
65
65
66 (hg-feature-cond
66 (hg-feature-cond
67 (xemacs (require 'view-less))
67 (xemacs (require 'view-less))
68 (t (require 'view)))
68 (t (require 'view)))
69
69
70
70
71 ;;; Variables accessible through the custom system.
71 ;;; Variables accessible through the custom system.
72
72
73 (defgroup mercurial nil
73 (defgroup mercurial nil
74 "Mercurial distributed SCM."
74 "Mercurial distributed SCM."
75 :group 'tools)
75 :group 'tools)
76
76
77 (defcustom hg-binary
77 (defcustom hg-binary
78 (or (executable-find "hg")
78 (or (executable-find "hg")
79 (dolist (path '("~/bin/hg" "/usr/bin/hg" "/usr/local/bin/hg"))
79 (dolist (path '("~/bin/hg" "/usr/bin/hg" "/usr/local/bin/hg"))
80 (when (file-executable-p path)
80 (when (file-executable-p path)
81 (return path))))
81 (return path))))
82 "The path to Mercurial's hg executable."
82 "The path to Mercurial's hg executable."
83 :type '(file :must-match t)
83 :type '(file :must-match t)
84 :group 'mercurial)
84 :group 'mercurial)
85
85
86 (defcustom hg-mode-hook nil
86 (defcustom hg-mode-hook nil
87 "Hook run when a buffer enters hg-mode."
87 "Hook run when a buffer enters hg-mode."
88 :type 'sexp
88 :type 'sexp
89 :group 'mercurial)
89 :group 'mercurial)
90
90
91 (defcustom hg-commit-mode-hook nil
91 (defcustom hg-commit-mode-hook nil
92 "Hook run when a buffer is created to prepare a commit."
92 "Hook run when a buffer is created to prepare a commit."
93 :type 'sexp
93 :type 'sexp
94 :group 'mercurial)
94 :group 'mercurial)
95
95
96 (defcustom hg-pre-commit-hook nil
96 (defcustom hg-pre-commit-hook nil
97 "Hook run before a commit is performed.
97 "Hook run before a commit is performed.
98 If you want to prevent the commit from proceeding, raise an error."
98 If you want to prevent the commit from proceeding, raise an error."
99 :type 'sexp
99 :type 'sexp
100 :group 'mercurial)
100 :group 'mercurial)
101
101
102 (defcustom hg-log-mode-hook nil
102 (defcustom hg-log-mode-hook nil
103 "Hook run after a buffer is filled with log information."
103 "Hook run after a buffer is filled with log information."
104 :type 'sexp
104 :type 'sexp
105 :group 'mercurial)
105 :group 'mercurial)
106
106
107 (defcustom hg-global-prefix "\C-ch"
107 (defcustom hg-global-prefix "\C-ch"
108 "The global prefix for Mercurial keymap bindings."
108 "The global prefix for Mercurial keymap bindings."
109 :type 'sexp
109 :type 'sexp
110 :group 'mercurial)
110 :group 'mercurial)
111
111
112 (defcustom hg-commit-allow-empty-message nil
112 (defcustom hg-commit-allow-empty-message nil
113 "Whether to allow changes to be committed with empty descriptions."
113 "Whether to allow changes to be committed with empty descriptions."
114 :type 'boolean
114 :type 'boolean
115 :group 'mercurial)
115 :group 'mercurial)
116
116
117 (defcustom hg-commit-allow-empty-file-list nil
117 (defcustom hg-commit-allow-empty-file-list nil
118 "Whether to allow changes to be committed without any modified files."
118 "Whether to allow changes to be committed without any modified files."
119 :type 'boolean
119 :type 'boolean
120 :group 'mercurial)
120 :group 'mercurial)
121
121
122 (defcustom hg-rev-completion-limit 100
122 (defcustom hg-rev-completion-limit 100
123 "The maximum number of revisions that hg-read-rev will offer to complete.
123 "The maximum number of revisions that hg-read-rev will offer to complete.
124 This affects memory usage and performance when prompting for revisions
124 This affects memory usage and performance when prompting for revisions
125 in a repository with a lot of history."
125 in a repository with a lot of history."
126 :type 'integer
126 :type 'integer
127 :group 'mercurial)
127 :group 'mercurial)
128
128
129 (defcustom hg-log-limit 50
129 (defcustom hg-log-limit 50
130 "The maximum number of revisions that hg-log will display."
130 "The maximum number of revisions that hg-log will display."
131 :type 'integer
131 :type 'integer
132 :group 'mercurial)
132 :group 'mercurial)
133
133
134 (defcustom hg-update-modeline t
134 (defcustom hg-update-modeline t
135 "Whether to update the modeline with the status of a file after every save.
135 "Whether to update the modeline with the status of a file after every save.
136 Set this to nil on platforms with poor process management, such as Windows."
136 Set this to nil on platforms with poor process management, such as Windows."
137 :type 'boolean
137 :type 'boolean
138 :group 'mercurial)
138 :group 'mercurial)
139
139
140 (defcustom hg-incoming-repository "default"
140 (defcustom hg-incoming-repository "default"
141 "The repository from which changes are pulled from by default.
141 "The repository from which changes are pulled from by default.
142 This should be a symbolic repository name, since it is used for all
142 This should be a symbolic repository name, since it is used for all
143 repository-related commands."
143 repository-related commands."
144 :type 'string
144 :type 'string
145 :group 'mercurial)
145 :group 'mercurial)
146
146
147 (defcustom hg-outgoing-repository ""
147 (defcustom hg-outgoing-repository ""
148 "The repository to which changes are pushed to by default.
148 "The repository to which changes are pushed to by default.
149 This should be a symbolic repository name, since it is used for all
149 This should be a symbolic repository name, since it is used for all
150 repository-related commands."
150 repository-related commands."
151 :type 'string
151 :type 'string
152 :group 'mercurial)
152 :group 'mercurial)
153
153
154
154
155 ;;; Other variables.
155 ;;; Other variables.
156
156
157 (defvar hg-mode nil
157 (defvar hg-mode nil
158 "Is this file managed by Mercurial?")
158 "Is this file managed by Mercurial?")
159 (make-variable-buffer-local 'hg-mode)
159 (make-variable-buffer-local 'hg-mode)
160 (put 'hg-mode 'permanent-local t)
160 (put 'hg-mode 'permanent-local t)
161
161
162 (defvar hg-status nil)
162 (defvar hg-status nil)
163 (make-variable-buffer-local 'hg-status)
163 (make-variable-buffer-local 'hg-status)
164 (put 'hg-status 'permanent-local t)
164 (put 'hg-status 'permanent-local t)
165
165
166 (defvar hg-prev-buffer nil)
166 (defvar hg-prev-buffer nil)
167 (make-variable-buffer-local 'hg-prev-buffer)
167 (make-variable-buffer-local 'hg-prev-buffer)
168 (put 'hg-prev-buffer 'permanent-local t)
168 (put 'hg-prev-buffer 'permanent-local t)
169
169
170 (defvar hg-root nil)
170 (defvar hg-root nil)
171 (make-variable-buffer-local 'hg-root)
171 (make-variable-buffer-local 'hg-root)
172 (put 'hg-root 'permanent-local t)
172 (put 'hg-root 'permanent-local t)
173
173
174 (defvar hg-view-mode nil)
174 (defvar hg-view-mode nil)
175 (make-variable-buffer-local 'hg-view-mode)
175 (make-variable-buffer-local 'hg-view-mode)
176 (put 'hg-view-mode 'permanent-local t)
176 (put 'hg-view-mode 'permanent-local t)
177
177
178 (defvar hg-view-file-name nil)
178 (defvar hg-view-file-name nil)
179 (make-variable-buffer-local 'hg-view-file-name)
179 (make-variable-buffer-local 'hg-view-file-name)
180 (put 'hg-view-file-name 'permanent-local t)
180 (put 'hg-view-file-name 'permanent-local t)
181
181
182 (defvar hg-output-buffer-name "*Hg*"
182 (defvar hg-output-buffer-name "*Hg*"
183 "The name to use for Mercurial output buffers.")
183 "The name to use for Mercurial output buffers.")
184
184
185 (defvar hg-file-history nil)
185 (defvar hg-file-history nil)
186 (defvar hg-repo-history nil)
186 (defvar hg-repo-history nil)
187 (defvar hg-rev-history nil)
187 (defvar hg-rev-history nil)
188 (defvar hg-repo-completion-table nil) ; shut up warnings
188 (defvar hg-repo-completion-table nil) ; shut up warnings
189
189
190
190
191 ;;; Random constants.
191 ;;; Random constants.
192
192
193 (defconst hg-commit-message-start
193 (defconst hg-commit-message-start
194 "--- Enter your commit message. Type `C-c C-c' to commit. ---\n")
194 "--- Enter your commit message. Type `C-c C-c' to commit. ---\n")
195
195
196 (defconst hg-commit-message-end
196 (defconst hg-commit-message-end
197 "--- Files in bold will be committed. Click to toggle selection. ---\n")
197 "--- Files in bold will be committed. Click to toggle selection. ---\n")
198
198
199 (defconst hg-state-alist
199 (defconst hg-state-alist
200 '((?M . modified)
200 '((?M . modified)
201 (?A . added)
201 (?A . added)
202 (?R . removed)
202 (?R . removed)
203 (?! . deleted)
203 (?! . deleted)
204 (?C . normal)
204 (?C . normal)
205 (?I . ignored)
205 (?I . ignored)
206 (?? . nil)))
206 (?? . nil)))
207
207
208 ;;; hg-mode keymap.
208 ;;; hg-mode keymap.
209
209
210 (defvar hg-prefix-map
210 (defvar hg-prefix-map
211 (let ((map (make-sparse-keymap)))
211 (let ((map (make-sparse-keymap)))
212 (hg-feature-cond (xemacs (set-keymap-name map 'hg-prefix-map))) ; XEmacs
212 (hg-feature-cond (xemacs (set-keymap-name map 'hg-prefix-map))) ; XEmacs
213 (set-keymap-parent map vc-prefix-map)
213 (set-keymap-parent map vc-prefix-map)
214 (define-key map "=" 'hg-diff)
214 (define-key map "=" 'hg-diff)
215 (define-key map "c" 'hg-undo)
215 (define-key map "c" 'hg-undo)
216 (define-key map "g" 'hg-annotate)
216 (define-key map "g" 'hg-annotate)
217 (define-key map "i" 'hg-add)
217 (define-key map "i" 'hg-add)
218 (define-key map "l" 'hg-log)
218 (define-key map "l" 'hg-log)
219 (define-key map "n" 'hg-commit-start)
219 (define-key map "n" 'hg-commit-start)
220 ;; (define-key map "r" 'hg-update)
220 ;; (define-key map "r" 'hg-update)
221 (define-key map "u" 'hg-revert-buffer)
221 (define-key map "u" 'hg-revert-buffer)
222 (define-key map "~" 'hg-version-other-window)
222 (define-key map "~" 'hg-version-other-window)
223 map)
223 map)
224 "This keymap overrides some default vc-mode bindings.")
224 "This keymap overrides some default vc-mode bindings.")
225
225
226 (defvar hg-mode-map
226 (defvar hg-mode-map
227 (let ((map (make-sparse-keymap)))
227 (let ((map (make-sparse-keymap)))
228 (define-key map "\C-xv" hg-prefix-map)
228 (define-key map "\C-xv" hg-prefix-map)
229 map))
229 map))
230
230
231 (add-minor-mode 'hg-mode 'hg-mode hg-mode-map)
231 (add-minor-mode 'hg-mode 'hg-mode hg-mode-map)
232
232
233
233
234 ;;; Global keymap.
234 ;;; Global keymap.
235
235
236 (defvar hg-global-map
236 (defvar hg-global-map
237 (let ((map (make-sparse-keymap)))
237 (let ((map (make-sparse-keymap)))
238 (define-key map "," 'hg-incoming)
238 (define-key map "," 'hg-incoming)
239 (define-key map "." 'hg-outgoing)
239 (define-key map "." 'hg-outgoing)
240 (define-key map "<" 'hg-pull)
240 (define-key map "<" 'hg-pull)
241 (define-key map "=" 'hg-diff-repo)
241 (define-key map "=" 'hg-diff-repo)
242 (define-key map ">" 'hg-push)
242 (define-key map ">" 'hg-push)
243 (define-key map "?" 'hg-help-overview)
243 (define-key map "?" 'hg-help-overview)
244 (define-key map "A" 'hg-addremove)
244 (define-key map "A" 'hg-addremove)
245 (define-key map "U" 'hg-revert)
245 (define-key map "U" 'hg-revert)
246 (define-key map "a" 'hg-add)
246 (define-key map "a" 'hg-add)
247 (define-key map "c" 'hg-commit-start)
247 (define-key map "c" 'hg-commit-start)
248 (define-key map "f" 'hg-forget)
248 (define-key map "f" 'hg-forget)
249 (define-key map "h" 'hg-help-overview)
249 (define-key map "h" 'hg-help-overview)
250 (define-key map "i" 'hg-init)
250 (define-key map "i" 'hg-init)
251 (define-key map "l" 'hg-log-repo)
251 (define-key map "l" 'hg-log-repo)
252 (define-key map "r" 'hg-root)
252 (define-key map "r" 'hg-root)
253 (define-key map "s" 'hg-status)
253 (define-key map "s" 'hg-status)
254 (define-key map "u" 'hg-update)
254 (define-key map "u" 'hg-update)
255 map))
255 map))
256
256
257 (global-set-key hg-global-prefix hg-global-map)
257 (global-set-key hg-global-prefix hg-global-map)
258
258
259 ;;; View mode keymap.
259 ;;; View mode keymap.
260
260
261 (defvar hg-view-mode-map
261 (defvar hg-view-mode-map
262 (let ((map (make-sparse-keymap)))
262 (let ((map (make-sparse-keymap)))
263 (hg-feature-cond (xemacs (set-keymap-name map 'hg-view-mode-map))) ; XEmacs
263 (hg-feature-cond (xemacs (set-keymap-name map 'hg-view-mode-map))) ; XEmacs
264 (define-key map (hg-feature-cond (xemacs [button2])
264 (define-key map (hg-feature-cond (xemacs [button2])
265 (t [mouse-2]))
265 (t [mouse-2]))
266 'hg-buffer-mouse-clicked)
266 'hg-buffer-mouse-clicked)
267 map))
267 map))
268
268
269 (add-minor-mode 'hg-view-mode "" hg-view-mode-map)
269 (add-minor-mode 'hg-view-mode "" hg-view-mode-map)
270
270
271
271
272 ;;; Commit mode keymaps.
272 ;;; Commit mode keymaps.
273
273
274 (defvar hg-commit-mode-map
274 (defvar hg-commit-mode-map
275 (let ((map (make-sparse-keymap)))
275 (let ((map (make-sparse-keymap)))
276 (define-key map "\C-c\C-c" 'hg-commit-finish)
276 (define-key map "\C-c\C-c" 'hg-commit-finish)
277 (define-key map "\C-c\C-k" 'hg-commit-kill)
277 (define-key map "\C-c\C-k" 'hg-commit-kill)
278 (define-key map "\C-xv=" 'hg-diff-repo)
278 (define-key map "\C-xv=" 'hg-diff-repo)
279 map))
279 map))
280
280
281 (defvar hg-commit-mode-file-map
281 (defvar hg-commit-mode-file-map
282 (let ((map (make-sparse-keymap)))
282 (let ((map (make-sparse-keymap)))
283 (define-key map (hg-feature-cond (xemacs [button2])
283 (define-key map (hg-feature-cond (xemacs [button2])
284 (t [mouse-2]))
284 (t [mouse-2]))
285 'hg-commit-mouse-clicked)
285 'hg-commit-mouse-clicked)
286 (define-key map " " 'hg-commit-toggle-file)
286 (define-key map " " 'hg-commit-toggle-file)
287 (define-key map "\r" 'hg-commit-toggle-file)
287 (define-key map "\r" 'hg-commit-toggle-file)
288 map))
288 map))
289
289
290
290
291 ;;; Convenience functions.
291 ;;; Convenience functions.
292
292
293 (defsubst hg-binary ()
293 (defsubst hg-binary ()
294 (if hg-binary
294 (if hg-binary
295 hg-binary
295 hg-binary
296 (error "No `hg' executable found!")))
296 (error "No `hg' executable found!")))
297
297
298 (defsubst hg-replace-in-string (str regexp newtext &optional literal)
298 (defsubst hg-replace-in-string (str regexp newtext &optional literal)
299 "Replace all matches in STR for REGEXP with NEWTEXT string.
299 "Replace all matches in STR for REGEXP with NEWTEXT string.
300 Return the new string. Optional LITERAL non-nil means do a literal
300 Return the new string. Optional LITERAL non-nil means do a literal
301 replacement.
301 replacement.
302
302
303 This function bridges yet another pointless impedance gap between
303 This function bridges yet another pointless impedance gap between
304 XEmacs and GNU Emacs."
304 XEmacs and GNU Emacs."
305 (hg-feature-cond
305 (hg-feature-cond
306 (xemacs (replace-in-string str regexp newtext literal))
306 (xemacs (replace-in-string str regexp newtext literal))
307 (t (replace-regexp-in-string regexp newtext str nil literal))))
307 (t (replace-regexp-in-string regexp newtext str nil literal))))
308
308
309 (defsubst hg-strip (str)
309 (defsubst hg-strip (str)
310 "Strip leading and trailing blank lines from a string."
310 "Strip leading and trailing blank lines from a string."
311 (hg-replace-in-string (hg-replace-in-string str "[\r\n][ \t\r\n]*\\'" "")
311 (hg-replace-in-string (hg-replace-in-string str "[\r\n][ \t\r\n]*\\'" "")
312 "\\`[ \t\r\n]*[\r\n]" ""))
312 "\\`[ \t\r\n]*[\r\n]" ""))
313
313
314 (defsubst hg-chomp (str)
314 (defsubst hg-chomp (str)
315 "Strip trailing newlines from a string."
315 "Strip trailing newlines from a string."
316 (hg-replace-in-string str "[\r\n]+\\'" ""))
316 (hg-replace-in-string str "[\r\n]+\\'" ""))
317
317
318 (defun hg-run-command (command &rest args)
318 (defun hg-run-command (command &rest args)
319 "Run the shell command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT).
319 "Run the shell command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT).
320 The list ARGS contains a list of arguments to pass to the command."
320 The list ARGS contains a list of arguments to pass to the command."
321 (let* (exit-code
321 (let* (exit-code
322 (output
322 (output
323 (with-output-to-string
323 (with-output-to-string
324 (with-current-buffer
324 (with-current-buffer
325 standard-output
325 standard-output
326 (setq exit-code
326 (setq exit-code
327 (apply 'call-process command nil t nil args))))))
327 (apply 'call-process command nil t nil args))))))
328 (cons exit-code output)))
328 (cons exit-code output)))
329
329
330 (defun hg-run (command &rest args)
330 (defun hg-run (command &rest args)
331 "Run the Mercurial command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT)."
331 "Run the Mercurial command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT)."
332 (apply 'hg-run-command (hg-binary) command args))
332 (apply 'hg-run-command (hg-binary) command args))
333
333
334 (defun hg-run0 (command &rest args)
334 (defun hg-run0 (command &rest args)
335 "Run the Mercurial command COMMAND, returning its output.
335 "Run the Mercurial command COMMAND, returning its output.
336 If the command does not exit with a zero status code, raise an error."
336 If the command does not exit with a zero status code, raise an error."
337 (let ((res (apply 'hg-run-command (hg-binary) command args)))
337 (let ((res (apply 'hg-run-command (hg-binary) command args)))
338 (if (not (eq (car res) 0))
338 (if (not (eq (car res) 0))
339 (error "Mercurial command failed %s - exit code %s"
339 (error "Mercurial command failed %s - exit code %s"
340 (cons command args)
340 (cons command args)
341 (car res))
341 (car res))
342 (cdr res))))
342 (cdr res))))
343
343
344 (defmacro hg-do-across-repo (path &rest body)
344 (defmacro hg-do-across-repo (path &rest body)
345 (let ((root-name (make-symbol "root-"))
345 (let ((root-name (make-symbol "root-"))
346 (buf-name (make-symbol "buf-")))
346 (buf-name (make-symbol "buf-")))
347 `(let ((,root-name (hg-root ,path)))
347 `(let ((,root-name (hg-root ,path)))
348 (save-excursion
348 (save-excursion
349 (dolist (,buf-name (buffer-list))
349 (dolist (,buf-name (buffer-list))
350 (set-buffer ,buf-name)
350 (set-buffer ,buf-name)
351 (when (and hg-status (equal (hg-root buffer-file-name) ,root-name))
351 (when (and hg-status (equal (hg-root buffer-file-name) ,root-name))
352 ,@body))))))
352 ,@body))))))
353
353
354 (put 'hg-do-across-repo 'lisp-indent-function 1)
354 (put 'hg-do-across-repo 'lisp-indent-function 1)
355
355
356 (defun hg-sync-buffers (path)
356 (defun hg-sync-buffers (path)
357 "Sync buffers visiting PATH with their on-disk copies.
357 "Sync buffers visiting PATH with their on-disk copies.
358 If PATH is not being visited, but is under the repository root, sync
358 If PATH is not being visited, but is under the repository root, sync
359 all buffers visiting files in the repository."
359 all buffers visiting files in the repository."
360 (let ((buf (find-buffer-visiting path)))
360 (let ((buf (find-buffer-visiting path)))
361 (if buf
361 (if buf
362 (with-current-buffer buf
362 (with-current-buffer buf
363 (vc-buffer-sync))
363 (vc-buffer-sync))
364 (hg-do-across-repo path
364 (hg-do-across-repo path
365 (vc-buffer-sync)))))
365 (vc-buffer-sync)))))
366
366
367 (defun hg-buffer-commands (pnt)
367 (defun hg-buffer-commands (pnt)
368 "Use the properties of a character to do something sensible."
368 "Use the properties of a character to do something sensible."
369 (interactive "d")
369 (interactive "d")
370 (let ((rev (get-char-property pnt 'rev))
370 (let ((rev (get-char-property pnt 'rev))
371 (file (get-char-property pnt 'file)))
371 (file (get-char-property pnt 'file)))
372 (cond
372 (cond
373 (file
373 (file
374 (find-file-other-window file))
374 (find-file-other-window file))
375 (rev
375 (rev
376 (hg-diff hg-view-file-name rev rev))
376 (hg-diff hg-view-file-name rev rev))
377 ((message "I don't know how to do that yet")))))
377 ((message "I don't know how to do that yet")))))
378
378
379 (defsubst hg-event-point (event)
379 (defsubst hg-event-point (event)
380 "Return the character position of the mouse event EVENT."
380 "Return the character position of the mouse event EVENT."
381 (hg-feature-cond (xemacs (event-point event))
381 (hg-feature-cond (xemacs (event-point event))
382 (t (posn-point (event-start event)))))
382 (t (posn-point (event-start event)))))
383
383
384 (defsubst hg-event-window (event)
384 (defsubst hg-event-window (event)
385 "Return the window over which mouse event EVENT occurred."
385 "Return the window over which mouse event EVENT occurred."
386 (hg-feature-cond (xemacs (event-window event))
386 (hg-feature-cond (xemacs (event-window event))
387 (t (posn-window (event-start event)))))
387 (t (posn-window (event-start event)))))
388
388
389 (defun hg-buffer-mouse-clicked (event)
389 (defun hg-buffer-mouse-clicked (event)
390 "Translate the mouse clicks in a HG log buffer to character events.
390 "Translate the mouse clicks in a HG log buffer to character events.
391 These are then handed off to `hg-buffer-commands'.
391 These are then handed off to `hg-buffer-commands'.
392
392
393 Handle frickin' frackin' gratuitous event-related incompatibilities."
393 Handle frickin' frackin' gratuitous event-related incompatibilities."
394 (interactive "e")
394 (interactive "e")
395 (select-window (hg-event-window event))
395 (select-window (hg-event-window event))
396 (hg-buffer-commands (hg-event-point event)))
396 (hg-buffer-commands (hg-event-point event)))
397
397
398 (defsubst hg-abbrev-file-name (file)
398 (defsubst hg-abbrev-file-name (file)
399 "Portable wrapper around abbreviate-file-name."
399 "Portable wrapper around abbreviate-file-name."
400 (hg-feature-cond (xemacs (abbreviate-file-name file t))
400 (hg-feature-cond (xemacs (abbreviate-file-name file t))
401 (t (abbreviate-file-name file))))
401 (t (abbreviate-file-name file))))
402
402
403 (defun hg-read-file-name (&optional prompt default)
403 (defun hg-read-file-name (&optional prompt default)
404 "Read a file or directory name, or a pattern, to use with a command."
404 "Read a file or directory name, or a pattern, to use with a command."
405 (save-excursion
405 (save-excursion
406 (while hg-prev-buffer
406 (while hg-prev-buffer
407 (set-buffer hg-prev-buffer))
407 (set-buffer hg-prev-buffer))
408 (let ((path (or default
408 (let ((path (or default
409 (buffer-file-name)
409 (buffer-file-name)
410 (expand-file-name default-directory))))
410 (expand-file-name default-directory))))
411 (if (or (not path) current-prefix-arg)
411 (if (or (not path) current-prefix-arg)
412 (expand-file-name
412 (expand-file-name
413 (eval (list* 'read-file-name
413 (eval (list* 'read-file-name
414 (format "File, directory or pattern%s: "
414 (format "File, directory or pattern%s: "
415 (or prompt ""))
415 (or prompt ""))
416 (and path (file-name-directory path))
416 (and path (file-name-directory path))
417 nil nil
417 nil nil
418 (and path (file-name-nondirectory path))
418 (and path (file-name-nondirectory path))
419 (hg-feature-cond
419 (hg-feature-cond
420 (xemacs (cons (quote 'hg-file-history) nil))
420 (xemacs (cons (quote 'hg-file-history) nil))
421 (t nil)))))
421 (t nil)))))
422 path))))
422 path))))
423
423
424 (defun hg-read-number (&optional prompt default)
424 (defun hg-read-number (&optional prompt default)
425 "Read a integer value."
425 "Read a integer value."
426 (save-excursion
426 (save-excursion
427 (if (or (not default) current-prefix-arg)
427 (if (or (not default) current-prefix-arg)
428 (string-to-number
428 (string-to-number
429 (eval (list* 'read-string
429 (eval (list* 'read-string
430 (or prompt "")
430 (or prompt "")
431 (if default (cons (format "%d" default) nil) nil))))
431 (if default (cons (format "%d" default) nil) nil))))
432 default)))
432 default)))
433
433
434 (defun hg-read-config ()
434 (defun hg-read-config ()
435 "Return an alist of (key . value) pairs of Mercurial config data.
435 "Return an alist of (key . value) pairs of Mercurial config data.
436 Each key is of the form (section . name)."
436 Each key is of the form (section . name)."
437 (let (items)
437 (let (items)
438 (dolist (line (split-string (hg-chomp (hg-run0 "debugconfig")) "\n") items)
438 (dolist (line (split-string (hg-chomp (hg-run0 "debugconfig")) "\n") items)
439 (string-match "^\\([^=]*\\)=\\(.*\\)" line)
439 (string-match "^\\([^=]*\\)=\\(.*\\)" line)
440 (let* ((left (substring line (match-beginning 1) (match-end 1)))
440 (let* ((left (substring line (match-beginning 1) (match-end 1)))
441 (right (substring line (match-beginning 2) (match-end 2)))
441 (right (substring line (match-beginning 2) (match-end 2)))
442 (key (split-string left "\\."))
442 (key (split-string left "\\."))
443 (value (hg-replace-in-string right "\\\\n" "\n" t)))
443 (value (hg-replace-in-string right "\\\\n" "\n" t)))
444 (setq items (cons (cons (cons (car key) (cadr key)) value) items))))))
444 (setq items (cons (cons (cons (car key) (cadr key)) value) items))))))
445
445
446 (defun hg-config-section (section config)
446 (defun hg-config-section (section config)
447 "Return an alist of (name . value) pairs for SECTION of CONFIG."
447 "Return an alist of (name . value) pairs for SECTION of CONFIG."
448 (let (items)
448 (let (items)
449 (dolist (item config items)
449 (dolist (item config items)
450 (when (equal (caar item) section)
450 (when (equal (caar item) section)
451 (setq items (cons (cons (cdar item) (cdr item)) items))))))
451 (setq items (cons (cons (cdar item) (cdr item)) items))))))
452
452
453 (defun hg-string-starts-with (sub str)
453 (defun hg-string-starts-with (sub str)
454 "Indicate whether string STR starts with the substring or character SUB."
454 "Indicate whether string STR starts with the substring or character SUB."
455 (if (not (stringp sub))
455 (if (not (stringp sub))
456 (and (> (length str) 0) (equal (elt str 0) sub))
456 (and (> (length str) 0) (equal (elt str 0) sub))
457 (let ((sub-len (length sub)))
457 (let ((sub-len (length sub)))
458 (and (<= sub-len (length str))
458 (and (<= sub-len (length str))
459 (string= sub (substring str 0 sub-len))))))
459 (string= sub (substring str 0 sub-len))))))
460
460
461 (defun hg-complete-repo (string predicate all)
461 (defun hg-complete-repo (string predicate all)
462 "Attempt to complete a repository name.
462 "Attempt to complete a repository name.
463 We complete on either symbolic names from Mercurial's config or real
463 We complete on either symbolic names from Mercurial's config or real
464 directory names from the file system. We do not penalise URLs."
464 directory names from the file system. We do not penalize URLs."
465 (or (if all
465 (or (if all
466 (all-completions string hg-repo-completion-table predicate)
466 (all-completions string hg-repo-completion-table predicate)
467 (try-completion string hg-repo-completion-table predicate))
467 (try-completion string hg-repo-completion-table predicate))
468 (let* ((str (expand-file-name string))
468 (let* ((str (expand-file-name string))
469 (dir (file-name-directory str))
469 (dir (file-name-directory str))
470 (file (file-name-nondirectory str)))
470 (file (file-name-nondirectory str)))
471 (if all
471 (if all
472 (let (completions)
472 (let (completions)
473 (dolist (name (delete "./" (file-name-all-completions file dir))
473 (dolist (name (delete "./" (file-name-all-completions file dir))
474 completions)
474 completions)
475 (let ((path (concat dir name)))
475 (let ((path (concat dir name)))
476 (when (file-directory-p path)
476 (when (file-directory-p path)
477 (setq completions (cons name completions))))))
477 (setq completions (cons name completions))))))
478 (let ((comp (file-name-completion file dir)))
478 (let ((comp (file-name-completion file dir)))
479 (if comp
479 (if comp
480 (hg-abbrev-file-name (concat dir comp))))))))
480 (hg-abbrev-file-name (concat dir comp))))))))
481
481
482 (defun hg-read-repo-name (&optional prompt initial-contents default)
482 (defun hg-read-repo-name (&optional prompt initial-contents default)
483 "Read the location of a repository."
483 "Read the location of a repository."
484 (save-excursion
484 (save-excursion
485 (while hg-prev-buffer
485 (while hg-prev-buffer
486 (set-buffer hg-prev-buffer))
486 (set-buffer hg-prev-buffer))
487 (let (hg-repo-completion-table)
487 (let (hg-repo-completion-table)
488 (if current-prefix-arg
488 (if current-prefix-arg
489 (progn
489 (progn
490 (dolist (path (hg-config-section "paths" (hg-read-config)))
490 (dolist (path (hg-config-section "paths" (hg-read-config)))
491 (setq hg-repo-completion-table
491 (setq hg-repo-completion-table
492 (cons (cons (car path) t) hg-repo-completion-table))
492 (cons (cons (car path) t) hg-repo-completion-table))
493 (unless (hg-string-starts-with (hg-feature-cond
493 (unless (hg-string-starts-with (hg-feature-cond
494 (xemacs directory-sep-char)
494 (xemacs directory-sep-char)
495 (t ?/))
495 (t ?/))
496 (cdr path))
496 (cdr path))
497 (setq hg-repo-completion-table
497 (setq hg-repo-completion-table
498 (cons (cons (cdr path) t) hg-repo-completion-table))))
498 (cons (cons (cdr path) t) hg-repo-completion-table))))
499 (completing-read (format "Repository%s: " (or prompt ""))
499 (completing-read (format "Repository%s: " (or prompt ""))
500 'hg-complete-repo
500 'hg-complete-repo
501 nil
501 nil
502 nil
502 nil
503 initial-contents
503 initial-contents
504 'hg-repo-history
504 'hg-repo-history
505 default))
505 default))
506 default))))
506 default))))
507
507
508 (defun hg-read-rev (&optional prompt default)
508 (defun hg-read-rev (&optional prompt default)
509 "Read a revision or tag, offering completions."
509 "Read a revision or tag, offering completions."
510 (save-excursion
510 (save-excursion
511 (while hg-prev-buffer
511 (while hg-prev-buffer
512 (set-buffer hg-prev-buffer))
512 (set-buffer hg-prev-buffer))
513 (let ((rev (or default "tip")))
513 (let ((rev (or default "tip")))
514 (if current-prefix-arg
514 (if current-prefix-arg
515 (let ((revs (split-string
515 (let ((revs (split-string
516 (hg-chomp
516 (hg-chomp
517 (hg-run0 "-q" "log" "-l"
517 (hg-run0 "-q" "log" "-l"
518 (format "%d" hg-rev-completion-limit)))
518 (format "%d" hg-rev-completion-limit)))
519 "[\n:]")))
519 "[\n:]")))
520 (dolist (line (split-string (hg-chomp (hg-run0 "tags")) "\n"))
520 (dolist (line (split-string (hg-chomp (hg-run0 "tags")) "\n"))
521 (setq revs (cons (car (split-string line "\\s-")) revs)))
521 (setq revs (cons (car (split-string line "\\s-")) revs)))
522 (completing-read (format "Revision%s (%s): "
522 (completing-read (format "Revision%s (%s): "
523 (or prompt "")
523 (or prompt "")
524 (or default "tip"))
524 (or default "tip"))
525 (mapcar (lambda (x) (cons x x)) revs)
525 (mapcar (lambda (x) (cons x x)) revs)
526 nil
526 nil
527 nil
527 nil
528 nil
528 nil
529 'hg-rev-history
529 'hg-rev-history
530 (or default "tip")))
530 (or default "tip")))
531 rev))))
531 rev))))
532
532
533 (defun hg-parents-for-mode-line (root)
533 (defun hg-parents-for-mode-line (root)
534 "Format the parents of the working directory for the mode line."
534 "Format the parents of the working directory for the mode line."
535 (let ((parents (split-string (hg-chomp
535 (let ((parents (split-string (hg-chomp
536 (hg-run0 "--cwd" root "parents" "--template"
536 (hg-run0 "--cwd" root "parents" "--template"
537 "{rev}\n")) "\n")))
537 "{rev}\n")) "\n")))
538 (mapconcat 'identity parents "+")))
538 (mapconcat 'identity parents "+")))
539
539
540 (defun hg-buffers-visiting-repo (&optional path)
540 (defun hg-buffers-visiting-repo (&optional path)
541 "Return a list of buffers visiting the repository containing PATH."
541 "Return a list of buffers visiting the repository containing PATH."
542 (let ((root-name (hg-root (or path (buffer-file-name))))
542 (let ((root-name (hg-root (or path (buffer-file-name))))
543 bufs)
543 bufs)
544 (save-excursion
544 (save-excursion
545 (dolist (buf (buffer-list) bufs)
545 (dolist (buf (buffer-list) bufs)
546 (set-buffer buf)
546 (set-buffer buf)
547 (let ((name (buffer-file-name)))
547 (let ((name (buffer-file-name)))
548 (when (and hg-status name (equal (hg-root name) root-name))
548 (when (and hg-status name (equal (hg-root name) root-name))
549 (setq bufs (cons buf bufs))))))))
549 (setq bufs (cons buf bufs))))))))
550
550
551 (defun hg-update-mode-lines (path)
551 (defun hg-update-mode-lines (path)
552 "Update the mode lines of all buffers visiting the same repository as PATH."
552 "Update the mode lines of all buffers visiting the same repository as PATH."
553 (let* ((root (hg-root path))
553 (let* ((root (hg-root path))
554 (parents (hg-parents-for-mode-line root)))
554 (parents (hg-parents-for-mode-line root)))
555 (save-excursion
555 (save-excursion
556 (dolist (info (hg-path-status
556 (dolist (info (hg-path-status
557 root
557 root
558 (mapcar
558 (mapcar
559 (function
559 (function
560 (lambda (buf)
560 (lambda (buf)
561 (substring (buffer-file-name buf) (length root))))
561 (substring (buffer-file-name buf) (length root))))
562 (hg-buffers-visiting-repo root))))
562 (hg-buffers-visiting-repo root))))
563 (let* ((name (car info))
563 (let* ((name (car info))
564 (status (cdr info))
564 (status (cdr info))
565 (buf (find-buffer-visiting (concat root name))))
565 (buf (find-buffer-visiting (concat root name))))
566 (when buf
566 (when buf
567 (set-buffer buf)
567 (set-buffer buf)
568 (hg-mode-line-internal status parents)))))))
568 (hg-mode-line-internal status parents)))))))
569
569
570
570
571 ;;; View mode bits.
571 ;;; View mode bits.
572
572
573 (defun hg-exit-view-mode (buf)
573 (defun hg-exit-view-mode (buf)
574 "Exit from hg-view-mode.
574 "Exit from hg-view-mode.
575 We delete the current window if entering hg-view-mode split the
575 We delete the current window if entering hg-view-mode split the
576 current frame."
576 current frame."
577 (when (and (eq buf (current-buffer))
577 (when (and (eq buf (current-buffer))
578 (> (length (window-list)) 1))
578 (> (length (window-list)) 1))
579 (delete-window))
579 (delete-window))
580 (when (buffer-live-p buf)
580 (when (buffer-live-p buf)
581 (kill-buffer buf)))
581 (kill-buffer buf)))
582
582
583 (defun hg-view-mode (prev-buffer &optional file-name)
583 (defun hg-view-mode (prev-buffer &optional file-name)
584 (goto-char (point-min))
584 (goto-char (point-min))
585 (set-buffer-modified-p nil)
585 (set-buffer-modified-p nil)
586 (toggle-read-only t)
586 (toggle-read-only t)
587 (hg-feature-cond (xemacs (view-minor-mode prev-buffer 'hg-exit-view-mode))
587 (hg-feature-cond (xemacs (view-minor-mode prev-buffer 'hg-exit-view-mode))
588 (t (view-mode-enter nil 'hg-exit-view-mode)))
588 (t (view-mode-enter nil 'hg-exit-view-mode)))
589 (setq hg-view-mode t)
589 (setq hg-view-mode t)
590 (setq truncate-lines t)
590 (setq truncate-lines t)
591 (when file-name
591 (when file-name
592 (setq hg-view-file-name
592 (setq hg-view-file-name
593 (hg-abbrev-file-name file-name))))
593 (hg-abbrev-file-name file-name))))
594
594
595 (defun hg-file-status (file)
595 (defun hg-file-status (file)
596 "Return status of FILE, or nil if FILE does not exist or is unmanaged."
596 "Return status of FILE, or nil if FILE does not exist or is unmanaged."
597 (let* ((s (hg-run "status" file))
597 (let* ((s (hg-run "status" file))
598 (exit (car s))
598 (exit (car s))
599 (output (cdr s)))
599 (output (cdr s)))
600 (if (= exit 0)
600 (if (= exit 0)
601 (let ((state (and (>= (length output) 2)
601 (let ((state (and (>= (length output) 2)
602 (= (aref output 1) ? )
602 (= (aref output 1) ? )
603 (assq (aref output 0) hg-state-alist))))
603 (assq (aref output 0) hg-state-alist))))
604 (if state
604 (if state
605 (cdr state)
605 (cdr state)
606 'normal)))))
606 'normal)))))
607
607
608 (defun hg-path-status (root paths)
608 (defun hg-path-status (root paths)
609 "Return status of PATHS in repo ROOT as an alist.
609 "Return status of PATHS in repo ROOT as an alist.
610 Each entry is a pair (FILE-NAME . STATUS)."
610 Each entry is a pair (FILE-NAME . STATUS)."
611 (let ((s (apply 'hg-run "--cwd" root "status" "-marduc" paths))
611 (let ((s (apply 'hg-run "--cwd" root "status" "-marduc" paths))
612 result)
612 result)
613 (dolist (entry (split-string (hg-chomp (cdr s)) "\n") (nreverse result))
613 (dolist (entry (split-string (hg-chomp (cdr s)) "\n") (nreverse result))
614 (let (state name)
614 (let (state name)
615 (cond ((= (aref entry 1) ? )
615 (cond ((= (aref entry 1) ? )
616 (setq state (assq (aref entry 0) hg-state-alist)
616 (setq state (assq (aref entry 0) hg-state-alist)
617 name (substring entry 2)))
617 name (substring entry 2)))
618 ((string-match "\\(.*\\): " entry)
618 ((string-match "\\(.*\\): " entry)
619 (setq name (match-string 1 entry))))
619 (setq name (match-string 1 entry))))
620 (setq result (cons (cons name state) result))))))
620 (setq result (cons (cons name state) result))))))
621
621
622 (defmacro hg-view-output (args &rest body)
622 (defmacro hg-view-output (args &rest body)
623 "Execute BODY in a clean buffer, then quickly display that buffer.
623 "Execute BODY in a clean buffer, then quickly display that buffer.
624 If the buffer contains one line, its contents are displayed in the
624 If the buffer contains one line, its contents are displayed in the
625 minibuffer. Otherwise, the buffer is displayed in view-mode.
625 minibuffer. Otherwise, the buffer is displayed in view-mode.
626 ARGS is of the form (BUFFER-NAME &optional FILE), where BUFFER-NAME is
626 ARGS is of the form (BUFFER-NAME &optional FILE), where BUFFER-NAME is
627 the name of the buffer to create, and FILE is the name of the file
627 the name of the buffer to create, and FILE is the name of the file
628 being viewed."
628 being viewed."
629 (let ((prev-buf (make-symbol "prev-buf-"))
629 (let ((prev-buf (make-symbol "prev-buf-"))
630 (v-b-name (car args))
630 (v-b-name (car args))
631 (v-m-rest (cdr args)))
631 (v-m-rest (cdr args)))
632 `(let ((view-buf-name ,v-b-name)
632 `(let ((view-buf-name ,v-b-name)
633 (,prev-buf (current-buffer)))
633 (,prev-buf (current-buffer)))
634 (get-buffer-create view-buf-name)
634 (get-buffer-create view-buf-name)
635 (kill-buffer view-buf-name)
635 (kill-buffer view-buf-name)
636 (get-buffer-create view-buf-name)
636 (get-buffer-create view-buf-name)
637 (set-buffer view-buf-name)
637 (set-buffer view-buf-name)
638 (save-excursion
638 (save-excursion
639 ,@body)
639 ,@body)
640 (case (count-lines (point-min) (point-max))
640 (case (count-lines (point-min) (point-max))
641 ((0)
641 ((0)
642 (kill-buffer view-buf-name)
642 (kill-buffer view-buf-name)
643 (message "(No output)"))
643 (message "(No output)"))
644 ((1)
644 ((1)
645 (let ((msg (hg-chomp (buffer-substring (point-min) (point-max)))))
645 (let ((msg (hg-chomp (buffer-substring (point-min) (point-max)))))
646 (kill-buffer view-buf-name)
646 (kill-buffer view-buf-name)
647 (message "%s" msg)))
647 (message "%s" msg)))
648 (t
648 (t
649 (pop-to-buffer view-buf-name)
649 (pop-to-buffer view-buf-name)
650 (setq hg-prev-buffer ,prev-buf)
650 (setq hg-prev-buffer ,prev-buf)
651 (hg-view-mode ,prev-buf ,@v-m-rest))))))
651 (hg-view-mode ,prev-buf ,@v-m-rest))))))
652
652
653 (put 'hg-view-output 'lisp-indent-function 1)
653 (put 'hg-view-output 'lisp-indent-function 1)
654
654
655 ;;; Context save and restore across revert and other operations.
655 ;;; Context save and restore across revert and other operations.
656
656
657 (defun hg-position-context (pos)
657 (defun hg-position-context (pos)
658 "Return information to help find the given position again."
658 "Return information to help find the given position again."
659 (let* ((end (min (point-max) (+ pos 98))))
659 (let* ((end (min (point-max) (+ pos 98))))
660 (list pos
660 (list pos
661 (buffer-substring (max (point-min) (- pos 2)) end)
661 (buffer-substring (max (point-min) (- pos 2)) end)
662 (- end pos))))
662 (- end pos))))
663
663
664 (defun hg-buffer-context ()
664 (defun hg-buffer-context ()
665 "Return information to help restore a user's editing context.
665 "Return information to help restore a user's editing context.
666 This is useful across reverts and merges, where a context is likely
666 This is useful across reverts and merges, where a context is likely
667 to have moved a little, but not really changed."
667 to have moved a little, but not really changed."
668 (let ((point-context (hg-position-context (point)))
668 (let ((point-context (hg-position-context (point)))
669 (mark-context (let ((mark (mark-marker)))
669 (mark-context (let ((mark (mark-marker)))
670 (and mark
670 (and mark
671 ;; make sure active mark
671 ;; make sure active mark
672 (marker-buffer mark)
672 (marker-buffer mark)
673 (marker-position mark)
673 (marker-position mark)
674 (hg-position-context mark)))))
674 (hg-position-context mark)))))
675 (list point-context mark-context)))
675 (list point-context mark-context)))
676
676
677 (defun hg-find-context (ctx)
677 (defun hg-find-context (ctx)
678 "Attempt to find a context in the given buffer.
678 "Attempt to find a context in the given buffer.
679 Always returns a valid, hopefully sane, position."
679 Always returns a valid, hopefully sane, position."
680 (let ((pos (nth 0 ctx))
680 (let ((pos (nth 0 ctx))
681 (str (nth 1 ctx))
681 (str (nth 1 ctx))
682 (fixup (nth 2 ctx)))
682 (fixup (nth 2 ctx)))
683 (save-excursion
683 (save-excursion
684 (goto-char (max (point-min) (- pos 15000)))
684 (goto-char (max (point-min) (- pos 15000)))
685 (if (and (not (equal str ""))
685 (if (and (not (equal str ""))
686 (search-forward str nil t))
686 (search-forward str nil t))
687 (- (point) fixup)
687 (- (point) fixup)
688 (max pos (point-min))))))
688 (max pos (point-min))))))
689
689
690 (defun hg-restore-context (ctx)
690 (defun hg-restore-context (ctx)
691 "Attempt to restore the user's editing context."
691 "Attempt to restore the user's editing context."
692 (let ((point-context (nth 0 ctx))
692 (let ((point-context (nth 0 ctx))
693 (mark-context (nth 1 ctx)))
693 (mark-context (nth 1 ctx)))
694 (goto-char (hg-find-context point-context))
694 (goto-char (hg-find-context point-context))
695 (when mark-context
695 (when mark-context
696 (set-mark (hg-find-context mark-context)))))
696 (set-mark (hg-find-context mark-context)))))
697
697
698
698
699 ;;; Hooks.
699 ;;; Hooks.
700
700
701 (defun hg-mode-line-internal (status parents)
701 (defun hg-mode-line-internal (status parents)
702 (setq hg-status status
702 (setq hg-status status
703 hg-mode (and status (concat " Hg:"
703 hg-mode (and status (concat " Hg:"
704 parents
704 parents
705 (cdr (assq status
705 (cdr (assq status
706 '((normal . "")
706 '((normal . "")
707 (removed . "r")
707 (removed . "r")
708 (added . "a")
708 (added . "a")
709 (deleted . "!")
709 (deleted . "!")
710 (modified . "m"))))))))
710 (modified . "m"))))))))
711
711
712 (defun hg-mode-line (&optional force)
712 (defun hg-mode-line (&optional force)
713 "Update the modeline with the current status of a file.
713 "Update the modeline with the current status of a file.
714 An update occurs if optional argument FORCE is non-nil,
714 An update occurs if optional argument FORCE is non-nil,
715 hg-update-modeline is non-nil, or we have not yet checked the state of
715 hg-update-modeline is non-nil, or we have not yet checked the state of
716 the file."
716 the file."
717 (let ((root (hg-root)))
717 (let ((root (hg-root)))
718 (when (and root (or force hg-update-modeline (not hg-mode)))
718 (when (and root (or force hg-update-modeline (not hg-mode)))
719 (let ((status (hg-file-status buffer-file-name))
719 (let ((status (hg-file-status buffer-file-name))
720 (parents (hg-parents-for-mode-line root)))
720 (parents (hg-parents-for-mode-line root)))
721 (hg-mode-line-internal status parents)
721 (hg-mode-line-internal status parents)
722 status))))
722 status))))
723
723
724 (defun hg-mode (&optional toggle)
724 (defun hg-mode (&optional toggle)
725 "Minor mode for Mercurial distributed SCM integration.
725 "Minor mode for Mercurial distributed SCM integration.
726
726
727 The Mercurial mode user interface is based on that of VC mode, so if
727 The Mercurial mode user interface is based on that of VC mode, so if
728 you're already familiar with VC, the same keybindings and functions
728 you're already familiar with VC, the same keybindings and functions
729 will generally work.
729 will generally work.
730
730
731 Below is a list of many common SCM tasks. In the list, `G/L\'
731 Below is a list of many common SCM tasks. In the list, `G/L\'
732 indicates whether a key binding is global (G) to a repository or
732 indicates whether a key binding is global (G) to a repository or
733 local (L) to a file. Many commands take a prefix argument.
733 local (L) to a file. Many commands take a prefix argument.
734
734
735 SCM Task G/L Key Binding Command Name
735 SCM Task G/L Key Binding Command Name
736 -------- --- ----------- ------------
736 -------- --- ----------- ------------
737 Help overview (what you are reading) G C-c h h hg-help-overview
737 Help overview (what you are reading) G C-c h h hg-help-overview
738
738
739 Tell Mercurial to manage a file G C-c h a hg-add
739 Tell Mercurial to manage a file G C-c h a hg-add
740 Commit changes to current file only L C-x v n hg-commit-start
740 Commit changes to current file only L C-x v n hg-commit-start
741 Undo changes to file since commit L C-x v u hg-revert-buffer
741 Undo changes to file since commit L C-x v u hg-revert-buffer
742
742
743 Diff file vs last checkin L C-x v = hg-diff
743 Diff file vs last checkin L C-x v = hg-diff
744
744
745 View file change history L C-x v l hg-log
745 View file change history L C-x v l hg-log
746 View annotated file L C-x v a hg-annotate
746 View annotated file L C-x v a hg-annotate
747
747
748 Diff repo vs last checkin G C-c h = hg-diff-repo
748 Diff repo vs last checkin G C-c h = hg-diff-repo
749 View status of files in repo G C-c h s hg-status
749 View status of files in repo G C-c h s hg-status
750 Commit all changes G C-c h c hg-commit-start
750 Commit all changes G C-c h c hg-commit-start
751
751
752 Undo all changes since last commit G C-c h U hg-revert
752 Undo all changes since last commit G C-c h U hg-revert
753 View repo change history G C-c h l hg-log-repo
753 View repo change history G C-c h l hg-log-repo
754
754
755 See changes that can be pulled G C-c h , hg-incoming
755 See changes that can be pulled G C-c h , hg-incoming
756 Pull changes G C-c h < hg-pull
756 Pull changes G C-c h < hg-pull
757 Update working directory after pull G C-c h u hg-update
757 Update working directory after pull G C-c h u hg-update
758 See changes that can be pushed G C-c h . hg-outgoing
758 See changes that can be pushed G C-c h . hg-outgoing
759 Push changes G C-c h > hg-push"
759 Push changes G C-c h > hg-push"
760 (unless vc-make-backup-files
760 (unless vc-make-backup-files
761 (set (make-local-variable 'backup-inhibited) t))
761 (set (make-local-variable 'backup-inhibited) t))
762 (run-hooks 'hg-mode-hook))
762 (run-hooks 'hg-mode-hook))
763
763
764 (defun hg-find-file-hook ()
764 (defun hg-find-file-hook ()
765 (ignore-errors
765 (ignore-errors
766 (when (hg-mode-line)
766 (when (hg-mode-line)
767 (hg-mode))))
767 (hg-mode))))
768
768
769 (add-hook 'find-file-hooks 'hg-find-file-hook)
769 (add-hook 'find-file-hooks 'hg-find-file-hook)
770
770
771 (defun hg-after-save-hook ()
771 (defun hg-after-save-hook ()
772 (ignore-errors
772 (ignore-errors
773 (let ((old-status hg-status))
773 (let ((old-status hg-status))
774 (hg-mode-line)
774 (hg-mode-line)
775 (if (and (not old-status) hg-status)
775 (if (and (not old-status) hg-status)
776 (hg-mode)))))
776 (hg-mode)))))
777
777
778 (add-hook 'after-save-hook 'hg-after-save-hook)
778 (add-hook 'after-save-hook 'hg-after-save-hook)
779
779
780
780
781 ;;; User interface functions.
781 ;;; User interface functions.
782
782
783 (defun hg-help-overview ()
783 (defun hg-help-overview ()
784 "This is an overview of the Mercurial SCM mode for Emacs.
784 "This is an overview of the Mercurial SCM mode for Emacs.
785
785
786 You can find the source code, license (GPLv2+), and credits for this
786 You can find the source code, license (GPLv2+), and credits for this
787 code by typing `M-x find-library mercurial RET'."
787 code by typing `M-x find-library mercurial RET'."
788 (interactive)
788 (interactive)
789 (hg-view-output ("Mercurial Help Overview")
789 (hg-view-output ("Mercurial Help Overview")
790 (insert (documentation 'hg-help-overview))
790 (insert (documentation 'hg-help-overview))
791 (let ((pos (point)))
791 (let ((pos (point)))
792 (insert (documentation 'hg-mode))
792 (insert (documentation 'hg-mode))
793 (goto-char pos)
793 (goto-char pos)
794 (end-of-line 1)
794 (end-of-line 1)
795 (delete-region pos (point)))
795 (delete-region pos (point)))
796 (let ((hg-root-dir (hg-root)))
796 (let ((hg-root-dir (hg-root)))
797 (if (not hg-root-dir)
797 (if (not hg-root-dir)
798 (error "error: %s: directory is not part of a Mercurial repository."
798 (error "error: %s: directory is not part of a Mercurial repository."
799 default-directory)
799 default-directory)
800 (cd hg-root-dir)))))
800 (cd hg-root-dir)))))
801
801
802 (defun hg-fix-paths ()
802 (defun hg-fix-paths ()
803 "Fix paths reported by some Mercurial commands."
803 "Fix paths reported by some Mercurial commands."
804 (save-excursion
804 (save-excursion
805 (goto-char (point-min))
805 (goto-char (point-min))
806 (while (re-search-forward " \\.\\.." nil t)
806 (while (re-search-forward " \\.\\.." nil t)
807 (replace-match " " nil nil))))
807 (replace-match " " nil nil))))
808
808
809 (defun hg-add (path)
809 (defun hg-add (path)
810 "Add PATH to the Mercurial repository on the next commit.
810 "Add PATH to the Mercurial repository on the next commit.
811 With a prefix argument, prompt for the path to add."
811 With a prefix argument, prompt for the path to add."
812 (interactive (list (hg-read-file-name " to add")))
812 (interactive (list (hg-read-file-name " to add")))
813 (let ((buf (current-buffer))
813 (let ((buf (current-buffer))
814 (update (equal buffer-file-name path)))
814 (update (equal buffer-file-name path)))
815 (hg-view-output (hg-output-buffer-name)
815 (hg-view-output (hg-output-buffer-name)
816 (apply 'call-process (hg-binary) nil t nil (list "add" path))
816 (apply 'call-process (hg-binary) nil t nil (list "add" path))
817 (hg-fix-paths)
817 (hg-fix-paths)
818 (goto-char (point-min))
818 (goto-char (point-min))
819 (cd (hg-root path)))
819 (cd (hg-root path)))
820 (when update
820 (when update
821 (unless vc-make-backup-files
821 (unless vc-make-backup-files
822 (set (make-local-variable 'backup-inhibited) t))
822 (set (make-local-variable 'backup-inhibited) t))
823 (with-current-buffer buf
823 (with-current-buffer buf
824 (hg-mode-line)))))
824 (hg-mode-line)))))
825
825
826 (defun hg-addremove ()
826 (defun hg-addremove ()
827 (interactive)
827 (interactive)
828 (error "not implemented"))
828 (error "not implemented"))
829
829
830 (defun hg-annotate ()
830 (defun hg-annotate ()
831 (interactive)
831 (interactive)
832 (error "not implemented"))
832 (error "not implemented"))
833
833
834 (defun hg-commit-toggle-file (pos)
834 (defun hg-commit-toggle-file (pos)
835 "Toggle whether or not the file at POS will be committed."
835 "Toggle whether or not the file at POS will be committed."
836 (interactive "d")
836 (interactive "d")
837 (save-excursion
837 (save-excursion
838 (goto-char pos)
838 (goto-char pos)
839 (let (face
839 (let (face
840 (inhibit-read-only t)
840 (inhibit-read-only t)
841 bol)
841 bol)
842 (beginning-of-line)
842 (beginning-of-line)
843 (setq bol (+ (point) 4))
843 (setq bol (+ (point) 4))
844 (setq face (get-text-property bol 'face))
844 (setq face (get-text-property bol 'face))
845 (end-of-line)
845 (end-of-line)
846 (if (eq face 'bold)
846 (if (eq face 'bold)
847 (progn
847 (progn
848 (remove-text-properties bol (point) '(face nil))
848 (remove-text-properties bol (point) '(face nil))
849 (message "%s will not be committed"
849 (message "%s will not be committed"
850 (buffer-substring bol (point))))
850 (buffer-substring bol (point))))
851 (add-text-properties bol (point) '(face bold))
851 (add-text-properties bol (point) '(face bold))
852 (message "%s will be committed"
852 (message "%s will be committed"
853 (buffer-substring bol (point)))))))
853 (buffer-substring bol (point)))))))
854
854
855 (defun hg-commit-mouse-clicked (event)
855 (defun hg-commit-mouse-clicked (event)
856 "Toggle whether or not the file at POS will be committed."
856 "Toggle whether or not the file at POS will be committed."
857 (interactive "@e")
857 (interactive "@e")
858 (hg-commit-toggle-file (hg-event-point event)))
858 (hg-commit-toggle-file (hg-event-point event)))
859
859
860 (defun hg-commit-kill ()
860 (defun hg-commit-kill ()
861 "Kill the commit currently being prepared."
861 "Kill the commit currently being prepared."
862 (interactive)
862 (interactive)
863 (when (or (not (buffer-modified-p)) (y-or-n-p "Really kill this commit? "))
863 (when (or (not (buffer-modified-p)) (y-or-n-p "Really kill this commit? "))
864 (let ((buf hg-prev-buffer))
864 (let ((buf hg-prev-buffer))
865 (kill-buffer nil)
865 (kill-buffer nil)
866 (switch-to-buffer buf))))
866 (switch-to-buffer buf))))
867
867
868 (defun hg-commit-finish ()
868 (defun hg-commit-finish ()
869 "Finish preparing a commit, and perform the actual commit.
869 "Finish preparing a commit, and perform the actual commit.
870 The hook hg-pre-commit-hook is run before anything else is done. If
870 The hook hg-pre-commit-hook is run before anything else is done. If
871 the commit message is empty and hg-commit-allow-empty-message is nil,
871 the commit message is empty and hg-commit-allow-empty-message is nil,
872 an error is raised. If the list of files to commit is empty and
872 an error is raised. If the list of files to commit is empty and
873 hg-commit-allow-empty-file-list is nil, an error is raised."
873 hg-commit-allow-empty-file-list is nil, an error is raised."
874 (interactive)
874 (interactive)
875 (let ((root hg-root))
875 (let ((root hg-root))
876 (save-excursion
876 (save-excursion
877 (run-hooks 'hg-pre-commit-hook)
877 (run-hooks 'hg-pre-commit-hook)
878 (goto-char (point-min))
878 (goto-char (point-min))
879 (search-forward hg-commit-message-start)
879 (search-forward hg-commit-message-start)
880 (let (message files)
880 (let (message files)
881 (let ((start (point)))
881 (let ((start (point)))
882 (goto-char (point-max))
882 (goto-char (point-max))
883 (search-backward hg-commit-message-end)
883 (search-backward hg-commit-message-end)
884 (setq message (hg-strip (buffer-substring start (point)))))
884 (setq message (hg-strip (buffer-substring start (point)))))
885 (when (and (= (length message) 0)
885 (when (and (= (length message) 0)
886 (not hg-commit-allow-empty-message))
886 (not hg-commit-allow-empty-message))
887 (error "Cannot proceed - commit message is empty"))
887 (error "Cannot proceed - commit message is empty"))
888 (forward-line 1)
888 (forward-line 1)
889 (beginning-of-line)
889 (beginning-of-line)
890 (while (< (point) (point-max))
890 (while (< (point) (point-max))
891 (let ((pos (+ (point) 4)))
891 (let ((pos (+ (point) 4)))
892 (end-of-line)
892 (end-of-line)
893 (when (eq (get-text-property pos 'face) 'bold)
893 (when (eq (get-text-property pos 'face) 'bold)
894 (end-of-line)
894 (end-of-line)
895 (setq files (cons (buffer-substring pos (point)) files))))
895 (setq files (cons (buffer-substring pos (point)) files))))
896 (forward-line 1))
896 (forward-line 1))
897 (when (and (= (length files) 0)
897 (when (and (= (length files) 0)
898 (not hg-commit-allow-empty-file-list))
898 (not hg-commit-allow-empty-file-list))
899 (error "Cannot proceed - no files to commit"))
899 (error "Cannot proceed - no files to commit"))
900 (setq message (concat message "\n"))
900 (setq message (concat message "\n"))
901 (apply 'hg-run0 "--cwd" hg-root "commit" "-m" message files))
901 (apply 'hg-run0 "--cwd" hg-root "commit" "-m" message files))
902 (let ((buf hg-prev-buffer))
902 (let ((buf hg-prev-buffer))
903 (kill-buffer nil)
903 (kill-buffer nil)
904 (switch-to-buffer buf))
904 (switch-to-buffer buf))
905 (hg-update-mode-lines root))))
905 (hg-update-mode-lines root))))
906
906
907 (defun hg-commit-mode ()
907 (defun hg-commit-mode ()
908 "Mode for describing a commit of changes to a Mercurial repository.
908 "Mode for describing a commit of changes to a Mercurial repository.
909 This involves two actions: describing the changes with a commit
909 This involves two actions: describing the changes with a commit
910 message, and choosing the files to commit.
910 message, and choosing the files to commit.
911
911
912 To describe the commit, simply type some text in the designated area.
912 To describe the commit, simply type some text in the designated area.
913
913
914 By default, all modified, added and removed files are selected for
914 By default, all modified, added and removed files are selected for
915 committing. Files that will be committed are displayed in bold face\;
915 committing. Files that will be committed are displayed in bold face\;
916 those that will not are displayed in normal face.
916 those that will not are displayed in normal face.
917
917
918 To toggle whether a file will be committed, move the cursor over a
918 To toggle whether a file will be committed, move the cursor over a
919 particular file and hit space or return. Alternatively, middle click
919 particular file and hit space or return. Alternatively, middle click
920 on the file.
920 on the file.
921
921
922 Key bindings
922 Key bindings
923 ------------
923 ------------
924 \\[hg-commit-finish] proceed with commit
924 \\[hg-commit-finish] proceed with commit
925 \\[hg-commit-kill] kill commit
925 \\[hg-commit-kill] kill commit
926
926
927 \\[hg-diff-repo] view diff of pending changes"
927 \\[hg-diff-repo] view diff of pending changes"
928 (interactive)
928 (interactive)
929 (use-local-map hg-commit-mode-map)
929 (use-local-map hg-commit-mode-map)
930 (set-syntax-table text-mode-syntax-table)
930 (set-syntax-table text-mode-syntax-table)
931 (setq local-abbrev-table text-mode-abbrev-table
931 (setq local-abbrev-table text-mode-abbrev-table
932 major-mode 'hg-commit-mode
932 major-mode 'hg-commit-mode
933 mode-name "Hg-Commit")
933 mode-name "Hg-Commit")
934 (set-buffer-modified-p nil)
934 (set-buffer-modified-p nil)
935 (setq buffer-undo-list nil)
935 (setq buffer-undo-list nil)
936 (run-hooks 'text-mode-hook 'hg-commit-mode-hook))
936 (run-hooks 'text-mode-hook 'hg-commit-mode-hook))
937
937
938 (defun hg-commit-start ()
938 (defun hg-commit-start ()
939 "Prepare a commit of changes to the repository containing the current file."
939 "Prepare a commit of changes to the repository containing the current file."
940 (interactive)
940 (interactive)
941 (while hg-prev-buffer
941 (while hg-prev-buffer
942 (set-buffer hg-prev-buffer))
942 (set-buffer hg-prev-buffer))
943 (let ((root (hg-root))
943 (let ((root (hg-root))
944 (prev-buffer (current-buffer))
944 (prev-buffer (current-buffer))
945 modified-files)
945 modified-files)
946 (unless root
946 (unless root
947 (error "Cannot commit outside a repository!"))
947 (error "Cannot commit outside a repository!"))
948 (hg-sync-buffers root)
948 (hg-sync-buffers root)
949 (setq modified-files (hg-chomp (hg-run0 "--cwd" root "status" "-arm")))
949 (setq modified-files (hg-chomp (hg-run0 "--cwd" root "status" "-arm")))
950 (when (and (= (length modified-files) 0)
950 (when (and (= (length modified-files) 0)
951 (not hg-commit-allow-empty-file-list))
951 (not hg-commit-allow-empty-file-list))
952 (error "No pending changes to commit"))
952 (error "No pending changes to commit"))
953 (let* ((buf-name (format "*Mercurial: Commit %s*" root)))
953 (let* ((buf-name (format "*Mercurial: Commit %s*" root)))
954 (pop-to-buffer (get-buffer-create buf-name))
954 (pop-to-buffer (get-buffer-create buf-name))
955 (when (= (point-min) (point-max))
955 (when (= (point-min) (point-max))
956 (set (make-local-variable 'hg-root) root)
956 (set (make-local-variable 'hg-root) root)
957 (setq hg-prev-buffer prev-buffer)
957 (setq hg-prev-buffer prev-buffer)
958 (insert "\n")
958 (insert "\n")
959 (let ((bol (point)))
959 (let ((bol (point)))
960 (insert hg-commit-message-end)
960 (insert hg-commit-message-end)
961 (add-text-properties bol (point) '(face bold-italic)))
961 (add-text-properties bol (point) '(face bold-italic)))
962 (let ((file-area (point)))
962 (let ((file-area (point)))
963 (insert modified-files)
963 (insert modified-files)
964 (goto-char file-area)
964 (goto-char file-area)
965 (while (< (point) (point-max))
965 (while (< (point) (point-max))
966 (let ((bol (point)))
966 (let ((bol (point)))
967 (forward-char 1)
967 (forward-char 1)
968 (insert " ")
968 (insert " ")
969 (end-of-line)
969 (end-of-line)
970 (add-text-properties (+ bol 4) (point)
970 (add-text-properties (+ bol 4) (point)
971 '(face bold mouse-face highlight)))
971 '(face bold mouse-face highlight)))
972 (forward-line 1))
972 (forward-line 1))
973 (goto-char file-area)
973 (goto-char file-area)
974 (add-text-properties (point) (point-max)
974 (add-text-properties (point) (point-max)
975 `(keymap ,hg-commit-mode-file-map))
975 `(keymap ,hg-commit-mode-file-map))
976 (goto-char (point-min))
976 (goto-char (point-min))
977 (insert hg-commit-message-start)
977 (insert hg-commit-message-start)
978 (add-text-properties (point-min) (point) '(face bold-italic))
978 (add-text-properties (point-min) (point) '(face bold-italic))
979 (insert "\n\n")
979 (insert "\n\n")
980 (forward-line -1)
980 (forward-line -1)
981 (save-excursion
981 (save-excursion
982 (goto-char (point-max))
982 (goto-char (point-max))
983 (search-backward hg-commit-message-end)
983 (search-backward hg-commit-message-end)
984 (add-text-properties (match-beginning 0) (point-max)
984 (add-text-properties (match-beginning 0) (point-max)
985 '(read-only t))
985 '(read-only t))
986 (goto-char (point-min))
986 (goto-char (point-min))
987 (search-forward hg-commit-message-start)
987 (search-forward hg-commit-message-start)
988 (add-text-properties (match-beginning 0) (match-end 0)
988 (add-text-properties (match-beginning 0) (match-end 0)
989 '(read-only t)))
989 '(read-only t)))
990 (hg-commit-mode)
990 (hg-commit-mode)
991 (cd root))))))
991 (cd root))))))
992
992
993 (defun hg-diff (path &optional rev1 rev2)
993 (defun hg-diff (path &optional rev1 rev2)
994 "Show the differences between REV1 and REV2 of PATH.
994 "Show the differences between REV1 and REV2 of PATH.
995 When called interactively, the default behaviour is to treat REV1 as
995 When called interactively, the default behaviour is to treat REV1 as
996 the \"parent\" revision, REV2 as the current edited version of the file, and
996 the \"parent\" revision, REV2 as the current edited version of the file, and
997 PATH as the file edited in the current buffer.
997 PATH as the file edited in the current buffer.
998 With a prefix argument, prompt for all of these."
998 With a prefix argument, prompt for all of these."
999 (interactive (list (hg-read-file-name " to diff")
999 (interactive (list (hg-read-file-name " to diff")
1000 (let ((rev1 (hg-read-rev " to start with" 'parent)))
1000 (let ((rev1 (hg-read-rev " to start with" 'parent)))
1001 (and (not (eq rev1 'parent)) rev1))
1001 (and (not (eq rev1 'parent)) rev1))
1002 (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
1002 (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
1003 (and (not (eq rev2 'working-dir)) rev2))))
1003 (and (not (eq rev2 'working-dir)) rev2))))
1004 (hg-sync-buffers path)
1004 (hg-sync-buffers path)
1005 (let ((a-path (hg-abbrev-file-name path))
1005 (let ((a-path (hg-abbrev-file-name path))
1006 ;; none revision is specified explicitly
1006 ;; none revision is specified explicitly
1007 (none (and (not rev1) (not rev2)))
1007 (none (and (not rev1) (not rev2)))
1008 ;; only one revision is specified explicitly
1008 ;; only one revision is specified explicitly
1009 (one (or (and (or (equal rev1 rev2) (not rev2)) rev1)
1009 (one (or (and (or (equal rev1 rev2) (not rev2)) rev1)
1010 (and (not rev1) rev2)))
1010 (and (not rev1) rev2)))
1011 diff)
1011 diff)
1012 (hg-view-output ((cond
1012 (hg-view-output ((cond
1013 (none
1013 (none
1014 (format "Mercurial: Diff against parent of %s" a-path))
1014 (format "Mercurial: Diff against parent of %s" a-path))
1015 (one
1015 (one
1016 (format "Mercurial: Diff of rev %s of %s" one a-path))
1016 (format "Mercurial: Diff of rev %s of %s" one a-path))
1017 (t
1017 (t
1018 (format "Mercurial: Diff from rev %s to %s of %s"
1018 (format "Mercurial: Diff from rev %s to %s of %s"
1019 rev1 rev2 a-path))))
1019 rev1 rev2 a-path))))
1020 (cond
1020 (cond
1021 (none
1021 (none
1022 (call-process (hg-binary) nil t nil "diff" path))
1022 (call-process (hg-binary) nil t nil "diff" path))
1023 (one
1023 (one
1024 (call-process (hg-binary) nil t nil "diff" "-r" one path))
1024 (call-process (hg-binary) nil t nil "diff" "-r" one path))
1025 (t
1025 (t
1026 (call-process (hg-binary) nil t nil "diff" "-r" rev1 "-r" rev2 path)))
1026 (call-process (hg-binary) nil t nil "diff" "-r" rev1 "-r" rev2 path)))
1027 (diff-mode)
1027 (diff-mode)
1028 (setq diff (not (= (point-min) (point-max))))
1028 (setq diff (not (= (point-min) (point-max))))
1029 (font-lock-fontify-buffer)
1029 (font-lock-fontify-buffer)
1030 (cd (hg-root path)))
1030 (cd (hg-root path)))
1031 diff))
1031 diff))
1032
1032
1033 (defun hg-diff-repo (path &optional rev1 rev2)
1033 (defun hg-diff-repo (path &optional rev1 rev2)
1034 "Show the differences between REV1 and REV2 of repository containing PATH.
1034 "Show the differences between REV1 and REV2 of repository containing PATH.
1035 When called interactively, the default behaviour is to treat REV1 as
1035 When called interactively, the default behaviour is to treat REV1 as
1036 the \"parent\" revision, REV2 as the current edited version of the file, and
1036 the \"parent\" revision, REV2 as the current edited version of the file, and
1037 PATH as the `hg-root' of the current buffer.
1037 PATH as the `hg-root' of the current buffer.
1038 With a prefix argument, prompt for all of these."
1038 With a prefix argument, prompt for all of these."
1039 (interactive (list (hg-read-file-name " to diff")
1039 (interactive (list (hg-read-file-name " to diff")
1040 (let ((rev1 (hg-read-rev " to start with" 'parent)))
1040 (let ((rev1 (hg-read-rev " to start with" 'parent)))
1041 (and (not (eq rev1 'parent)) rev1))
1041 (and (not (eq rev1 'parent)) rev1))
1042 (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
1042 (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
1043 (and (not (eq rev2 'working-dir)) rev2))))
1043 (and (not (eq rev2 'working-dir)) rev2))))
1044 (hg-diff (hg-root path) rev1 rev2))
1044 (hg-diff (hg-root path) rev1 rev2))
1045
1045
1046 (defun hg-forget (path)
1046 (defun hg-forget (path)
1047 "Lose track of PATH, which has been added, but not yet committed.
1047 "Lose track of PATH, which has been added, but not yet committed.
1048 This will prevent the file from being incorporated into the Mercurial
1048 This will prevent the file from being incorporated into the Mercurial
1049 repository on the next commit.
1049 repository on the next commit.
1050 With a prefix argument, prompt for the path to forget."
1050 With a prefix argument, prompt for the path to forget."
1051 (interactive (list (hg-read-file-name " to forget")))
1051 (interactive (list (hg-read-file-name " to forget")))
1052 (let ((buf (current-buffer))
1052 (let ((buf (current-buffer))
1053 (update (equal buffer-file-name path)))
1053 (update (equal buffer-file-name path)))
1054 (hg-view-output (hg-output-buffer-name)
1054 (hg-view-output (hg-output-buffer-name)
1055 (apply 'call-process (hg-binary) nil t nil (list "forget" path))
1055 (apply 'call-process (hg-binary) nil t nil (list "forget" path))
1056 ;; "hg forget" shows pathes relative NOT TO ROOT BUT TO REPOSITORY
1056 ;; "hg forget" shows pathes relative NOT TO ROOT BUT TO REPOSITORY
1057 (hg-fix-paths)
1057 (hg-fix-paths)
1058 (goto-char (point-min))
1058 (goto-char (point-min))
1059 (cd (hg-root path)))
1059 (cd (hg-root path)))
1060 (when update
1060 (when update
1061 (with-current-buffer buf
1061 (with-current-buffer buf
1062 (when (local-variable-p 'backup-inhibited)
1062 (when (local-variable-p 'backup-inhibited)
1063 (kill-local-variable 'backup-inhibited))
1063 (kill-local-variable 'backup-inhibited))
1064 (hg-mode-line)))))
1064 (hg-mode-line)))))
1065
1065
1066 (defun hg-incoming (&optional repo)
1066 (defun hg-incoming (&optional repo)
1067 "Display changesets present in REPO that are not present locally."
1067 "Display changesets present in REPO that are not present locally."
1068 (interactive (list (hg-read-repo-name " where changes would come from")))
1068 (interactive (list (hg-read-repo-name " where changes would come from")))
1069 (hg-view-output ((format "Mercurial: Incoming from %s to %s"
1069 (hg-view-output ((format "Mercurial: Incoming from %s to %s"
1070 (hg-abbrev-file-name (hg-root))
1070 (hg-abbrev-file-name (hg-root))
1071 (hg-abbrev-file-name
1071 (hg-abbrev-file-name
1072 (or repo hg-incoming-repository))))
1072 (or repo hg-incoming-repository))))
1073 (call-process (hg-binary) nil t nil "incoming"
1073 (call-process (hg-binary) nil t nil "incoming"
1074 (or repo hg-incoming-repository))
1074 (or repo hg-incoming-repository))
1075 (hg-log-mode)
1075 (hg-log-mode)
1076 (cd (hg-root))))
1076 (cd (hg-root))))
1077
1077
1078 (defun hg-init ()
1078 (defun hg-init ()
1079 (interactive)
1079 (interactive)
1080 (error "not implemented"))
1080 (error "not implemented"))
1081
1081
1082 (defun hg-log-mode ()
1082 (defun hg-log-mode ()
1083 "Mode for viewing a Mercurial change log."
1083 "Mode for viewing a Mercurial change log."
1084 (goto-char (point-min))
1084 (goto-char (point-min))
1085 (when (looking-at "^searching for changes.*$")
1085 (when (looking-at "^searching for changes.*$")
1086 (delete-region (match-beginning 0) (match-end 0)))
1086 (delete-region (match-beginning 0) (match-end 0)))
1087 (run-hooks 'hg-log-mode-hook))
1087 (run-hooks 'hg-log-mode-hook))
1088
1088
1089 (defun hg-log (path &optional rev1 rev2 log-limit)
1089 (defun hg-log (path &optional rev1 rev2 log-limit)
1090 "Display the revision history of PATH.
1090 "Display the revision history of PATH.
1091 History is displayed between REV1 and REV2.
1091 History is displayed between REV1 and REV2.
1092 Number of displayed changesets is limited to LOG-LIMIT.
1092 Number of displayed changesets is limited to LOG-LIMIT.
1093 REV1 defaults to the tip, while REV2 defaults to 0.
1093 REV1 defaults to the tip, while REV2 defaults to 0.
1094 LOG-LIMIT defaults to `hg-log-limit'.
1094 LOG-LIMIT defaults to `hg-log-limit'.
1095 With a prefix argument, prompt for each parameter."
1095 With a prefix argument, prompt for each parameter."
1096 (interactive (list (hg-read-file-name " to log")
1096 (interactive (list (hg-read-file-name " to log")
1097 (hg-read-rev " to start with"
1097 (hg-read-rev " to start with"
1098 "tip")
1098 "tip")
1099 (hg-read-rev " to end with"
1099 (hg-read-rev " to end with"
1100 "0")
1100 "0")
1101 (hg-read-number "Output limited to: "
1101 (hg-read-number "Output limited to: "
1102 hg-log-limit)))
1102 hg-log-limit)))
1103 (let ((a-path (hg-abbrev-file-name path))
1103 (let ((a-path (hg-abbrev-file-name path))
1104 (r1 (or rev1 "tip"))
1104 (r1 (or rev1 "tip"))
1105 (r2 (or rev2 "0"))
1105 (r2 (or rev2 "0"))
1106 (limit (format "%d" (or log-limit hg-log-limit))))
1106 (limit (format "%d" (or log-limit hg-log-limit))))
1107 (hg-view-output ((if (equal r1 r2)
1107 (hg-view-output ((if (equal r1 r2)
1108 (format "Mercurial: Log of rev %s of %s" rev1 a-path)
1108 (format "Mercurial: Log of rev %s of %s" rev1 a-path)
1109 (format
1109 (format
1110 "Mercurial: at most %s log(s) from rev %s to %s of %s"
1110 "Mercurial: at most %s log(s) from rev %s to %s of %s"
1111 limit r1 r2 a-path)))
1111 limit r1 r2 a-path)))
1112 (eval (list* 'call-process (hg-binary) nil t nil
1112 (eval (list* 'call-process (hg-binary) nil t nil
1113 "log"
1113 "log"
1114 "-r" (format "%s:%s" r1 r2)
1114 "-r" (format "%s:%s" r1 r2)
1115 "-l" limit
1115 "-l" limit
1116 (if (> (length path) (length (hg-root path)))
1116 (if (> (length path) (length (hg-root path)))
1117 (cons path nil)
1117 (cons path nil)
1118 nil)))
1118 nil)))
1119 (hg-log-mode)
1119 (hg-log-mode)
1120 (cd (hg-root path)))))
1120 (cd (hg-root path)))))
1121
1121
1122 (defun hg-log-repo (path &optional rev1 rev2 log-limit)
1122 (defun hg-log-repo (path &optional rev1 rev2 log-limit)
1123 "Display the revision history of the repository containing PATH.
1123 "Display the revision history of the repository containing PATH.
1124 History is displayed between REV1 and REV2.
1124 History is displayed between REV1 and REV2.
1125 Number of displayed changesets is limited to LOG-LIMIT,
1125 Number of displayed changesets is limited to LOG-LIMIT,
1126 REV1 defaults to the tip, while REV2 defaults to 0.
1126 REV1 defaults to the tip, while REV2 defaults to 0.
1127 LOG-LIMIT defaults to `hg-log-limit'.
1127 LOG-LIMIT defaults to `hg-log-limit'.
1128 With a prefix argument, prompt for each parameter."
1128 With a prefix argument, prompt for each parameter."
1129 (interactive (list (hg-read-file-name " to log")
1129 (interactive (list (hg-read-file-name " to log")
1130 (hg-read-rev " to start with"
1130 (hg-read-rev " to start with"
1131 "tip")
1131 "tip")
1132 (hg-read-rev " to end with"
1132 (hg-read-rev " to end with"
1133 "0")
1133 "0")
1134 (hg-read-number "Output limited to: "
1134 (hg-read-number "Output limited to: "
1135 hg-log-limit)))
1135 hg-log-limit)))
1136 (hg-log (hg-root path) rev1 rev2 log-limit))
1136 (hg-log (hg-root path) rev1 rev2 log-limit))
1137
1137
1138 (defun hg-outgoing (&optional repo)
1138 (defun hg-outgoing (&optional repo)
1139 "Display changesets present locally that are not present in REPO."
1139 "Display changesets present locally that are not present in REPO."
1140 (interactive (list (hg-read-repo-name " where changes would go to" nil
1140 (interactive (list (hg-read-repo-name " where changes would go to" nil
1141 hg-outgoing-repository)))
1141 hg-outgoing-repository)))
1142 (hg-view-output ((format "Mercurial: Outgoing from %s to %s"
1142 (hg-view-output ((format "Mercurial: Outgoing from %s to %s"
1143 (hg-abbrev-file-name (hg-root))
1143 (hg-abbrev-file-name (hg-root))
1144 (hg-abbrev-file-name
1144 (hg-abbrev-file-name
1145 (or repo hg-outgoing-repository))))
1145 (or repo hg-outgoing-repository))))
1146 (call-process (hg-binary) nil t nil "outgoing"
1146 (call-process (hg-binary) nil t nil "outgoing"
1147 (or repo hg-outgoing-repository))
1147 (or repo hg-outgoing-repository))
1148 (hg-log-mode)
1148 (hg-log-mode)
1149 (cd (hg-root))))
1149 (cd (hg-root))))
1150
1150
1151 (defun hg-pull (&optional repo)
1151 (defun hg-pull (&optional repo)
1152 "Pull changes from repository REPO.
1152 "Pull changes from repository REPO.
1153 This does not update the working directory."
1153 This does not update the working directory."
1154 (interactive (list (hg-read-repo-name " to pull from")))
1154 (interactive (list (hg-read-repo-name " to pull from")))
1155 (hg-view-output ((format "Mercurial: Pull to %s from %s"
1155 (hg-view-output ((format "Mercurial: Pull to %s from %s"
1156 (hg-abbrev-file-name (hg-root))
1156 (hg-abbrev-file-name (hg-root))
1157 (hg-abbrev-file-name
1157 (hg-abbrev-file-name
1158 (or repo hg-incoming-repository))))
1158 (or repo hg-incoming-repository))))
1159 (call-process (hg-binary) nil t nil "pull"
1159 (call-process (hg-binary) nil t nil "pull"
1160 (or repo hg-incoming-repository))
1160 (or repo hg-incoming-repository))
1161 (cd (hg-root))))
1161 (cd (hg-root))))
1162
1162
1163 (defun hg-push (&optional repo)
1163 (defun hg-push (&optional repo)
1164 "Push changes to repository REPO."
1164 "Push changes to repository REPO."
1165 (interactive (list (hg-read-repo-name " to push to")))
1165 (interactive (list (hg-read-repo-name " to push to")))
1166 (hg-view-output ((format "Mercurial: Push from %s to %s"
1166 (hg-view-output ((format "Mercurial: Push from %s to %s"
1167 (hg-abbrev-file-name (hg-root))
1167 (hg-abbrev-file-name (hg-root))
1168 (hg-abbrev-file-name
1168 (hg-abbrev-file-name
1169 (or repo hg-outgoing-repository))))
1169 (or repo hg-outgoing-repository))))
1170 (call-process (hg-binary) nil t nil "push"
1170 (call-process (hg-binary) nil t nil "push"
1171 (or repo hg-outgoing-repository))
1171 (or repo hg-outgoing-repository))
1172 (cd (hg-root))))
1172 (cd (hg-root))))
1173
1173
1174 (defun hg-revert-buffer-internal ()
1174 (defun hg-revert-buffer-internal ()
1175 (let ((ctx (hg-buffer-context)))
1175 (let ((ctx (hg-buffer-context)))
1176 (message "Reverting %s..." buffer-file-name)
1176 (message "Reverting %s..." buffer-file-name)
1177 (hg-run0 "revert" buffer-file-name)
1177 (hg-run0 "revert" buffer-file-name)
1178 (revert-buffer t t t)
1178 (revert-buffer t t t)
1179 (hg-restore-context ctx)
1179 (hg-restore-context ctx)
1180 (hg-mode-line)
1180 (hg-mode-line)
1181 (message "Reverting %s...done" buffer-file-name)))
1181 (message "Reverting %s...done" buffer-file-name)))
1182
1182
1183 (defun hg-revert-buffer ()
1183 (defun hg-revert-buffer ()
1184 "Revert current buffer's file back to the latest committed version.
1184 "Revert current buffer's file back to the latest committed version.
1185 If the file has not changed, nothing happens. Otherwise, this
1185 If the file has not changed, nothing happens. Otherwise, this
1186 displays a diff and asks for confirmation before reverting."
1186 displays a diff and asks for confirmation before reverting."
1187 (interactive)
1187 (interactive)
1188 (let ((vc-suppress-confirm nil)
1188 (let ((vc-suppress-confirm nil)
1189 (obuf (current-buffer))
1189 (obuf (current-buffer))
1190 diff)
1190 diff)
1191 (vc-buffer-sync)
1191 (vc-buffer-sync)
1192 (unwind-protect
1192 (unwind-protect
1193 (setq diff (hg-diff buffer-file-name))
1193 (setq diff (hg-diff buffer-file-name))
1194 (when diff
1194 (when diff
1195 (unless (yes-or-no-p "Discard changes? ")
1195 (unless (yes-or-no-p "Discard changes? ")
1196 (error "Revert cancelled")))
1196 (error "Revert cancelled")))
1197 (when diff
1197 (when diff
1198 (let ((buf (current-buffer)))
1198 (let ((buf (current-buffer)))
1199 (delete-window (selected-window))
1199 (delete-window (selected-window))
1200 (kill-buffer buf))))
1200 (kill-buffer buf))))
1201 (set-buffer obuf)
1201 (set-buffer obuf)
1202 (when diff
1202 (when diff
1203 (hg-revert-buffer-internal))))
1203 (hg-revert-buffer-internal))))
1204
1204
1205 (defun hg-root (&optional path)
1205 (defun hg-root (&optional path)
1206 "Return the root of the repository that contains the given path.
1206 "Return the root of the repository that contains the given path.
1207 If the path is outside a repository, return nil.
1207 If the path is outside a repository, return nil.
1208 When called interactively, the root is printed. A prefix argument
1208 When called interactively, the root is printed. A prefix argument
1209 prompts for a path to check."
1209 prompts for a path to check."
1210 (interactive (list (hg-read-file-name)))
1210 (interactive (list (hg-read-file-name)))
1211 (if (or path (not hg-root))
1211 (if (or path (not hg-root))
1212 (let ((root (do ((prev nil dir)
1212 (let ((root (do ((prev nil dir)
1213 (dir (file-name-directory
1213 (dir (file-name-directory
1214 (or
1214 (or
1215 path
1215 path
1216 buffer-file-name
1216 buffer-file-name
1217 (expand-file-name default-directory)))
1217 (expand-file-name default-directory)))
1218 (file-name-directory (directory-file-name dir))))
1218 (file-name-directory (directory-file-name dir))))
1219 ((equal prev dir))
1219 ((equal prev dir))
1220 (when (file-directory-p (concat dir ".hg"))
1220 (when (file-directory-p (concat dir ".hg"))
1221 (return dir)))))
1221 (return dir)))))
1222 (when (interactive-p)
1222 (when (interactive-p)
1223 (if root
1223 (if root
1224 (message "The root of this repository is `%s'." root)
1224 (message "The root of this repository is `%s'." root)
1225 (message "The path `%s' is not in a Mercurial repository."
1225 (message "The path `%s' is not in a Mercurial repository."
1226 (hg-abbrev-file-name path))))
1226 (hg-abbrev-file-name path))))
1227 root)
1227 root)
1228 hg-root))
1228 hg-root))
1229
1229
1230 (defun hg-cwd (&optional path)
1230 (defun hg-cwd (&optional path)
1231 "Return the current directory of PATH within the repository."
1231 "Return the current directory of PATH within the repository."
1232 (do ((stack nil (cons (file-name-nondirectory
1232 (do ((stack nil (cons (file-name-nondirectory
1233 (directory-file-name dir))
1233 (directory-file-name dir))
1234 stack))
1234 stack))
1235 (prev nil dir)
1235 (prev nil dir)
1236 (dir (file-name-directory (or path buffer-file-name
1236 (dir (file-name-directory (or path buffer-file-name
1237 (expand-file-name default-directory)))
1237 (expand-file-name default-directory)))
1238 (file-name-directory (directory-file-name dir))))
1238 (file-name-directory (directory-file-name dir))))
1239 ((equal prev dir))
1239 ((equal prev dir))
1240 (when (file-directory-p (concat dir ".hg"))
1240 (when (file-directory-p (concat dir ".hg"))
1241 (let ((cwd (mapconcat 'identity stack "/")))
1241 (let ((cwd (mapconcat 'identity stack "/")))
1242 (unless (equal cwd "")
1242 (unless (equal cwd "")
1243 (return (file-name-as-directory cwd)))))))
1243 (return (file-name-as-directory cwd)))))))
1244
1244
1245 (defun hg-status (path)
1245 (defun hg-status (path)
1246 "Print revision control status of a file or directory.
1246 "Print revision control status of a file or directory.
1247 With prefix argument, prompt for the path to give status for.
1247 With prefix argument, prompt for the path to give status for.
1248 Names are displayed relative to the repository root."
1248 Names are displayed relative to the repository root."
1249 (interactive (list (hg-read-file-name " for status" (hg-root))))
1249 (interactive (list (hg-read-file-name " for status" (hg-root))))
1250 (let ((root (hg-root)))
1250 (let ((root (hg-root)))
1251 (hg-view-output ((format "Mercurial: Status of %s in %s"
1251 (hg-view-output ((format "Mercurial: Status of %s in %s"
1252 (let ((name (substring (expand-file-name path)
1252 (let ((name (substring (expand-file-name path)
1253 (length root))))
1253 (length root))))
1254 (if (> (length name) 0)
1254 (if (> (length name) 0)
1255 name
1255 name
1256 "*"))
1256 "*"))
1257 (hg-abbrev-file-name root)))
1257 (hg-abbrev-file-name root)))
1258 (apply 'call-process (hg-binary) nil t nil
1258 (apply 'call-process (hg-binary) nil t nil
1259 (list "--cwd" root "status" path))
1259 (list "--cwd" root "status" path))
1260 (cd (hg-root path)))))
1260 (cd (hg-root path)))))
1261
1261
1262 (defun hg-undo ()
1262 (defun hg-undo ()
1263 (interactive)
1263 (interactive)
1264 (error "not implemented"))
1264 (error "not implemented"))
1265
1265
1266 (defun hg-update ()
1266 (defun hg-update ()
1267 (interactive)
1267 (interactive)
1268 (error "not implemented"))
1268 (error "not implemented"))
1269
1269
1270 (defun hg-version-other-window (rev)
1270 (defun hg-version-other-window (rev)
1271 "Visit version REV of the current file in another window.
1271 "Visit version REV of the current file in another window.
1272 If the current file is named `F', the version is named `F.~REV~'.
1272 If the current file is named `F', the version is named `F.~REV~'.
1273 If `F.~REV~' already exists, use it instead of checking it out again."
1273 If `F.~REV~' already exists, use it instead of checking it out again."
1274 (interactive "sVersion to visit (default is workfile version): ")
1274 (interactive "sVersion to visit (default is workfile version): ")
1275 (let* ((file buffer-file-name)
1275 (let* ((file buffer-file-name)
1276 (version (if (string-equal rev "")
1276 (version (if (string-equal rev "")
1277 "tip"
1277 "tip"
1278 rev))
1278 rev))
1279 (automatic-backup (vc-version-backup-file-name file version))
1279 (automatic-backup (vc-version-backup-file-name file version))
1280 (manual-backup (vc-version-backup-file-name file version 'manual)))
1280 (manual-backup (vc-version-backup-file-name file version 'manual)))
1281 (unless (file-exists-p manual-backup)
1281 (unless (file-exists-p manual-backup)
1282 (if (file-exists-p automatic-backup)
1282 (if (file-exists-p automatic-backup)
1283 (rename-file automatic-backup manual-backup nil)
1283 (rename-file automatic-backup manual-backup nil)
1284 (hg-run0 "-q" "cat" "-r" version "-o" manual-backup file)))
1284 (hg-run0 "-q" "cat" "-r" version "-o" manual-backup file)))
1285 (find-file-other-window manual-backup)))
1285 (find-file-other-window manual-backup)))
1286
1286
1287
1287
1288 (provide 'mercurial)
1288 (provide 'mercurial)
1289
1289
1290
1290
1291 ;;; Local Variables:
1291 ;;; Local Variables:
1292 ;;; prompt-to-byte-compile: nil
1292 ;;; prompt-to-byte-compile: nil
1293 ;;; end:
1293 ;;; end:
@@ -1,1703 +1,1703 b''
1 " vim600: set foldmethod=marker:
1 " vim600: set foldmethod=marker:
2 "
2 "
3 " Vim plugin to assist in working with HG-controlled files.
3 " Vim plugin to assist in working with HG-controlled files.
4 "
4 "
5 " Last Change: 2006/02/22
5 " Last Change: 2006/02/22
6 " Version: 1.77
6 " Version: 1.77
7 " Maintainer: Mathieu Clabaut <mathieu.clabaut@gmail.com>
7 " Maintainer: Mathieu Clabaut <mathieu.clabaut@gmail.com>
8 " License: This file is placed in the public domain.
8 " License: This file is placed in the public domain.
9 " Credits:
9 " Credits:
10 " Bob Hiestand <bob.hiestand@gmail.com> for the fabulous
10 " Bob Hiestand <bob.hiestand@gmail.com> for the fabulous
11 " cvscommand.vim from which this script was directly created by
11 " cvscommand.vim from which this script was directly created by
12 " means of sed commands and minor tweaks.
12 " means of sed commands and minor tweaks.
13 " Note:
13 " Note:
14 " For Vim7 the use of Bob Hiestand's vcscommand.vim
14 " For Vim7 the use of Bob Hiestand's vcscommand.vim
15 " <http://www.vim.org/scripts/script.php?script_id=90>
15 " <http://www.vim.org/scripts/script.php?script_id=90>
16 " in conjunction with Vladmir Marek's Hg backend
16 " in conjunction with Vladmir Marek's Hg backend
17 " <http://www.vim.org/scripts/script.php?script_id=1898>
17 " <http://www.vim.org/scripts/script.php?script_id=1898>
18 " is recommended.
18 " is recommended.
19
19
20 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
20 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
21 "
21 "
22 " Section: Documentation
22 " Section: Documentation
23 "----------------------------
23 "----------------------------
24 "
24 "
25 " Documentation should be available by ":help hgcommand" command, once the
25 " Documentation should be available by ":help hgcommand" command, once the
26 " script has been copied in you .vim/plugin directory.
26 " script has been copied in you .vim/plugin directory.
27 "
27 "
28 " You still can read the documentation at the end of this file. Locate it by
28 " You still can read the documentation at the end of this file. Locate it by
29 " searching the "hgcommand-contents" string (and set ft=help to have
29 " searching the "hgcommand-contents" string (and set ft=help to have
30 " appropriate syntaxic coloration).
30 " appropriate syntactic coloration).
31
31
32 " Section: Plugin header {{{1
32 " Section: Plugin header {{{1
33
33
34 " loaded_hgcommand is set to 1 when the initialization begins, and 2 when it
34 " loaded_hgcommand is set to 1 when the initialization begins, and 2 when it
35 " completes. This allows various actions to only be taken by functions after
35 " completes. This allows various actions to only be taken by functions after
36 " system initialization.
36 " system initialization.
37
37
38 if exists("g:loaded_hgcommand")
38 if exists("g:loaded_hgcommand")
39 finish
39 finish
40 endif
40 endif
41 let g:loaded_hgcommand = 1
41 let g:loaded_hgcommand = 1
42
42
43 " store 'compatible' settings
43 " store 'compatible' settings
44 let s:save_cpo = &cpo
44 let s:save_cpo = &cpo
45 set cpo&vim
45 set cpo&vim
46
46
47 " run checks
47 " run checks
48 let s:script_name = expand("<sfile>:t:r")
48 let s:script_name = expand("<sfile>:t:r")
49
49
50 function! s:HGCleanupOnFailure(err)
50 function! s:HGCleanupOnFailure(err)
51 echohl WarningMsg
51 echohl WarningMsg
52 echomsg s:script_name . ":" a:err "Plugin not loaded"
52 echomsg s:script_name . ":" a:err "Plugin not loaded"
53 echohl None
53 echohl None
54 let g:loaded_hgcommand = "no"
54 let g:loaded_hgcommand = "no"
55 unlet s:save_cpo s:script_name
55 unlet s:save_cpo s:script_name
56 endfunction
56 endfunction
57
57
58 if v:version < 602
58 if v:version < 602
59 call <SID>HGCleanupOnFailure("VIM 6.2 or later required.")
59 call <SID>HGCleanupOnFailure("VIM 6.2 or later required.")
60 finish
60 finish
61 endif
61 endif
62
62
63 if !exists("*system")
63 if !exists("*system")
64 call <SID>HGCleanupOnFailure("builtin system() function required.")
64 call <SID>HGCleanupOnFailure("builtin system() function required.")
65 finish
65 finish
66 endif
66 endif
67
67
68 let s:script_version = "v0.2"
68 let s:script_version = "v0.2"
69
69
70 " Section: Event group setup {{{1
70 " Section: Event group setup {{{1
71
71
72 augroup HGCommand
72 augroup HGCommand
73 augroup END
73 augroup END
74
74
75 " Section: Plugin initialization {{{1
75 " Section: Plugin initialization {{{1
76 silent do HGCommand User HGPluginInit
76 silent do HGCommand User HGPluginInit
77
77
78 " Section: Script variable initialization {{{1
78 " Section: Script variable initialization {{{1
79
79
80 let s:HGCommandEditFileRunning = 0
80 let s:HGCommandEditFileRunning = 0
81 unlet! s:vimDiffRestoreCmd
81 unlet! s:vimDiffRestoreCmd
82 unlet! s:vimDiffSourceBuffer
82 unlet! s:vimDiffSourceBuffer
83 unlet! s:vimDiffBufferCount
83 unlet! s:vimDiffBufferCount
84 unlet! s:vimDiffScratchList
84 unlet! s:vimDiffScratchList
85
85
86 " Section: Utility functions {{{1
86 " Section: Utility functions {{{1
87
87
88 " Function: s:HGResolveLink() {{{2
88 " Function: s:HGResolveLink() {{{2
89 " Fully resolve the given file name to remove shortcuts or symbolic links.
89 " Fully resolve the given file name to remove shortcuts or symbolic links.
90
90
91 function! s:HGResolveLink(fileName)
91 function! s:HGResolveLink(fileName)
92 let resolved = resolve(a:fileName)
92 let resolved = resolve(a:fileName)
93 if resolved != a:fileName
93 if resolved != a:fileName
94 let resolved = <SID>HGResolveLink(resolved)
94 let resolved = <SID>HGResolveLink(resolved)
95 endif
95 endif
96 return resolved
96 return resolved
97 endfunction
97 endfunction
98
98
99 " Function: s:HGChangeToCurrentFileDir() {{{2
99 " Function: s:HGChangeToCurrentFileDir() {{{2
100 " Go to the directory in which the current HG-controlled file is located.
100 " Go to the directory in which the current HG-controlled file is located.
101 " If this is a HG command buffer, first switch to the original file.
101 " If this is a HG command buffer, first switch to the original file.
102
102
103 function! s:HGChangeToCurrentFileDir(fileName)
103 function! s:HGChangeToCurrentFileDir(fileName)
104 let oldCwd=getcwd()
104 let oldCwd=getcwd()
105 let fileName=<SID>HGResolveLink(a:fileName)
105 let fileName=<SID>HGResolveLink(a:fileName)
106 let newCwd=fnamemodify(fileName, ':h')
106 let newCwd=fnamemodify(fileName, ':h')
107 if strlen(newCwd) > 0
107 if strlen(newCwd) > 0
108 execute 'cd' escape(newCwd, ' ')
108 execute 'cd' escape(newCwd, ' ')
109 endif
109 endif
110 return oldCwd
110 return oldCwd
111 endfunction
111 endfunction
112
112
113 " Function: <SID>HGGetOption(name, default) {{{2
113 " Function: <SID>HGGetOption(name, default) {{{2
114 " Grab a user-specified option to override the default provided. Options are
114 " Grab a user-specified option to override the default provided. Options are
115 " searched in the window, buffer, then global spaces.
115 " searched in the window, buffer, then global spaces.
116
116
117 function! s:HGGetOption(name, default)
117 function! s:HGGetOption(name, default)
118 if exists("s:" . a:name . "Override")
118 if exists("s:" . a:name . "Override")
119 execute "return s:".a:name."Override"
119 execute "return s:".a:name."Override"
120 elseif exists("w:" . a:name)
120 elseif exists("w:" . a:name)
121 execute "return w:".a:name
121 execute "return w:".a:name
122 elseif exists("b:" . a:name)
122 elseif exists("b:" . a:name)
123 execute "return b:".a:name
123 execute "return b:".a:name
124 elseif exists("g:" . a:name)
124 elseif exists("g:" . a:name)
125 execute "return g:".a:name
125 execute "return g:".a:name
126 else
126 else
127 return a:default
127 return a:default
128 endif
128 endif
129 endfunction
129 endfunction
130
130
131 " Function: s:HGEditFile(name, origBuffNR) {{{2
131 " Function: s:HGEditFile(name, origBuffNR) {{{2
132 " Wrapper around the 'edit' command to provide some helpful error text if the
132 " Wrapper around the 'edit' command to provide some helpful error text if the
133 " current buffer can't be abandoned. If name is provided, it is used;
133 " current buffer can't be abandoned. If name is provided, it is used;
134 " otherwise, a nameless scratch buffer is used.
134 " otherwise, a nameless scratch buffer is used.
135 " Returns: 0 if successful, -1 if an error occurs.
135 " Returns: 0 if successful, -1 if an error occurs.
136
136
137 function! s:HGEditFile(name, origBuffNR)
137 function! s:HGEditFile(name, origBuffNR)
138 "Name parameter will be pasted into expression.
138 "Name parameter will be pasted into expression.
139 let name = escape(a:name, ' *?\')
139 let name = escape(a:name, ' *?\')
140
140
141 let editCommand = <SID>HGGetOption('HGCommandEdit', 'edit')
141 let editCommand = <SID>HGGetOption('HGCommandEdit', 'edit')
142 if editCommand != 'edit'
142 if editCommand != 'edit'
143 if <SID>HGGetOption('HGCommandSplit', 'horizontal') == 'horizontal'
143 if <SID>HGGetOption('HGCommandSplit', 'horizontal') == 'horizontal'
144 if name == ""
144 if name == ""
145 let editCommand = 'rightbelow new'
145 let editCommand = 'rightbelow new'
146 else
146 else
147 let editCommand = 'rightbelow split ' . name
147 let editCommand = 'rightbelow split ' . name
148 endif
148 endif
149 else
149 else
150 if name == ""
150 if name == ""
151 let editCommand = 'vert rightbelow new'
151 let editCommand = 'vert rightbelow new'
152 else
152 else
153 let editCommand = 'vert rightbelow split ' . name
153 let editCommand = 'vert rightbelow split ' . name
154 endif
154 endif
155 endif
155 endif
156 else
156 else
157 if name == ""
157 if name == ""
158 let editCommand = 'enew'
158 let editCommand = 'enew'
159 else
159 else
160 let editCommand = 'edit ' . name
160 let editCommand = 'edit ' . name
161 endif
161 endif
162 endif
162 endif
163
163
164 " Protect against useless buffer set-up
164 " Protect against useless buffer set-up
165 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning + 1
165 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning + 1
166 try
166 try
167 execute editCommand
167 execute editCommand
168 finally
168 finally
169 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning - 1
169 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning - 1
170 endtry
170 endtry
171
171
172 let b:HGOrigBuffNR=a:origBuffNR
172 let b:HGOrigBuffNR=a:origBuffNR
173 let b:HGCommandEdit='split'
173 let b:HGCommandEdit='split'
174 endfunction
174 endfunction
175
175
176 " Function: s:HGCreateCommandBuffer(cmd, cmdName, statusText, filename) {{{2
176 " Function: s:HGCreateCommandBuffer(cmd, cmdName, statusText, filename) {{{2
177 " Creates a new scratch buffer and captures the output from execution of the
177 " Creates a new scratch buffer and captures the output from execution of the
178 " given command. The name of the scratch buffer is returned.
178 " given command. The name of the scratch buffer is returned.
179
179
180 function! s:HGCreateCommandBuffer(cmd, cmdName, statusText, origBuffNR)
180 function! s:HGCreateCommandBuffer(cmd, cmdName, statusText, origBuffNR)
181 let fileName=bufname(a:origBuffNR)
181 let fileName=bufname(a:origBuffNR)
182
182
183 let resultBufferName=''
183 let resultBufferName=''
184
184
185 if <SID>HGGetOption("HGCommandNameResultBuffers", 0)
185 if <SID>HGGetOption("HGCommandNameResultBuffers", 0)
186 let nameMarker = <SID>HGGetOption("HGCommandNameMarker", '_')
186 let nameMarker = <SID>HGGetOption("HGCommandNameMarker", '_')
187 if strlen(a:statusText) > 0
187 if strlen(a:statusText) > 0
188 let bufName=a:cmdName . ' -- ' . a:statusText
188 let bufName=a:cmdName . ' -- ' . a:statusText
189 else
189 else
190 let bufName=a:cmdName
190 let bufName=a:cmdName
191 endif
191 endif
192 let bufName=fileName . ' ' . nameMarker . bufName . nameMarker
192 let bufName=fileName . ' ' . nameMarker . bufName . nameMarker
193 let counter=0
193 let counter=0
194 let resultBufferName = bufName
194 let resultBufferName = bufName
195 while buflisted(resultBufferName)
195 while buflisted(resultBufferName)
196 let counter=counter + 1
196 let counter=counter + 1
197 let resultBufferName=bufName . ' (' . counter . ')'
197 let resultBufferName=bufName . ' (' . counter . ')'
198 endwhile
198 endwhile
199 endif
199 endif
200
200
201 let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " " . a:cmd
201 let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " " . a:cmd
202 "echomsg "DBG :".hgCommand
202 "echomsg "DBG :".hgCommand
203 let hgOut = system(hgCommand)
203 let hgOut = system(hgCommand)
204 " HACK: diff command does not return proper error codes
204 " HACK: diff command does not return proper error codes
205 if v:shell_error && a:cmdName != 'hgdiff'
205 if v:shell_error && a:cmdName != 'hgdiff'
206 if strlen(hgOut) == 0
206 if strlen(hgOut) == 0
207 echoerr "HG command failed"
207 echoerr "HG command failed"
208 else
208 else
209 echoerr "HG command failed: " . hgOut
209 echoerr "HG command failed: " . hgOut
210 endif
210 endif
211 return -1
211 return -1
212 endif
212 endif
213 if strlen(hgOut) == 0
213 if strlen(hgOut) == 0
214 " Handle case of no output. In this case, it is important to check the
214 " Handle case of no output. In this case, it is important to check the
215 " file status, especially since hg edit/unedit may change the attributes
215 " file status, especially since hg edit/unedit may change the attributes
216 " of the file with no visible output.
216 " of the file with no visible output.
217
217
218 echomsg "No output from HG command"
218 echomsg "No output from HG command"
219 checktime
219 checktime
220 return -1
220 return -1
221 endif
221 endif
222
222
223 if <SID>HGEditFile(resultBufferName, a:origBuffNR) == -1
223 if <SID>HGEditFile(resultBufferName, a:origBuffNR) == -1
224 return -1
224 return -1
225 endif
225 endif
226
226
227 set buftype=nofile
227 set buftype=nofile
228 set noswapfile
228 set noswapfile
229 set filetype=
229 set filetype=
230
230
231 if <SID>HGGetOption("HGCommandDeleteOnHide", 0)
231 if <SID>HGGetOption("HGCommandDeleteOnHide", 0)
232 set bufhidden=delete
232 set bufhidden=delete
233 endif
233 endif
234
234
235 silent 0put=hgOut
235 silent 0put=hgOut
236
236
237 " The last command left a blank line at the end of the buffer. If the
237 " The last command left a blank line at the end of the buffer. If the
238 " last line is folded (a side effect of the 'put') then the attempt to
238 " last line is folded (a side effect of the 'put') then the attempt to
239 " remove the blank line will kill the last fold.
239 " remove the blank line will kill the last fold.
240 "
240 "
241 " This could be fixed by explicitly detecting whether the last line is
241 " This could be fixed by explicitly detecting whether the last line is
242 " within a fold, but I prefer to simply unfold the result buffer altogether.
242 " within a fold, but I prefer to simply unfold the result buffer altogether.
243
243
244 if has("folding")
244 if has("folding")
245 setlocal nofoldenable
245 setlocal nofoldenable
246 endif
246 endif
247
247
248 $d
248 $d
249 1
249 1
250
250
251 " Define the environment and execute user-defined hooks.
251 " Define the environment and execute user-defined hooks.
252
252
253 let b:HGSourceFile=fileName
253 let b:HGSourceFile=fileName
254 let b:HGCommand=a:cmdName
254 let b:HGCommand=a:cmdName
255 if a:statusText != ""
255 if a:statusText != ""
256 let b:HGStatusText=a:statusText
256 let b:HGStatusText=a:statusText
257 endif
257 endif
258
258
259 silent do HGCommand User HGBufferCreated
259 silent do HGCommand User HGBufferCreated
260 return bufnr("%")
260 return bufnr("%")
261 endfunction
261 endfunction
262
262
263 " Function: s:HGBufferCheck(hgBuffer) {{{2
263 " Function: s:HGBufferCheck(hgBuffer) {{{2
264 " Attempts to locate the original file to which HG operations were applied
264 " Attempts to locate the original file to which HG operations were applied
265 " for a given buffer.
265 " for a given buffer.
266
266
267 function! s:HGBufferCheck(hgBuffer)
267 function! s:HGBufferCheck(hgBuffer)
268 let origBuffer = getbufvar(a:hgBuffer, "HGOrigBuffNR")
268 let origBuffer = getbufvar(a:hgBuffer, "HGOrigBuffNR")
269 if origBuffer
269 if origBuffer
270 if bufexists(origBuffer)
270 if bufexists(origBuffer)
271 return origBuffer
271 return origBuffer
272 else
272 else
273 " Original buffer no longer exists.
273 " Original buffer no longer exists.
274 return -1
274 return -1
275 endif
275 endif
276 else
276 else
277 " No original buffer
277 " No original buffer
278 return a:hgBuffer
278 return a:hgBuffer
279 endif
279 endif
280 endfunction
280 endfunction
281
281
282 " Function: s:HGCurrentBufferCheck() {{{2
282 " Function: s:HGCurrentBufferCheck() {{{2
283 " Attempts to locate the original file to which HG operations were applied
283 " Attempts to locate the original file to which HG operations were applied
284 " for the current buffer.
284 " for the current buffer.
285
285
286 function! s:HGCurrentBufferCheck()
286 function! s:HGCurrentBufferCheck()
287 return <SID>HGBufferCheck(bufnr("%"))
287 return <SID>HGBufferCheck(bufnr("%"))
288 endfunction
288 endfunction
289
289
290 " Function: s:HGToggleDeleteOnHide() {{{2
290 " Function: s:HGToggleDeleteOnHide() {{{2
291 " Toggles on and off the delete-on-hide behavior of HG buffers
291 " Toggles on and off the delete-on-hide behavior of HG buffers
292
292
293 function! s:HGToggleDeleteOnHide()
293 function! s:HGToggleDeleteOnHide()
294 if exists("g:HGCommandDeleteOnHide")
294 if exists("g:HGCommandDeleteOnHide")
295 unlet g:HGCommandDeleteOnHide
295 unlet g:HGCommandDeleteOnHide
296 else
296 else
297 let g:HGCommandDeleteOnHide=1
297 let g:HGCommandDeleteOnHide=1
298 endif
298 endif
299 endfunction
299 endfunction
300
300
301 " Function: s:HGDoCommand(hgcmd, cmdName, statusText) {{{2
301 " Function: s:HGDoCommand(hgcmd, cmdName, statusText) {{{2
302 " General skeleton for HG function execution.
302 " General skeleton for HG function execution.
303 " Returns: name of the new command buffer containing the command results
303 " Returns: name of the new command buffer containing the command results
304
304
305 function! s:HGDoCommand(cmd, cmdName, statusText)
305 function! s:HGDoCommand(cmd, cmdName, statusText)
306 let hgBufferCheck=<SID>HGCurrentBufferCheck()
306 let hgBufferCheck=<SID>HGCurrentBufferCheck()
307 if hgBufferCheck == -1
307 if hgBufferCheck == -1
308 echo "Original buffer no longer exists, aborting."
308 echo "Original buffer no longer exists, aborting."
309 return -1
309 return -1
310 endif
310 endif
311
311
312 let fileName=bufname(hgBufferCheck)
312 let fileName=bufname(hgBufferCheck)
313 if isdirectory(fileName)
313 if isdirectory(fileName)
314 let fileName=fileName . "/" . getline(".")
314 let fileName=fileName . "/" . getline(".")
315 endif
315 endif
316 let realFileName = fnamemodify(<SID>HGResolveLink(fileName), ':t')
316 let realFileName = fnamemodify(<SID>HGResolveLink(fileName), ':t')
317 let oldCwd=<SID>HGChangeToCurrentFileDir(fileName)
317 let oldCwd=<SID>HGChangeToCurrentFileDir(fileName)
318 try
318 try
319 " TODO
319 " TODO
320 "if !filereadable('HG/Root')
320 "if !filereadable('HG/Root')
321 "throw fileName . ' is not a HG-controlled file.'
321 "throw fileName . ' is not a HG-controlled file.'
322 "endif
322 "endif
323 let fullCmd = a:cmd . ' "' . realFileName . '"'
323 let fullCmd = a:cmd . ' "' . realFileName . '"'
324 "echomsg "DEBUG".fullCmd
324 "echomsg "DEBUG".fullCmd
325 let resultBuffer=<SID>HGCreateCommandBuffer(fullCmd, a:cmdName, a:statusText, hgBufferCheck)
325 let resultBuffer=<SID>HGCreateCommandBuffer(fullCmd, a:cmdName, a:statusText, hgBufferCheck)
326 return resultBuffer
326 return resultBuffer
327 catch
327 catch
328 echoerr v:exception
328 echoerr v:exception
329 return -1
329 return -1
330 finally
330 finally
331 execute 'cd' escape(oldCwd, ' ')
331 execute 'cd' escape(oldCwd, ' ')
332 endtry
332 endtry
333 endfunction
333 endfunction
334
334
335
335
336 " Function: s:HGGetStatusVars(revision, branch, repository) {{{2
336 " Function: s:HGGetStatusVars(revision, branch, repository) {{{2
337 "
337 "
338 " Obtains a HG revision number and branch name. The 'revisionVar',
338 " Obtains a HG revision number and branch name. The 'revisionVar',
339 " 'branchVar'and 'repositoryVar' arguments, if non-empty, contain the names of variables to hold
339 " 'branchVar'and 'repositoryVar' arguments, if non-empty, contain the names of variables to hold
340 " the corresponding results.
340 " the corresponding results.
341 "
341 "
342 " Returns: string to be exec'd that sets the multiple return values.
342 " Returns: string to be exec'd that sets the multiple return values.
343
343
344 function! s:HGGetStatusVars(revisionVar, branchVar, repositoryVar)
344 function! s:HGGetStatusVars(revisionVar, branchVar, repositoryVar)
345 let hgBufferCheck=<SID>HGCurrentBufferCheck()
345 let hgBufferCheck=<SID>HGCurrentBufferCheck()
346 "echomsg "DBG : in HGGetStatusVars"
346 "echomsg "DBG : in HGGetStatusVars"
347 if hgBufferCheck == -1
347 if hgBufferCheck == -1
348 return ""
348 return ""
349 endif
349 endif
350 let fileName=bufname(hgBufferCheck)
350 let fileName=bufname(hgBufferCheck)
351 let fileNameWithoutLink=<SID>HGResolveLink(fileName)
351 let fileNameWithoutLink=<SID>HGResolveLink(fileName)
352 let realFileName = fnamemodify(fileNameWithoutLink, ':t')
352 let realFileName = fnamemodify(fileNameWithoutLink, ':t')
353 let oldCwd=<SID>HGChangeToCurrentFileDir(realFileName)
353 let oldCwd=<SID>HGChangeToCurrentFileDir(realFileName)
354 try
354 try
355 let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " root "
355 let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " root "
356 let roottext=system(hgCommand)
356 let roottext=system(hgCommand)
357 " Suppress ending null char ! Does it work in window ?
357 " Suppress ending null char ! Does it work in window ?
358 let roottext=substitute(roottext,'^.*/\([^/\n\r]*\)\n\_.*$','\1','')
358 let roottext=substitute(roottext,'^.*/\([^/\n\r]*\)\n\_.*$','\1','')
359 if match(getcwd()."/".fileNameWithoutLink, roottext) == -1
359 if match(getcwd()."/".fileNameWithoutLink, roottext) == -1
360 return ""
360 return ""
361 endif
361 endif
362 let returnExpression = ""
362 let returnExpression = ""
363 if a:repositoryVar != ""
363 if a:repositoryVar != ""
364 let returnExpression=returnExpression . " | let " . a:repositoryVar . "='" . roottext . "'"
364 let returnExpression=returnExpression . " | let " . a:repositoryVar . "='" . roottext . "'"
365 endif
365 endif
366 let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " status -mardui " . realFileName
366 let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " status -mardui " . realFileName
367 let statustext=system(hgCommand)
367 let statustext=system(hgCommand)
368 if(v:shell_error)
368 if(v:shell_error)
369 return ""
369 return ""
370 endif
370 endif
371 if match(statustext, '^[?I]') >= 0
371 if match(statustext, '^[?I]') >= 0
372 let revision="NEW"
372 let revision="NEW"
373 elseif match(statustext, '^[R]') >= 0
373 elseif match(statustext, '^[R]') >= 0
374 let revision="REMOVED"
374 let revision="REMOVED"
375 elseif match(statustext, '^[D]') >= 0
375 elseif match(statustext, '^[D]') >= 0
376 let revision="DELETED"
376 let revision="DELETED"
377 elseif match(statustext, '^[A]') >= 0
377 elseif match(statustext, '^[A]') >= 0
378 let revision="ADDED"
378 let revision="ADDED"
379 else
379 else
380 " The file is tracked, we can try to get is revision number
380 " The file is tracked, we can try to get is revision number
381 let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " parents "
381 let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " parents "
382 let statustext=system(hgCommand)
382 let statustext=system(hgCommand)
383 if(v:shell_error)
383 if(v:shell_error)
384 return ""
384 return ""
385 endif
385 endif
386 let revision=substitute(statustext, '^changeset:\s*\(\d\+\):.*\_$\_.*$', '\1', "")
386 let revision=substitute(statustext, '^changeset:\s*\(\d\+\):.*\_$\_.*$', '\1', "")
387
387
388 if a:branchVar != "" && match(statustext, '^\_.*\_^branch:') >= 0
388 if a:branchVar != "" && match(statustext, '^\_.*\_^branch:') >= 0
389 let branch=substitute(statustext, '^\_.*\_^branch:\s*\(\S\+\)\n\_.*$', '\1', "")
389 let branch=substitute(statustext, '^\_.*\_^branch:\s*\(\S\+\)\n\_.*$', '\1', "")
390 let returnExpression=returnExpression . " | let " . a:branchVar . "='" . branch . "'"
390 let returnExpression=returnExpression . " | let " . a:branchVar . "='" . branch . "'"
391 endif
391 endif
392 endif
392 endif
393 if (exists('revision'))
393 if (exists('revision'))
394 let returnExpression = "let " . a:revisionVar . "='" . revision . "' " . returnExpression
394 let returnExpression = "let " . a:revisionVar . "='" . revision . "' " . returnExpression
395 endif
395 endif
396
396
397 return returnExpression
397 return returnExpression
398 finally
398 finally
399 execute 'cd' escape(oldCwd, ' ')
399 execute 'cd' escape(oldCwd, ' ')
400 endtry
400 endtry
401 endfunction
401 endfunction
402
402
403 " Function: s:HGSetupBuffer() {{{2
403 " Function: s:HGSetupBuffer() {{{2
404 " Attempts to set the b:HGBranch, b:HGRevision and b:HGRepository variables.
404 " Attempts to set the b:HGBranch, b:HGRevision and b:HGRepository variables.
405
405
406 function! s:HGSetupBuffer(...)
406 function! s:HGSetupBuffer(...)
407 if (exists("b:HGBufferSetup") && b:HGBufferSetup && !exists('a:1'))
407 if (exists("b:HGBufferSetup") && b:HGBufferSetup && !exists('a:1'))
408 " This buffer is already set up.
408 " This buffer is already set up.
409 return
409 return
410 endif
410 endif
411
411
412 if !<SID>HGGetOption("HGCommandEnableBufferSetup", 0)
412 if !<SID>HGGetOption("HGCommandEnableBufferSetup", 0)
413 \ || @% == ""
413 \ || @% == ""
414 \ || s:HGCommandEditFileRunning > 0
414 \ || s:HGCommandEditFileRunning > 0
415 \ || exists("b:HGOrigBuffNR")
415 \ || exists("b:HGOrigBuffNR")
416 unlet! b:HGRevision
416 unlet! b:HGRevision
417 unlet! b:HGBranch
417 unlet! b:HGBranch
418 unlet! b:HGRepository
418 unlet! b:HGRepository
419 return
419 return
420 endif
420 endif
421
421
422 if !filereadable(expand("%"))
422 if !filereadable(expand("%"))
423 return -1
423 return -1
424 endif
424 endif
425
425
426 let revision=""
426 let revision=""
427 let branch=""
427 let branch=""
428 let repository=""
428 let repository=""
429
429
430 exec <SID>HGGetStatusVars('revision', 'branch', 'repository')
430 exec <SID>HGGetStatusVars('revision', 'branch', 'repository')
431 "echomsg "DBG ".revision."#".branch."#".repository
431 "echomsg "DBG ".revision."#".branch."#".repository
432 if revision != ""
432 if revision != ""
433 let b:HGRevision=revision
433 let b:HGRevision=revision
434 else
434 else
435 unlet! b:HGRevision
435 unlet! b:HGRevision
436 endif
436 endif
437 if branch != ""
437 if branch != ""
438 let b:HGBranch=branch
438 let b:HGBranch=branch
439 else
439 else
440 unlet! b:HGBranch
440 unlet! b:HGBranch
441 endif
441 endif
442 if repository != ""
442 if repository != ""
443 let b:HGRepository=repository
443 let b:HGRepository=repository
444 else
444 else
445 unlet! b:HGRepository
445 unlet! b:HGRepository
446 endif
446 endif
447 silent do HGCommand User HGBufferSetup
447 silent do HGCommand User HGBufferSetup
448 let b:HGBufferSetup=1
448 let b:HGBufferSetup=1
449 endfunction
449 endfunction
450
450
451 " Function: s:HGMarkOrigBufferForSetup(hgbuffer) {{{2
451 " Function: s:HGMarkOrigBufferForSetup(hgbuffer) {{{2
452 " Resets the buffer setup state of the original buffer for a given HG buffer.
452 " Resets the buffer setup state of the original buffer for a given HG buffer.
453 " Returns: The HG buffer number in a passthrough mode.
453 " Returns: The HG buffer number in a passthrough mode.
454
454
455 function! s:HGMarkOrigBufferForSetup(hgBuffer)
455 function! s:HGMarkOrigBufferForSetup(hgBuffer)
456 checktime
456 checktime
457 if a:hgBuffer != -1
457 if a:hgBuffer != -1
458 let origBuffer = <SID>HGBufferCheck(a:hgBuffer)
458 let origBuffer = <SID>HGBufferCheck(a:hgBuffer)
459 "This should never not work, but I'm paranoid
459 "This should never not work, but I'm paranoid
460 if origBuffer != a:hgBuffer
460 if origBuffer != a:hgBuffer
461 call setbufvar(origBuffer, "HGBufferSetup", 0)
461 call setbufvar(origBuffer, "HGBufferSetup", 0)
462 endif
462 endif
463 else
463 else
464 "We are presumably in the original buffer
464 "We are presumably in the original buffer
465 let b:HGBufferSetup = 0
465 let b:HGBufferSetup = 0
466 "We do the setup now as now event will be triggered allowing it later.
466 "We do the setup now as now event will be triggered allowing it later.
467 call <SID>HGSetupBuffer()
467 call <SID>HGSetupBuffer()
468 endif
468 endif
469 return a:hgBuffer
469 return a:hgBuffer
470 endfunction
470 endfunction
471
471
472 " Function: s:HGOverrideOption(option, [value]) {{{2
472 " Function: s:HGOverrideOption(option, [value]) {{{2
473 " Provides a temporary override for the given HG option. If no value is
473 " Provides a temporary override for the given HG option. If no value is
474 " passed, the override is disabled.
474 " passed, the override is disabled.
475
475
476 function! s:HGOverrideOption(option, ...)
476 function! s:HGOverrideOption(option, ...)
477 if a:0 == 0
477 if a:0 == 0
478 unlet! s:{a:option}Override
478 unlet! s:{a:option}Override
479 else
479 else
480 let s:{a:option}Override = a:1
480 let s:{a:option}Override = a:1
481 endif
481 endif
482 endfunction
482 endfunction
483
483
484 " Function: s:HGWipeoutCommandBuffers() {{{2
484 " Function: s:HGWipeoutCommandBuffers() {{{2
485 " Clears all current HG buffers of the specified type for a given source.
485 " Clears all current HG buffers of the specified type for a given source.
486
486
487 function! s:HGWipeoutCommandBuffers(originalBuffer, hgCommand)
487 function! s:HGWipeoutCommandBuffers(originalBuffer, hgCommand)
488 let buffer = 1
488 let buffer = 1
489 while buffer <= bufnr('$')
489 while buffer <= bufnr('$')
490 if getbufvar(buffer, 'HGOrigBuffNR') == a:originalBuffer
490 if getbufvar(buffer, 'HGOrigBuffNR') == a:originalBuffer
491 if getbufvar(buffer, 'HGCommand') == a:hgCommand
491 if getbufvar(buffer, 'HGCommand') == a:hgCommand
492 execute 'bw' buffer
492 execute 'bw' buffer
493 endif
493 endif
494 endif
494 endif
495 let buffer = buffer + 1
495 let buffer = buffer + 1
496 endwhile
496 endwhile
497 endfunction
497 endfunction
498
498
499 " Function: s:HGInstallDocumentation(full_name, revision) {{{2
499 " Function: s:HGInstallDocumentation(full_name, revision) {{{2
500 " Install help documentation.
500 " Install help documentation.
501 " Arguments:
501 " Arguments:
502 " full_name: Full name of this vim plugin script, including path name.
502 " full_name: Full name of this vim plugin script, including path name.
503 " revision: Revision of the vim script. #version# mark in the document file
503 " revision: Revision of the vim script. #version# mark in the document file
504 " will be replaced with this string with 'v' prefix.
504 " will be replaced with this string with 'v' prefix.
505 " Return:
505 " Return:
506 " 1 if new document installed, 0 otherwise.
506 " 1 if new document installed, 0 otherwise.
507 " Note: Cleaned and generalized by guo-peng Wen
507 " Note: Cleaned and generalized by guo-peng Wen
508 "'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
508 "'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
509 " Helper function to make mkdir as portable as possible
509 " Helper function to make mkdir as portable as possible
510 function! s:HGFlexiMkdir(dir)
510 function! s:HGFlexiMkdir(dir)
511 if exists("*mkdir") " we can use Vim's own mkdir()
511 if exists("*mkdir") " we can use Vim's own mkdir()
512 call mkdir(a:dir)
512 call mkdir(a:dir)
513 elseif !exists("+shellslash")
513 elseif !exists("+shellslash")
514 call system("mkdir -p '".a:dir."'")
514 call system("mkdir -p '".a:dir."'")
515 else " M$
515 else " M$
516 let l:ssl = &shellslash
516 let l:ssl = &shellslash
517 try
517 try
518 set shellslash
518 set shellslash
519 " no single quotes?
519 " no single quotes?
520 call system('mkdir "'.a:dir.'"')
520 call system('mkdir "'.a:dir.'"')
521 finally
521 finally
522 let &shellslash = l:ssl
522 let &shellslash = l:ssl
523 endtry
523 endtry
524 endif
524 endif
525 endfunction
525 endfunction
526
526
527 function! s:HGInstallDocumentation(full_name)
527 function! s:HGInstallDocumentation(full_name)
528 " Figure out document path based on full name of this script:
528 " Figure out document path based on full name of this script:
529 let l:vim_doc_path = fnamemodify(a:full_name, ":h:h") . "/doc"
529 let l:vim_doc_path = fnamemodify(a:full_name, ":h:h") . "/doc"
530 if filewritable(l:vim_doc_path) != 2
530 if filewritable(l:vim_doc_path) != 2
531 echomsg s:script_name . ": Trying to update docs at" l:vim_doc_path
531 echomsg s:script_name . ": Trying to update docs at" l:vim_doc_path
532 silent! call <SID>HGFlexiMkdir(l:vim_doc_path)
532 silent! call <SID>HGFlexiMkdir(l:vim_doc_path)
533 if filewritable(l:vim_doc_path) != 2
533 if filewritable(l:vim_doc_path) != 2
534 " Try first item in 'runtimepath':
534 " Try first item in 'runtimepath':
535 let l:vim_doc_path =
535 let l:vim_doc_path =
536 \ substitute(&runtimepath, '^\([^,]*\).*', '\1/doc', 'e')
536 \ substitute(&runtimepath, '^\([^,]*\).*', '\1/doc', 'e')
537 if filewritable(l:vim_doc_path) != 2
537 if filewritable(l:vim_doc_path) != 2
538 echomsg s:script_name . ": Trying to update docs at" l:vim_doc_path
538 echomsg s:script_name . ": Trying to update docs at" l:vim_doc_path
539 silent! call <SID>HGFlexiMkdir(l:vim_doc_path)
539 silent! call <SID>HGFlexiMkdir(l:vim_doc_path)
540 if filewritable(l:vim_doc_path) != 2
540 if filewritable(l:vim_doc_path) != 2
541 " Put a warning:
541 " Put a warning:
542 echomsg "Unable to open documentation directory"
542 echomsg "Unable to open documentation directory"
543 echomsg " type `:help add-local-help' for more information."
543 echomsg " type `:help add-local-help' for more information."
544 return 0
544 return 0
545 endif
545 endif
546 endif
546 endif
547 endif
547 endif
548 endif
548 endif
549
549
550 " Full name of documentation file:
550 " Full name of documentation file:
551 let l:doc_file =
551 let l:doc_file =
552 \ l:vim_doc_path . "/" . s:script_name . ".txt"
552 \ l:vim_doc_path . "/" . s:script_name . ".txt"
553 " Bail out if document file is still up to date:
553 " Bail out if document file is still up to date:
554 if filereadable(l:doc_file) &&
554 if filereadable(l:doc_file) &&
555 \ getftime(a:full_name) < getftime(l:doc_file)
555 \ getftime(a:full_name) < getftime(l:doc_file)
556 return 0
556 return 0
557 endif
557 endif
558
558
559 " temporary global settings
559 " temporary global settings
560 let l:lz = &lazyredraw
560 let l:lz = &lazyredraw
561 let l:hls = &hlsearch
561 let l:hls = &hlsearch
562 set lazyredraw nohlsearch
562 set lazyredraw nohlsearch
563 " Create a new buffer & read in the plugin file (me):
563 " Create a new buffer & read in the plugin file (me):
564 1 new
564 1 new
565 setlocal noswapfile modifiable nomodeline
565 setlocal noswapfile modifiable nomodeline
566 if has("folding")
566 if has("folding")
567 setlocal nofoldenable
567 setlocal nofoldenable
568 endif
568 endif
569 silent execute "read" escape(a:full_name, " ")
569 silent execute "read" escape(a:full_name, " ")
570 let l:doc_buf = bufnr("%")
570 let l:doc_buf = bufnr("%")
571
571
572 1
572 1
573 " Delete from first line to a line starts with
573 " Delete from first line to a line starts with
574 " === START_DOC
574 " === START_DOC
575 silent 1,/^=\{3,}\s\+START_DOC\C/ delete _
575 silent 1,/^=\{3,}\s\+START_DOC\C/ delete _
576 " Delete from a line starts with
576 " Delete from a line starts with
577 " === END_DOC
577 " === END_DOC
578 " to the end of the documents:
578 " to the end of the documents:
579 silent /^=\{3,}\s\+END_DOC\C/,$ delete _
579 silent /^=\{3,}\s\+END_DOC\C/,$ delete _
580
580
581 " Add modeline for help doc: the modeline string is mangled intentionally
581 " Add modeline for help doc: the modeline string is mangled intentionally
582 " to avoid it be recognized by VIM:
582 " to avoid it be recognized by VIM:
583 call append(line("$"), "")
583 call append(line("$"), "")
584 call append(line("$"), " v" . "im:tw=78:ts=8:ft=help:norl:")
584 call append(line("$"), " v" . "im:tw=78:ts=8:ft=help:norl:")
585
585
586 " Replace revision:
586 " Replace revision:
587 silent execute "normal :1s/#version#/" . s:script_version . "/\<CR>"
587 silent execute "normal :1s/#version#/" . s:script_version . "/\<CR>"
588 " Save the help document and wipe out buffer:
588 " Save the help document and wipe out buffer:
589 silent execute "wq!" escape(l:doc_file, " ") "| bw" l:doc_buf
589 silent execute "wq!" escape(l:doc_file, " ") "| bw" l:doc_buf
590 " Build help tags:
590 " Build help tags:
591 silent execute "helptags" l:vim_doc_path
591 silent execute "helptags" l:vim_doc_path
592
592
593 let &hlsearch = l:hls
593 let &hlsearch = l:hls
594 let &lazyredraw = l:lz
594 let &lazyredraw = l:lz
595 return 1
595 return 1
596 endfunction
596 endfunction
597
597
598 " Section: Public functions {{{1
598 " Section: Public functions {{{1
599
599
600 " Function: HGGetRevision() {{{2
600 " Function: HGGetRevision() {{{2
601 " Global function for retrieving the current buffer's HG revision number.
601 " Global function for retrieving the current buffer's HG revision number.
602 " Returns: Revision number or an empty string if an error occurs.
602 " Returns: Revision number or an empty string if an error occurs.
603
603
604 function! HGGetRevision()
604 function! HGGetRevision()
605 let revision=""
605 let revision=""
606 exec <SID>HGGetStatusVars('revision', '', '')
606 exec <SID>HGGetStatusVars('revision', '', '')
607 return revision
607 return revision
608 endfunction
608 endfunction
609
609
610 " Function: HGDisableBufferSetup() {{{2
610 " Function: HGDisableBufferSetup() {{{2
611 " Global function for deactivating the buffer autovariables.
611 " Global function for deactivating the buffer autovariables.
612
612
613 function! HGDisableBufferSetup()
613 function! HGDisableBufferSetup()
614 let g:HGCommandEnableBufferSetup=0
614 let g:HGCommandEnableBufferSetup=0
615 silent! augroup! HGCommandPlugin
615 silent! augroup! HGCommandPlugin
616 endfunction
616 endfunction
617
617
618 " Function: HGEnableBufferSetup() {{{2
618 " Function: HGEnableBufferSetup() {{{2
619 " Global function for activating the buffer autovariables.
619 " Global function for activating the buffer autovariables.
620
620
621 function! HGEnableBufferSetup()
621 function! HGEnableBufferSetup()
622 let g:HGCommandEnableBufferSetup=1
622 let g:HGCommandEnableBufferSetup=1
623 augroup HGCommandPlugin
623 augroup HGCommandPlugin
624 au!
624 au!
625 au BufEnter * call <SID>HGSetupBuffer()
625 au BufEnter * call <SID>HGSetupBuffer()
626 au BufWritePost * call <SID>HGSetupBuffer()
626 au BufWritePost * call <SID>HGSetupBuffer()
627 " Force resetting up buffer on external file change (HG update)
627 " Force resetting up buffer on external file change (HG update)
628 au FileChangedShell * call <SID>HGSetupBuffer(1)
628 au FileChangedShell * call <SID>HGSetupBuffer(1)
629 augroup END
629 augroup END
630
630
631 " Only auto-load if the plugin is fully loaded. This gives other plugins a
631 " Only auto-load if the plugin is fully loaded. This gives other plugins a
632 " chance to run.
632 " chance to run.
633 if g:loaded_hgcommand == 2
633 if g:loaded_hgcommand == 2
634 call <SID>HGSetupBuffer()
634 call <SID>HGSetupBuffer()
635 endif
635 endif
636 endfunction
636 endfunction
637
637
638 " Function: HGGetStatusLine() {{{2
638 " Function: HGGetStatusLine() {{{2
639 " Default (sample) status line entry for HG files. This is only useful if
639 " Default (sample) status line entry for HG files. This is only useful if
640 " HG-managed buffer mode is on (see the HGCommandEnableBufferSetup variable
640 " HG-managed buffer mode is on (see the HGCommandEnableBufferSetup variable
641 " for how to do this).
641 " for how to do this).
642
642
643 function! HGGetStatusLine()
643 function! HGGetStatusLine()
644 if exists('b:HGSourceFile')
644 if exists('b:HGSourceFile')
645 " This is a result buffer
645 " This is a result buffer
646 let value='[' . b:HGCommand . ' ' . b:HGSourceFile
646 let value='[' . b:HGCommand . ' ' . b:HGSourceFile
647 if exists('b:HGStatusText')
647 if exists('b:HGStatusText')
648 let value=value . ' ' . b:HGStatusText
648 let value=value . ' ' . b:HGStatusText
649 endif
649 endif
650 let value = value . ']'
650 let value = value . ']'
651 return value
651 return value
652 endif
652 endif
653
653
654 if exists('b:HGRevision')
654 if exists('b:HGRevision')
655 \ && b:HGRevision != ''
655 \ && b:HGRevision != ''
656 \ && exists('b:HGRepository')
656 \ && exists('b:HGRepository')
657 \ && b:HGRepository != ''
657 \ && b:HGRepository != ''
658 \ && exists('g:HGCommandEnableBufferSetup')
658 \ && exists('g:HGCommandEnableBufferSetup')
659 \ && g:HGCommandEnableBufferSetup
659 \ && g:HGCommandEnableBufferSetup
660 if !exists('b:HGBranch')
660 if !exists('b:HGBranch')
661 let l:branch=''
661 let l:branch=''
662 else
662 else
663 let l:branch=b:HGBranch
663 let l:branch=b:HGBranch
664 endif
664 endif
665 return '[HG ' . b:HGRepository . '/' . l:branch .'/' . b:HGRevision . ']'
665 return '[HG ' . b:HGRepository . '/' . l:branch .'/' . b:HGRevision . ']'
666 else
666 else
667 return ''
667 return ''
668 endif
668 endif
669 endfunction
669 endfunction
670
670
671 " Section: HG command functions {{{1
671 " Section: HG command functions {{{1
672
672
673 " Function: s:HGAdd() {{{2
673 " Function: s:HGAdd() {{{2
674 function! s:HGAdd()
674 function! s:HGAdd()
675 return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('add', 'hgadd', ''))
675 return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('add', 'hgadd', ''))
676 endfunction
676 endfunction
677
677
678 " Function: s:HGAnnotate(...) {{{2
678 " Function: s:HGAnnotate(...) {{{2
679 function! s:HGAnnotate(...)
679 function! s:HGAnnotate(...)
680 if a:0 == 0
680 if a:0 == 0
681 if &filetype == "HGAnnotate"
681 if &filetype == "HGAnnotate"
682 " This is a HGAnnotate buffer. Perform annotation of the version
682 " This is a HGAnnotate buffer. Perform annotation of the version
683 " indicated by the current line.
683 " indicated by the current line.
684 let revision = substitute(getline("."),'\(^[0-9]*\):.*','\1','')
684 let revision = substitute(getline("."),'\(^[0-9]*\):.*','\1','')
685 if <SID>HGGetOption('HGCommandAnnotateParent', 0) != 0 && revision > 0
685 if <SID>HGGetOption('HGCommandAnnotateParent', 0) != 0 && revision > 0
686 let revision = revision - 1
686 let revision = revision - 1
687 endif
687 endif
688 else
688 else
689 let revision=HGGetRevision()
689 let revision=HGGetRevision()
690 if revision == ""
690 if revision == ""
691 echoerr "Unable to obtain HG version information."
691 echoerr "Unable to obtain HG version information."
692 return -1
692 return -1
693 endif
693 endif
694 endif
694 endif
695 else
695 else
696 let revision=a:1
696 let revision=a:1
697 endif
697 endif
698
698
699 if revision == "NEW"
699 if revision == "NEW"
700 echo "No annotatation available for new file."
700 echo "No annotatation available for new file."
701 return -1
701 return -1
702 endif
702 endif
703
703
704 let resultBuffer=<SID>HGDoCommand('annotate -ndu -r ' . revision, 'hgannotate', revision)
704 let resultBuffer=<SID>HGDoCommand('annotate -ndu -r ' . revision, 'hgannotate', revision)
705 "echomsg "DBG: ".resultBuffer
705 "echomsg "DBG: ".resultBuffer
706 if resultBuffer != -1
706 if resultBuffer != -1
707 set filetype=HGAnnotate
707 set filetype=HGAnnotate
708 endif
708 endif
709
709
710 return resultBuffer
710 return resultBuffer
711 endfunction
711 endfunction
712
712
713 " Function: s:HGCommit() {{{2
713 " Function: s:HGCommit() {{{2
714 function! s:HGCommit(...)
714 function! s:HGCommit(...)
715 " Handle the commit message being specified. If a message is supplied, it
715 " Handle the commit message being specified. If a message is supplied, it
716 " is used; if bang is supplied, an empty message is used; otherwise, the
716 " is used; if bang is supplied, an empty message is used; otherwise, the
717 " user is provided a buffer from which to edit the commit message.
717 " user is provided a buffer from which to edit the commit message.
718 if a:2 != "" || a:1 == "!"
718 if a:2 != "" || a:1 == "!"
719 return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('commit -m "' . a:2 . '"', 'hgcommit', ''))
719 return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('commit -m "' . a:2 . '"', 'hgcommit', ''))
720 endif
720 endif
721
721
722 let hgBufferCheck=<SID>HGCurrentBufferCheck()
722 let hgBufferCheck=<SID>HGCurrentBufferCheck()
723 if hgBufferCheck == -1
723 if hgBufferCheck == -1
724 echo "Original buffer no longer exists, aborting."
724 echo "Original buffer no longer exists, aborting."
725 return -1
725 return -1
726 endif
726 endif
727
727
728 " Protect against windows' backslashes in paths. They confuse exec'd
728 " Protect against windows' backslashes in paths. They confuse exec'd
729 " commands.
729 " commands.
730
730
731 let shellSlashBak = &shellslash
731 let shellSlashBak = &shellslash
732 try
732 try
733 set shellslash
733 set shellslash
734
734
735 let messageFileName = tempname()
735 let messageFileName = tempname()
736
736
737 let fileName=bufname(hgBufferCheck)
737 let fileName=bufname(hgBufferCheck)
738 let realFilePath=<SID>HGResolveLink(fileName)
738 let realFilePath=<SID>HGResolveLink(fileName)
739 let newCwd=fnamemodify(realFilePath, ':h')
739 let newCwd=fnamemodify(realFilePath, ':h')
740 if strlen(newCwd) == 0
740 if strlen(newCwd) == 0
741 " Account for autochdir being in effect, which will make this blank, but
741 " Account for autochdir being in effect, which will make this blank, but
742 " we know we'll be in the current directory for the original file.
742 " we know we'll be in the current directory for the original file.
743 let newCwd = getcwd()
743 let newCwd = getcwd()
744 endif
744 endif
745
745
746 let realFileName=fnamemodify(realFilePath, ':t')
746 let realFileName=fnamemodify(realFilePath, ':t')
747
747
748 if <SID>HGEditFile(messageFileName, hgBufferCheck) == -1
748 if <SID>HGEditFile(messageFileName, hgBufferCheck) == -1
749 return
749 return
750 endif
750 endif
751
751
752 " Protect against case and backslash issues in Windows.
752 " Protect against case and backslash issues in Windows.
753 let autoPattern = '\c' . messageFileName
753 let autoPattern = '\c' . messageFileName
754
754
755 " Ensure existance of group
755 " Ensure existence of group
756 augroup HGCommit
756 augroup HGCommit
757 augroup END
757 augroup END
758
758
759 execute 'au HGCommit BufDelete' autoPattern 'call delete("' . messageFileName . '")'
759 execute 'au HGCommit BufDelete' autoPattern 'call delete("' . messageFileName . '")'
760 execute 'au HGCommit BufDelete' autoPattern 'au! HGCommit * ' autoPattern
760 execute 'au HGCommit BufDelete' autoPattern 'au! HGCommit * ' autoPattern
761
761
762 " Create a commit mapping. The mapping must clear all autocommands in case
762 " Create a commit mapping. The mapping must clear all autocommands in case
763 " it is invoked when HGCommandCommitOnWrite is active, as well as to not
763 " it is invoked when HGCommandCommitOnWrite is active, as well as to not
764 " invoke the buffer deletion autocommand.
764 " invoke the buffer deletion autocommand.
765
765
766 execute 'nnoremap <silent> <buffer> <Plug>HGCommit '.
766 execute 'nnoremap <silent> <buffer> <Plug>HGCommit '.
767 \ ':au! HGCommit * ' . autoPattern . '<CR>'.
767 \ ':au! HGCommit * ' . autoPattern . '<CR>'.
768 \ ':g/^HG:/d<CR>'.
768 \ ':g/^HG:/d<CR>'.
769 \ ':update<CR>'.
769 \ ':update<CR>'.
770 \ ':call <SID>HGFinishCommit("' . messageFileName . '",' .
770 \ ':call <SID>HGFinishCommit("' . messageFileName . '",' .
771 \ '"' . newCwd . '",' .
771 \ '"' . newCwd . '",' .
772 \ '"' . realFileName . '",' .
772 \ '"' . realFileName . '",' .
773 \ hgBufferCheck . ')<CR>'
773 \ hgBufferCheck . ')<CR>'
774
774
775 silent 0put ='HG: ----------------------------------------------------------------------'
775 silent 0put ='HG: ----------------------------------------------------------------------'
776 silent put =\"HG: Enter Log. Lines beginning with `HG:' are removed automatically\"
776 silent put =\"HG: Enter Log. Lines beginning with `HG:' are removed automatically\"
777 silent put ='HG: Type <leader>cc (or your own <Plug>HGCommit mapping)'
777 silent put ='HG: Type <leader>cc (or your own <Plug>HGCommit mapping)'
778
778
779 if <SID>HGGetOption('HGCommandCommitOnWrite', 1) == 1
779 if <SID>HGGetOption('HGCommandCommitOnWrite', 1) == 1
780 execute 'au HGCommit BufWritePre' autoPattern 'g/^HG:/d'
780 execute 'au HGCommit BufWritePre' autoPattern 'g/^HG:/d'
781 execute 'au HGCommit BufWritePost' autoPattern 'call <SID>HGFinishCommit("' . messageFileName . '", "' . newCwd . '", "' . realFileName . '", ' . hgBufferCheck . ') | au! * ' autoPattern
781 execute 'au HGCommit BufWritePost' autoPattern 'call <SID>HGFinishCommit("' . messageFileName . '", "' . newCwd . '", "' . realFileName . '", ' . hgBufferCheck . ') | au! * ' autoPattern
782 silent put ='HG: or write this buffer'
782 silent put ='HG: or write this buffer'
783 endif
783 endif
784
784
785 silent put ='HG: to finish this commit operation'
785 silent put ='HG: to finish this commit operation'
786 silent put ='HG: ----------------------------------------------------------------------'
786 silent put ='HG: ----------------------------------------------------------------------'
787 $
787 $
788 let b:HGSourceFile=fileName
788 let b:HGSourceFile=fileName
789 let b:HGCommand='HGCommit'
789 let b:HGCommand='HGCommit'
790 set filetype=hg
790 set filetype=hg
791 finally
791 finally
792 let &shellslash = shellSlashBak
792 let &shellslash = shellSlashBak
793 endtry
793 endtry
794
794
795 endfunction
795 endfunction
796
796
797 " Function: s:HGDiff(...) {{{2
797 " Function: s:HGDiff(...) {{{2
798 function! s:HGDiff(...)
798 function! s:HGDiff(...)
799 if a:0 == 1
799 if a:0 == 1
800 let revOptions = '-r' . a:1
800 let revOptions = '-r' . a:1
801 let caption = a:1 . ' -> current'
801 let caption = a:1 . ' -> current'
802 elseif a:0 == 2
802 elseif a:0 == 2
803 let revOptions = '-r' . a:1 . ' -r' . a:2
803 let revOptions = '-r' . a:1 . ' -r' . a:2
804 let caption = a:1 . ' -> ' . a:2
804 let caption = a:1 . ' -> ' . a:2
805 else
805 else
806 let revOptions = ''
806 let revOptions = ''
807 let caption = ''
807 let caption = ''
808 endif
808 endif
809
809
810 let hgdiffopt=<SID>HGGetOption('HGCommandDiffOpt', 'w')
810 let hgdiffopt=<SID>HGGetOption('HGCommandDiffOpt', 'w')
811
811
812 if hgdiffopt == ""
812 if hgdiffopt == ""
813 let diffoptionstring=""
813 let diffoptionstring=""
814 else
814 else
815 let diffoptionstring=" -" . hgdiffopt . " "
815 let diffoptionstring=" -" . hgdiffopt . " "
816 endif
816 endif
817
817
818 let resultBuffer = <SID>HGDoCommand('diff ' . diffoptionstring . revOptions , 'hgdiff', caption)
818 let resultBuffer = <SID>HGDoCommand('diff ' . diffoptionstring . revOptions , 'hgdiff', caption)
819 if resultBuffer != -1
819 if resultBuffer != -1
820 set filetype=diff
820 set filetype=diff
821 endif
821 endif
822 return resultBuffer
822 return resultBuffer
823 endfunction
823 endfunction
824
824
825
825
826 " Function: s:HGGotoOriginal(["!]) {{{2
826 " Function: s:HGGotoOriginal(["!]) {{{2
827 function! s:HGGotoOriginal(...)
827 function! s:HGGotoOriginal(...)
828 let origBuffNR = <SID>HGCurrentBufferCheck()
828 let origBuffNR = <SID>HGCurrentBufferCheck()
829 if origBuffNR > 0
829 if origBuffNR > 0
830 let origWinNR = bufwinnr(origBuffNR)
830 let origWinNR = bufwinnr(origBuffNR)
831 if origWinNR == -1
831 if origWinNR == -1
832 execute 'buffer' origBuffNR
832 execute 'buffer' origBuffNR
833 else
833 else
834 execute origWinNR . 'wincmd w'
834 execute origWinNR . 'wincmd w'
835 endif
835 endif
836 if a:0 == 1
836 if a:0 == 1
837 if a:1 == "!"
837 if a:1 == "!"
838 let buffnr = 1
838 let buffnr = 1
839 let buffmaxnr = bufnr("$")
839 let buffmaxnr = bufnr("$")
840 while buffnr <= buffmaxnr
840 while buffnr <= buffmaxnr
841 if getbufvar(buffnr, "HGOrigBuffNR") == origBuffNR
841 if getbufvar(buffnr, "HGOrigBuffNR") == origBuffNR
842 execute "bw" buffnr
842 execute "bw" buffnr
843 endif
843 endif
844 let buffnr = buffnr + 1
844 let buffnr = buffnr + 1
845 endwhile
845 endwhile
846 endif
846 endif
847 endif
847 endif
848 endif
848 endif
849 endfunction
849 endfunction
850
850
851 " Function: s:HGFinishCommit(messageFile, targetDir, targetFile) {{{2
851 " Function: s:HGFinishCommit(messageFile, targetDir, targetFile) {{{2
852 function! s:HGFinishCommit(messageFile, targetDir, targetFile, origBuffNR)
852 function! s:HGFinishCommit(messageFile, targetDir, targetFile, origBuffNR)
853 if filereadable(a:messageFile)
853 if filereadable(a:messageFile)
854 let oldCwd=getcwd()
854 let oldCwd=getcwd()
855 if strlen(a:targetDir) > 0
855 if strlen(a:targetDir) > 0
856 execute 'cd' escape(a:targetDir, ' ')
856 execute 'cd' escape(a:targetDir, ' ')
857 endif
857 endif
858 let resultBuffer=<SID>HGCreateCommandBuffer('commit -l "' . a:messageFile . '" "'. a:targetFile . '"', 'hgcommit', '', a:origBuffNR)
858 let resultBuffer=<SID>HGCreateCommandBuffer('commit -l "' . a:messageFile . '" "'. a:targetFile . '"', 'hgcommit', '', a:origBuffNR)
859 execute 'cd' escape(oldCwd, ' ')
859 execute 'cd' escape(oldCwd, ' ')
860 execute 'bw' escape(a:messageFile, ' *?\')
860 execute 'bw' escape(a:messageFile, ' *?\')
861 silent execute 'call delete("' . a:messageFile . '")'
861 silent execute 'call delete("' . a:messageFile . '")'
862 return <SID>HGMarkOrigBufferForSetup(resultBuffer)
862 return <SID>HGMarkOrigBufferForSetup(resultBuffer)
863 else
863 else
864 echoerr "Can't read message file; no commit is possible."
864 echoerr "Can't read message file; no commit is possible."
865 return -1
865 return -1
866 endif
866 endif
867 endfunction
867 endfunction
868
868
869 " Function: s:HGLog() {{{2
869 " Function: s:HGLog() {{{2
870 function! s:HGLog(...)
870 function! s:HGLog(...)
871 if a:0 == 0
871 if a:0 == 0
872 let versionOption = ""
872 let versionOption = ""
873 let caption = ''
873 let caption = ''
874 else
874 else
875 let versionOption=" -r" . a:1
875 let versionOption=" -r" . a:1
876 let caption = a:1
876 let caption = a:1
877 endif
877 endif
878
878
879 let resultBuffer=<SID>HGDoCommand('log' . versionOption, 'hglog', caption)
879 let resultBuffer=<SID>HGDoCommand('log' . versionOption, 'hglog', caption)
880 if resultBuffer != ""
880 if resultBuffer != ""
881 set filetype=rcslog
881 set filetype=rcslog
882 endif
882 endif
883 return resultBuffer
883 return resultBuffer
884 endfunction
884 endfunction
885
885
886 " Function: s:HGRevert() {{{2
886 " Function: s:HGRevert() {{{2
887 function! s:HGRevert()
887 function! s:HGRevert()
888 return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('revert', 'hgrevert', ''))
888 return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('revert', 'hgrevert', ''))
889 endfunction
889 endfunction
890
890
891 " Function: s:HGReview(...) {{{2
891 " Function: s:HGReview(...) {{{2
892 function! s:HGReview(...)
892 function! s:HGReview(...)
893 if a:0 == 0
893 if a:0 == 0
894 let versiontag=""
894 let versiontag=""
895 if <SID>HGGetOption('HGCommandInteractive', 0)
895 if <SID>HGGetOption('HGCommandInteractive', 0)
896 let versiontag=input('Revision: ')
896 let versiontag=input('Revision: ')
897 endif
897 endif
898 if versiontag == ""
898 if versiontag == ""
899 let versiontag="(current)"
899 let versiontag="(current)"
900 let versionOption=""
900 let versionOption=""
901 else
901 else
902 let versionOption=" -r " . versiontag . " "
902 let versionOption=" -r " . versiontag . " "
903 endif
903 endif
904 else
904 else
905 let versiontag=a:1
905 let versiontag=a:1
906 let versionOption=" -r " . versiontag . " "
906 let versionOption=" -r " . versiontag . " "
907 endif
907 endif
908
908
909 let resultBuffer = <SID>HGDoCommand('cat' . versionOption, 'hgreview', versiontag)
909 let resultBuffer = <SID>HGDoCommand('cat' . versionOption, 'hgreview', versiontag)
910 if resultBuffer > 0
910 if resultBuffer > 0
911 let &filetype=getbufvar(b:HGOrigBuffNR, '&filetype')
911 let &filetype=getbufvar(b:HGOrigBuffNR, '&filetype')
912 endif
912 endif
913
913
914 return resultBuffer
914 return resultBuffer
915 endfunction
915 endfunction
916
916
917 " Function: s:HGStatus() {{{2
917 " Function: s:HGStatus() {{{2
918 function! s:HGStatus()
918 function! s:HGStatus()
919 return <SID>HGDoCommand('status', 'hgstatus', '')
919 return <SID>HGDoCommand('status', 'hgstatus', '')
920 endfunction
920 endfunction
921
921
922
922
923 " Function: s:HGUpdate() {{{2
923 " Function: s:HGUpdate() {{{2
924 function! s:HGUpdate()
924 function! s:HGUpdate()
925 return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('update', 'update', ''))
925 return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('update', 'update', ''))
926 endfunction
926 endfunction
927
927
928 " Function: s:HGVimDiff(...) {{{2
928 " Function: s:HGVimDiff(...) {{{2
929 function! s:HGVimDiff(...)
929 function! s:HGVimDiff(...)
930 let originalBuffer = <SID>HGCurrentBufferCheck()
930 let originalBuffer = <SID>HGCurrentBufferCheck()
931 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning + 1
931 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning + 1
932 try
932 try
933 " If there's already a VimDiff'ed window, restore it.
933 " If there's already a VimDiff'ed window, restore it.
934 " There may only be one HGVimDiff original window at a time.
934 " There may only be one HGVimDiff original window at a time.
935
935
936 if exists("s:vimDiffSourceBuffer") && s:vimDiffSourceBuffer != originalBuffer
936 if exists("s:vimDiffSourceBuffer") && s:vimDiffSourceBuffer != originalBuffer
937 " Clear the existing vimdiff setup by removing the result buffers.
937 " Clear the existing vimdiff setup by removing the result buffers.
938 call <SID>HGWipeoutCommandBuffers(s:vimDiffSourceBuffer, 'vimdiff')
938 call <SID>HGWipeoutCommandBuffers(s:vimDiffSourceBuffer, 'vimdiff')
939 endif
939 endif
940
940
941 " Split and diff
941 " Split and diff
942 if(a:0 == 2)
942 if(a:0 == 2)
943 " Reset the vimdiff system, as 2 explicit versions were provided.
943 " Reset the vimdiff system, as 2 explicit versions were provided.
944 if exists('s:vimDiffSourceBuffer')
944 if exists('s:vimDiffSourceBuffer')
945 call <SID>HGWipeoutCommandBuffers(s:vimDiffSourceBuffer, 'vimdiff')
945 call <SID>HGWipeoutCommandBuffers(s:vimDiffSourceBuffer, 'vimdiff')
946 endif
946 endif
947 let resultBuffer = <SID>HGReview(a:1)
947 let resultBuffer = <SID>HGReview(a:1)
948 if resultBuffer < 0
948 if resultBuffer < 0
949 echomsg "Can't open HG revision " . a:1
949 echomsg "Can't open HG revision " . a:1
950 return resultBuffer
950 return resultBuffer
951 endif
951 endif
952 let b:HGCommand = 'vimdiff'
952 let b:HGCommand = 'vimdiff'
953 diffthis
953 diffthis
954 let s:vimDiffBufferCount = 1
954 let s:vimDiffBufferCount = 1
955 let s:vimDiffScratchList = '{'. resultBuffer . '}'
955 let s:vimDiffScratchList = '{'. resultBuffer . '}'
956 " If no split method is defined, cheat, and set it to vertical.
956 " If no split method is defined, cheat, and set it to vertical.
957 try
957 try
958 call <SID>HGOverrideOption('HGCommandSplit', <SID>HGGetOption('HGCommandDiffSplit', <SID>HGGetOption('HGCommandSplit', 'vertical')))
958 call <SID>HGOverrideOption('HGCommandSplit', <SID>HGGetOption('HGCommandDiffSplit', <SID>HGGetOption('HGCommandSplit', 'vertical')))
959 let resultBuffer=<SID>HGReview(a:2)
959 let resultBuffer=<SID>HGReview(a:2)
960 finally
960 finally
961 call <SID>HGOverrideOption('HGCommandSplit')
961 call <SID>HGOverrideOption('HGCommandSplit')
962 endtry
962 endtry
963 if resultBuffer < 0
963 if resultBuffer < 0
964 echomsg "Can't open HG revision " . a:1
964 echomsg "Can't open HG revision " . a:1
965 return resultBuffer
965 return resultBuffer
966 endif
966 endif
967 let b:HGCommand = 'vimdiff'
967 let b:HGCommand = 'vimdiff'
968 diffthis
968 diffthis
969 let s:vimDiffBufferCount = 2
969 let s:vimDiffBufferCount = 2
970 let s:vimDiffScratchList = s:vimDiffScratchList . '{'. resultBuffer . '}'
970 let s:vimDiffScratchList = s:vimDiffScratchList . '{'. resultBuffer . '}'
971 else
971 else
972 " Add new buffer
972 " Add new buffer
973 try
973 try
974 " Force splitting behavior, otherwise why use vimdiff?
974 " Force splitting behavior, otherwise why use vimdiff?
975 call <SID>HGOverrideOption("HGCommandEdit", "split")
975 call <SID>HGOverrideOption("HGCommandEdit", "split")
976 call <SID>HGOverrideOption("HGCommandSplit", <SID>HGGetOption('HGCommandDiffSplit', <SID>HGGetOption('HGCommandSplit', 'vertical')))
976 call <SID>HGOverrideOption("HGCommandSplit", <SID>HGGetOption('HGCommandDiffSplit', <SID>HGGetOption('HGCommandSplit', 'vertical')))
977 if(a:0 == 0)
977 if(a:0 == 0)
978 let resultBuffer=<SID>HGReview()
978 let resultBuffer=<SID>HGReview()
979 else
979 else
980 let resultBuffer=<SID>HGReview(a:1)
980 let resultBuffer=<SID>HGReview(a:1)
981 endif
981 endif
982 finally
982 finally
983 call <SID>HGOverrideOption("HGCommandEdit")
983 call <SID>HGOverrideOption("HGCommandEdit")
984 call <SID>HGOverrideOption("HGCommandSplit")
984 call <SID>HGOverrideOption("HGCommandSplit")
985 endtry
985 endtry
986 if resultBuffer < 0
986 if resultBuffer < 0
987 echomsg "Can't open current HG revision"
987 echomsg "Can't open current HG revision"
988 return resultBuffer
988 return resultBuffer
989 endif
989 endif
990 let b:HGCommand = 'vimdiff'
990 let b:HGCommand = 'vimdiff'
991 diffthis
991 diffthis
992
992
993 if !exists('s:vimDiffBufferCount')
993 if !exists('s:vimDiffBufferCount')
994 " New instance of vimdiff.
994 " New instance of vimdiff.
995 let s:vimDiffBufferCount = 2
995 let s:vimDiffBufferCount = 2
996 let s:vimDiffScratchList = '{' . resultBuffer . '}'
996 let s:vimDiffScratchList = '{' . resultBuffer . '}'
997
997
998 " This could have been invoked on a HG result buffer, not the
998 " This could have been invoked on a HG result buffer, not the
999 " original buffer.
999 " original buffer.
1000 wincmd W
1000 wincmd W
1001 execute 'buffer' originalBuffer
1001 execute 'buffer' originalBuffer
1002 " Store info for later original buffer restore
1002 " Store info for later original buffer restore
1003 let s:vimDiffRestoreCmd =
1003 let s:vimDiffRestoreCmd =
1004 \ "call setbufvar(".originalBuffer.", \"&diff\", ".getbufvar(originalBuffer, '&diff').")"
1004 \ "call setbufvar(".originalBuffer.", \"&diff\", ".getbufvar(originalBuffer, '&diff').")"
1005 \ . "|call setbufvar(".originalBuffer.", \"&foldcolumn\", ".getbufvar(originalBuffer, '&foldcolumn').")"
1005 \ . "|call setbufvar(".originalBuffer.", \"&foldcolumn\", ".getbufvar(originalBuffer, '&foldcolumn').")"
1006 \ . "|call setbufvar(".originalBuffer.", \"&foldenable\", ".getbufvar(originalBuffer, '&foldenable').")"
1006 \ . "|call setbufvar(".originalBuffer.", \"&foldenable\", ".getbufvar(originalBuffer, '&foldenable').")"
1007 \ . "|call setbufvar(".originalBuffer.", \"&foldmethod\", '".getbufvar(originalBuffer, '&foldmethod')."')"
1007 \ . "|call setbufvar(".originalBuffer.", \"&foldmethod\", '".getbufvar(originalBuffer, '&foldmethod')."')"
1008 \ . "|call setbufvar(".originalBuffer.", \"&scrollbind\", ".getbufvar(originalBuffer, '&scrollbind').")"
1008 \ . "|call setbufvar(".originalBuffer.", \"&scrollbind\", ".getbufvar(originalBuffer, '&scrollbind').")"
1009 \ . "|call setbufvar(".originalBuffer.", \"&wrap\", ".getbufvar(originalBuffer, '&wrap').")"
1009 \ . "|call setbufvar(".originalBuffer.", \"&wrap\", ".getbufvar(originalBuffer, '&wrap').")"
1010 \ . "|if &foldmethod=='manual'|execute 'normal! zE'|endif"
1010 \ . "|if &foldmethod=='manual'|execute 'normal! zE'|endif"
1011 diffthis
1011 diffthis
1012 wincmd w
1012 wincmd w
1013 else
1013 else
1014 " Adding a window to an existing vimdiff
1014 " Adding a window to an existing vimdiff
1015 let s:vimDiffBufferCount = s:vimDiffBufferCount + 1
1015 let s:vimDiffBufferCount = s:vimDiffBufferCount + 1
1016 let s:vimDiffScratchList = s:vimDiffScratchList . '{' . resultBuffer . '}'
1016 let s:vimDiffScratchList = s:vimDiffScratchList . '{' . resultBuffer . '}'
1017 endif
1017 endif
1018 endif
1018 endif
1019
1019
1020 let s:vimDiffSourceBuffer = originalBuffer
1020 let s:vimDiffSourceBuffer = originalBuffer
1021
1021
1022 " Avoid executing the modeline in the current buffer after the autocommand.
1022 " Avoid executing the modeline in the current buffer after the autocommand.
1023
1023
1024 let currentBuffer = bufnr('%')
1024 let currentBuffer = bufnr('%')
1025 let saveModeline = getbufvar(currentBuffer, '&modeline')
1025 let saveModeline = getbufvar(currentBuffer, '&modeline')
1026 try
1026 try
1027 call setbufvar(currentBuffer, '&modeline', 0)
1027 call setbufvar(currentBuffer, '&modeline', 0)
1028 silent do HGCommand User HGVimDiffFinish
1028 silent do HGCommand User HGVimDiffFinish
1029 finally
1029 finally
1030 call setbufvar(currentBuffer, '&modeline', saveModeline)
1030 call setbufvar(currentBuffer, '&modeline', saveModeline)
1031 endtry
1031 endtry
1032 return resultBuffer
1032 return resultBuffer
1033 finally
1033 finally
1034 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning - 1
1034 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning - 1
1035 endtry
1035 endtry
1036 endfunction
1036 endfunction
1037
1037
1038 " Section: Command definitions {{{1
1038 " Section: Command definitions {{{1
1039 " Section: Primary commands {{{2
1039 " Section: Primary commands {{{2
1040 com! HGAdd call <SID>HGAdd()
1040 com! HGAdd call <SID>HGAdd()
1041 com! -nargs=? HGAnnotate call <SID>HGAnnotate(<f-args>)
1041 com! -nargs=? HGAnnotate call <SID>HGAnnotate(<f-args>)
1042 com! -bang -nargs=? HGCommit call <SID>HGCommit(<q-bang>, <q-args>)
1042 com! -bang -nargs=? HGCommit call <SID>HGCommit(<q-bang>, <q-args>)
1043 com! -nargs=* HGDiff call <SID>HGDiff(<f-args>)
1043 com! -nargs=* HGDiff call <SID>HGDiff(<f-args>)
1044 com! -bang HGGotoOriginal call <SID>HGGotoOriginal(<q-bang>)
1044 com! -bang HGGotoOriginal call <SID>HGGotoOriginal(<q-bang>)
1045 com! -nargs=? HGLog call <SID>HGLog(<f-args>)
1045 com! -nargs=? HGLog call <SID>HGLog(<f-args>)
1046 com! HGRevert call <SID>HGRevert()
1046 com! HGRevert call <SID>HGRevert()
1047 com! -nargs=? HGReview call <SID>HGReview(<f-args>)
1047 com! -nargs=? HGReview call <SID>HGReview(<f-args>)
1048 com! HGStatus call <SID>HGStatus()
1048 com! HGStatus call <SID>HGStatus()
1049 com! HGUpdate call <SID>HGUpdate()
1049 com! HGUpdate call <SID>HGUpdate()
1050 com! -nargs=* HGVimDiff call <SID>HGVimDiff(<f-args>)
1050 com! -nargs=* HGVimDiff call <SID>HGVimDiff(<f-args>)
1051
1051
1052 " Section: HG buffer management commands {{{2
1052 " Section: HG buffer management commands {{{2
1053 com! HGDisableBufferSetup call HGDisableBufferSetup()
1053 com! HGDisableBufferSetup call HGDisableBufferSetup()
1054 com! HGEnableBufferSetup call HGEnableBufferSetup()
1054 com! HGEnableBufferSetup call HGEnableBufferSetup()
1055
1055
1056 " Allow reloading hgcommand.vim
1056 " Allow reloading hgcommand.vim
1057 com! HGReload unlet! g:loaded_hgcommand | runtime plugin/hgcommand.vim
1057 com! HGReload unlet! g:loaded_hgcommand | runtime plugin/hgcommand.vim
1058
1058
1059 " Section: Plugin command mappings {{{1
1059 " Section: Plugin command mappings {{{1
1060 nnoremap <silent> <Plug>HGAdd :HGAdd<CR>
1060 nnoremap <silent> <Plug>HGAdd :HGAdd<CR>
1061 nnoremap <silent> <Plug>HGAnnotate :HGAnnotate<CR>
1061 nnoremap <silent> <Plug>HGAnnotate :HGAnnotate<CR>
1062 nnoremap <silent> <Plug>HGCommit :HGCommit<CR>
1062 nnoremap <silent> <Plug>HGCommit :HGCommit<CR>
1063 nnoremap <silent> <Plug>HGDiff :HGDiff<CR>
1063 nnoremap <silent> <Plug>HGDiff :HGDiff<CR>
1064 nnoremap <silent> <Plug>HGGotoOriginal :HGGotoOriginal<CR>
1064 nnoremap <silent> <Plug>HGGotoOriginal :HGGotoOriginal<CR>
1065 nnoremap <silent> <Plug>HGClearAndGotoOriginal :HGGotoOriginal!<CR>
1065 nnoremap <silent> <Plug>HGClearAndGotoOriginal :HGGotoOriginal!<CR>
1066 nnoremap <silent> <Plug>HGLog :HGLog<CR>
1066 nnoremap <silent> <Plug>HGLog :HGLog<CR>
1067 nnoremap <silent> <Plug>HGRevert :HGRevert<CR>
1067 nnoremap <silent> <Plug>HGRevert :HGRevert<CR>
1068 nnoremap <silent> <Plug>HGReview :HGReview<CR>
1068 nnoremap <silent> <Plug>HGReview :HGReview<CR>
1069 nnoremap <silent> <Plug>HGStatus :HGStatus<CR>
1069 nnoremap <silent> <Plug>HGStatus :HGStatus<CR>
1070 nnoremap <silent> <Plug>HGUpdate :HGUpdate<CR>
1070 nnoremap <silent> <Plug>HGUpdate :HGUpdate<CR>
1071 nnoremap <silent> <Plug>HGVimDiff :HGVimDiff<CR>
1071 nnoremap <silent> <Plug>HGVimDiff :HGVimDiff<CR>
1072
1072
1073 " Section: Default mappings {{{1
1073 " Section: Default mappings {{{1
1074 if !hasmapto('<Plug>HGAdd')
1074 if !hasmapto('<Plug>HGAdd')
1075 nmap <unique> <Leader>hga <Plug>HGAdd
1075 nmap <unique> <Leader>hga <Plug>HGAdd
1076 endif
1076 endif
1077 if !hasmapto('<Plug>HGAnnotate')
1077 if !hasmapto('<Plug>HGAnnotate')
1078 nmap <unique> <Leader>hgn <Plug>HGAnnotate
1078 nmap <unique> <Leader>hgn <Plug>HGAnnotate
1079 endif
1079 endif
1080 if !hasmapto('<Plug>HGClearAndGotoOriginal')
1080 if !hasmapto('<Plug>HGClearAndGotoOriginal')
1081 nmap <unique> <Leader>hgG <Plug>HGClearAndGotoOriginal
1081 nmap <unique> <Leader>hgG <Plug>HGClearAndGotoOriginal
1082 endif
1082 endif
1083 if !hasmapto('<Plug>HGCommit')
1083 if !hasmapto('<Plug>HGCommit')
1084 nmap <unique> <Leader>hgc <Plug>HGCommit
1084 nmap <unique> <Leader>hgc <Plug>HGCommit
1085 endif
1085 endif
1086 if !hasmapto('<Plug>HGDiff')
1086 if !hasmapto('<Plug>HGDiff')
1087 nmap <unique> <Leader>hgd <Plug>HGDiff
1087 nmap <unique> <Leader>hgd <Plug>HGDiff
1088 endif
1088 endif
1089 if !hasmapto('<Plug>HGGotoOriginal')
1089 if !hasmapto('<Plug>HGGotoOriginal')
1090 nmap <unique> <Leader>hgg <Plug>HGGotoOriginal
1090 nmap <unique> <Leader>hgg <Plug>HGGotoOriginal
1091 endif
1091 endif
1092 if !hasmapto('<Plug>HGLog')
1092 if !hasmapto('<Plug>HGLog')
1093 nmap <unique> <Leader>hgl <Plug>HGLog
1093 nmap <unique> <Leader>hgl <Plug>HGLog
1094 endif
1094 endif
1095 if !hasmapto('<Plug>HGRevert')
1095 if !hasmapto('<Plug>HGRevert')
1096 nmap <unique> <Leader>hgq <Plug>HGRevert
1096 nmap <unique> <Leader>hgq <Plug>HGRevert
1097 endif
1097 endif
1098 if !hasmapto('<Plug>HGReview')
1098 if !hasmapto('<Plug>HGReview')
1099 nmap <unique> <Leader>hgr <Plug>HGReview
1099 nmap <unique> <Leader>hgr <Plug>HGReview
1100 endif
1100 endif
1101 if !hasmapto('<Plug>HGStatus')
1101 if !hasmapto('<Plug>HGStatus')
1102 nmap <unique> <Leader>hgs <Plug>HGStatus
1102 nmap <unique> <Leader>hgs <Plug>HGStatus
1103 endif
1103 endif
1104 if !hasmapto('<Plug>HGUpdate')
1104 if !hasmapto('<Plug>HGUpdate')
1105 nmap <unique> <Leader>hgu <Plug>HGUpdate
1105 nmap <unique> <Leader>hgu <Plug>HGUpdate
1106 endif
1106 endif
1107 if !hasmapto('<Plug>HGVimDiff')
1107 if !hasmapto('<Plug>HGVimDiff')
1108 nmap <unique> <Leader>hgv <Plug>HGVimDiff
1108 nmap <unique> <Leader>hgv <Plug>HGVimDiff
1109 endif
1109 endif
1110
1110
1111 " Section: Menu items {{{1
1111 " Section: Menu items {{{1
1112 silent! aunmenu Plugin.HG
1112 silent! aunmenu Plugin.HG
1113 amenu <silent> &Plugin.HG.&Add <Plug>HGAdd
1113 amenu <silent> &Plugin.HG.&Add <Plug>HGAdd
1114 amenu <silent> &Plugin.HG.A&nnotate <Plug>HGAnnotate
1114 amenu <silent> &Plugin.HG.A&nnotate <Plug>HGAnnotate
1115 amenu <silent> &Plugin.HG.&Commit <Plug>HGCommit
1115 amenu <silent> &Plugin.HG.&Commit <Plug>HGCommit
1116 amenu <silent> &Plugin.HG.&Diff <Plug>HGDiff
1116 amenu <silent> &Plugin.HG.&Diff <Plug>HGDiff
1117 amenu <silent> &Plugin.HG.&Log <Plug>HGLog
1117 amenu <silent> &Plugin.HG.&Log <Plug>HGLog
1118 amenu <silent> &Plugin.HG.Revert <Plug>HGRevert
1118 amenu <silent> &Plugin.HG.Revert <Plug>HGRevert
1119 amenu <silent> &Plugin.HG.&Review <Plug>HGReview
1119 amenu <silent> &Plugin.HG.&Review <Plug>HGReview
1120 amenu <silent> &Plugin.HG.&Status <Plug>HGStatus
1120 amenu <silent> &Plugin.HG.&Status <Plug>HGStatus
1121 amenu <silent> &Plugin.HG.&Update <Plug>HGUpdate
1121 amenu <silent> &Plugin.HG.&Update <Plug>HGUpdate
1122 amenu <silent> &Plugin.HG.&VimDiff <Plug>HGVimDiff
1122 amenu <silent> &Plugin.HG.&VimDiff <Plug>HGVimDiff
1123
1123
1124 " Section: Autocommands to restore vimdiff state {{{1
1124 " Section: Autocommands to restore vimdiff state {{{1
1125 function! s:HGVimDiffRestore(vimDiffBuff)
1125 function! s:HGVimDiffRestore(vimDiffBuff)
1126 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning + 1
1126 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning + 1
1127 try
1127 try
1128 if exists("s:vimDiffSourceBuffer")
1128 if exists("s:vimDiffSourceBuffer")
1129 if a:vimDiffBuff == s:vimDiffSourceBuffer
1129 if a:vimDiffBuff == s:vimDiffSourceBuffer
1130 " Original file is being removed.
1130 " Original file is being removed.
1131 unlet! s:vimDiffSourceBuffer
1131 unlet! s:vimDiffSourceBuffer
1132 unlet! s:vimDiffBufferCount
1132 unlet! s:vimDiffBufferCount
1133 unlet! s:vimDiffRestoreCmd
1133 unlet! s:vimDiffRestoreCmd
1134 unlet! s:vimDiffScratchList
1134 unlet! s:vimDiffScratchList
1135 elseif match(s:vimDiffScratchList, '{' . a:vimDiffBuff . '}') >= 0
1135 elseif match(s:vimDiffScratchList, '{' . a:vimDiffBuff . '}') >= 0
1136 let s:vimDiffScratchList = substitute(s:vimDiffScratchList, '{' . a:vimDiffBuff . '}', '', '')
1136 let s:vimDiffScratchList = substitute(s:vimDiffScratchList, '{' . a:vimDiffBuff . '}', '', '')
1137 let s:vimDiffBufferCount = s:vimDiffBufferCount - 1
1137 let s:vimDiffBufferCount = s:vimDiffBufferCount - 1
1138 if s:vimDiffBufferCount == 1 && exists('s:vimDiffRestoreCmd')
1138 if s:vimDiffBufferCount == 1 && exists('s:vimDiffRestoreCmd')
1139 " All scratch buffers are gone, reset the original.
1139 " All scratch buffers are gone, reset the original.
1140 " Only restore if the source buffer is still in Diff mode
1140 " Only restore if the source buffer is still in Diff mode
1141
1141
1142 let sourceWinNR=bufwinnr(s:vimDiffSourceBuffer)
1142 let sourceWinNR=bufwinnr(s:vimDiffSourceBuffer)
1143 if sourceWinNR != -1
1143 if sourceWinNR != -1
1144 " The buffer is visible in at least one window
1144 " The buffer is visible in at least one window
1145 let currentWinNR = winnr()
1145 let currentWinNR = winnr()
1146 while winbufnr(sourceWinNR) != -1
1146 while winbufnr(sourceWinNR) != -1
1147 if winbufnr(sourceWinNR) == s:vimDiffSourceBuffer
1147 if winbufnr(sourceWinNR) == s:vimDiffSourceBuffer
1148 execute sourceWinNR . 'wincmd w'
1148 execute sourceWinNR . 'wincmd w'
1149 if getwinvar('', "&diff")
1149 if getwinvar('', "&diff")
1150 execute s:vimDiffRestoreCmd
1150 execute s:vimDiffRestoreCmd
1151 endif
1151 endif
1152 endif
1152 endif
1153 let sourceWinNR = sourceWinNR + 1
1153 let sourceWinNR = sourceWinNR + 1
1154 endwhile
1154 endwhile
1155 execute currentWinNR . 'wincmd w'
1155 execute currentWinNR . 'wincmd w'
1156 else
1156 else
1157 " The buffer is hidden. It must be visible in order to set the
1157 " The buffer is hidden. It must be visible in order to set the
1158 " diff option.
1158 " diff option.
1159 let currentBufNR = bufnr('')
1159 let currentBufNR = bufnr('')
1160 execute "hide buffer" s:vimDiffSourceBuffer
1160 execute "hide buffer" s:vimDiffSourceBuffer
1161 if getwinvar('', "&diff")
1161 if getwinvar('', "&diff")
1162 execute s:vimDiffRestoreCmd
1162 execute s:vimDiffRestoreCmd
1163 endif
1163 endif
1164 execute "hide buffer" currentBufNR
1164 execute "hide buffer" currentBufNR
1165 endif
1165 endif
1166
1166
1167 unlet s:vimDiffRestoreCmd
1167 unlet s:vimDiffRestoreCmd
1168 unlet s:vimDiffSourceBuffer
1168 unlet s:vimDiffSourceBuffer
1169 unlet s:vimDiffBufferCount
1169 unlet s:vimDiffBufferCount
1170 unlet s:vimDiffScratchList
1170 unlet s:vimDiffScratchList
1171 elseif s:vimDiffBufferCount == 0
1171 elseif s:vimDiffBufferCount == 0
1172 " All buffers are gone.
1172 " All buffers are gone.
1173 unlet s:vimDiffSourceBuffer
1173 unlet s:vimDiffSourceBuffer
1174 unlet s:vimDiffBufferCount
1174 unlet s:vimDiffBufferCount
1175 unlet s:vimDiffScratchList
1175 unlet s:vimDiffScratchList
1176 endif
1176 endif
1177 endif
1177 endif
1178 endif
1178 endif
1179 finally
1179 finally
1180 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning - 1
1180 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning - 1
1181 endtry
1181 endtry
1182 endfunction
1182 endfunction
1183
1183
1184 augroup HGVimDiffRestore
1184 augroup HGVimDiffRestore
1185 au!
1185 au!
1186 au BufUnload * call <SID>HGVimDiffRestore(expand("<abuf>"))
1186 au BufUnload * call <SID>HGVimDiffRestore(expand("<abuf>"))
1187 augroup END
1187 augroup END
1188
1188
1189 " Section: Optional activation of buffer management {{{1
1189 " Section: Optional activation of buffer management {{{1
1190
1190
1191 if s:HGGetOption('HGCommandEnableBufferSetup', 1)
1191 if s:HGGetOption('HGCommandEnableBufferSetup', 1)
1192 call HGEnableBufferSetup()
1192 call HGEnableBufferSetup()
1193 endif
1193 endif
1194
1194
1195 " Section: Doc installation {{{1
1195 " Section: Doc installation {{{1
1196
1196
1197 if <SID>HGInstallDocumentation(expand("<sfile>:p"))
1197 if <SID>HGInstallDocumentation(expand("<sfile>:p"))
1198 echomsg s:script_name s:script_version . ": updated documentation"
1198 echomsg s:script_name s:script_version . ": updated documentation"
1199 endif
1199 endif
1200
1200
1201 " Section: Plugin completion {{{1
1201 " Section: Plugin completion {{{1
1202
1202
1203 " delete one-time vars and functions
1203 " delete one-time vars and functions
1204 delfunction <SID>HGInstallDocumentation
1204 delfunction <SID>HGInstallDocumentation
1205 delfunction <SID>HGFlexiMkdir
1205 delfunction <SID>HGFlexiMkdir
1206 delfunction <SID>HGCleanupOnFailure
1206 delfunction <SID>HGCleanupOnFailure
1207 unlet s:script_version s:script_name
1207 unlet s:script_version s:script_name
1208
1208
1209 let g:loaded_hgcommand=2
1209 let g:loaded_hgcommand=2
1210 silent do HGCommand User HGPluginFinish
1210 silent do HGCommand User HGPluginFinish
1211
1211
1212 let &cpo = s:save_cpo
1212 let &cpo = s:save_cpo
1213 unlet s:save_cpo
1213 unlet s:save_cpo
1214 " vim:se expandtab sts=2 sw=2:
1214 " vim:se expandtab sts=2 sw=2:
1215 finish
1215 finish
1216
1216
1217 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1217 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1218 " Section: Documentation content {{{1
1218 " Section: Documentation content {{{1
1219 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1219 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1220 === START_DOC
1220 === START_DOC
1221 *hgcommand.txt* Mercurial vim integration #version#
1221 *hgcommand.txt* Mercurial vim integration #version#
1222
1222
1223
1223
1224 HGCOMMAND REFERENCE MANUAL~
1224 HGCOMMAND REFERENCE MANUAL~
1225
1225
1226
1226
1227 Author: Mathieu Clabaut <mathieu.clabaut@gmail.com>
1227 Author: Mathieu Clabaut <mathieu.clabaut@gmail.com>
1228 Credits: Bob Hiestand <bob.hiestand@gmail.com>
1228 Credits: Bob Hiestand <bob.hiestand@gmail.com>
1229 Mercurial: http://mercurial.selenic.com/
1229 Mercurial: http://mercurial.selenic.com/
1230 Mercurial (noted Hg) is a fast, lightweight Source Control Management
1230 Mercurial (noted Hg) is a fast, lightweight Source Control Management
1231 system designed for efficient handling of very large distributed projects.
1231 system designed for efficient handling of very large distributed projects.
1232
1232
1233 ==============================================================================
1233 ==============================================================================
1234 1. Contents *hgcommand-contents*
1234 1. Contents *hgcommand-contents*
1235
1235
1236 Installation : |hgcommand-install|
1236 Installation : |hgcommand-install|
1237 HGCommand Intro : |hgcommand|
1237 HGCommand Intro : |hgcommand|
1238 HGCommand Manual : |hgcommand-manual|
1238 HGCommand Manual : |hgcommand-manual|
1239 Customization : |hgcommand-customize|
1239 Customization : |hgcommand-customize|
1240 Bugs : |hgcommand-bugs|
1240 Bugs : |hgcommand-bugs|
1241
1241
1242 ==============================================================================
1242 ==============================================================================
1243 2. HGCommand Installation *hgcommand-install*
1243 2. HGCommand Installation *hgcommand-install*
1244
1244
1245 In order to install the plugin, place the hgcommand.vim file into a plugin'
1245 In order to install the plugin, place the hgcommand.vim file into a plugin'
1246 directory in your runtime path (please see |add-global-plugin| and
1246 directory in your runtime path (please see |add-global-plugin| and
1247 |'runtimepath'|.
1247 |'runtimepath'|.
1248
1248
1249 HGCommand may be customized by setting variables, creating maps, and
1249 HGCommand may be customized by setting variables, creating maps, and
1250 specifying event handlers. Please see |hgcommand-customize| for more
1250 specifying event handlers. Please see |hgcommand-customize| for more
1251 details.
1251 details.
1252
1252
1253 *hgcommand-auto-help*
1253 *hgcommand-auto-help*
1254 The help file is automagically generated when the |hgcommand| script is
1254 The help file is automagically generated when the |hgcommand| script is
1255 loaded for the first time.
1255 loaded for the first time.
1256
1256
1257 ==============================================================================
1257 ==============================================================================
1258
1258
1259 3. HGCommand Intro *hgcommand*
1259 3. HGCommand Intro *hgcommand*
1260 *hgcommand-intro*
1260 *hgcommand-intro*
1261
1261
1262 The HGCommand plugin provides global ex commands for manipulating
1262 The HGCommand plugin provides global ex commands for manipulating
1263 HG-controlled source files. In general, each command operates on the
1263 HG-controlled source files. In general, each command operates on the
1264 current buffer and accomplishes a separate hg function, such as update,
1264 current buffer and accomplishes a separate hg function, such as update,
1265 commit, log, and others (please see |hgcommand-commands| for a list of all
1265 commit, log, and others (please see |hgcommand-commands| for a list of all
1266 available commands). The results of each operation are displayed in a
1266 available commands). The results of each operation are displayed in a
1267 scratch buffer. Several buffer variables are defined for those scratch
1267 scratch buffer. Several buffer variables are defined for those scratch
1268 buffers (please see |hgcommand-buffer-variables|).
1268 buffers (please see |hgcommand-buffer-variables|).
1269
1269
1270 The notion of "current file" means either the current buffer, or, in the
1270 The notion of "current file" means either the current buffer, or, in the
1271 case of a directory buffer, the file on the current line within the buffer.
1271 case of a directory buffer, the file on the current line within the buffer.
1272
1272
1273 For convenience, any HGCommand invoked on a HGCommand scratch buffer acts
1273 For convenience, any HGCommand invoked on a HGCommand scratch buffer acts
1274 as though it was invoked on the original file and splits the screen so that
1274 as though it was invoked on the original file and splits the screen so that
1275 the output appears in a new window.
1275 the output appears in a new window.
1276
1276
1277 Many of the commands accept revisions as arguments. By default, most
1277 Many of the commands accept revisions as arguments. By default, most
1278 operate on the most recent revision on the current branch if no revision is
1278 operate on the most recent revision on the current branch if no revision is
1279 specified (though see |HGCommandInteractive| to prompt instead).
1279 specified (though see |HGCommandInteractive| to prompt instead).
1280
1280
1281 Each HGCommand is mapped to a key sequence starting with the <Leader>
1281 Each HGCommand is mapped to a key sequence starting with the <Leader>
1282 keystroke. The default mappings may be overridden by supplying different
1282 keystroke. The default mappings may be overridden by supplying different
1283 mappings before the plugin is loaded, such as in the vimrc, in the standard
1283 mappings before the plugin is loaded, such as in the vimrc, in the standard
1284 fashion for plugin mappings. For examples, please see
1284 fashion for plugin mappings. For examples, please see
1285 |hgcommand-mappings-override|.
1285 |hgcommand-mappings-override|.
1286
1286
1287 The HGCommand plugin may be configured in several ways. For more details,
1287 The HGCommand plugin may be configured in several ways. For more details,
1288 please see |hgcommand-customize|.
1288 please see |hgcommand-customize|.
1289
1289
1290 ==============================================================================
1290 ==============================================================================
1291 4. HGCommand Manual *hgcommand-manual*
1291 4. HGCommand Manual *hgcommand-manual*
1292
1292
1293 4.1 HGCommand commands *hgcommand-commands*
1293 4.1 HGCommand commands *hgcommand-commands*
1294
1294
1295 HGCommand defines the following commands:
1295 HGCommand defines the following commands:
1296
1296
1297 |:HGAdd|
1297 |:HGAdd|
1298 |:HGAnnotate|
1298 |:HGAnnotate|
1299 |:HGCommit|
1299 |:HGCommit|
1300 |:HGDiff|
1300 |:HGDiff|
1301 |:HGGotoOriginal|
1301 |:HGGotoOriginal|
1302 |:HGLog|
1302 |:HGLog|
1303 |:HGRevert|
1303 |:HGRevert|
1304 |:HGReview|
1304 |:HGReview|
1305 |:HGStatus|
1305 |:HGStatus|
1306 |:HGUpdate|
1306 |:HGUpdate|
1307 |:HGVimDiff|
1307 |:HGVimDiff|
1308
1308
1309 :HGAdd *:HGAdd*
1309 :HGAdd *:HGAdd*
1310
1310
1311 This command performs "hg add" on the current file. Please note, this does
1311 This command performs "hg add" on the current file. Please note, this does
1312 not commit the newly-added file.
1312 not commit the newly-added file.
1313
1313
1314 :HGAnnotate *:HGAnnotate*
1314 :HGAnnotate *:HGAnnotate*
1315
1315
1316 This command performs "hg annotate" on the current file. If an argument is
1316 This command performs "hg annotate" on the current file. If an argument is
1317 given, the argument is used as a revision number to display. If not given
1317 given, the argument is used as a revision number to display. If not given
1318 an argument, it uses the most recent version of the file on the current
1318 an argument, it uses the most recent version of the file on the current
1319 branch. Additionally, if the current buffer is a HGAnnotate buffer
1319 branch. Additionally, if the current buffer is a HGAnnotate buffer
1320 already, the version number on the current line is used.
1320 already, the version number on the current line is used.
1321
1321
1322 If the |HGCommandAnnotateParent| variable is set to a non-zero value, the
1322 If the |HGCommandAnnotateParent| variable is set to a non-zero value, the
1323 version previous to the one on the current line is used instead. This
1323 version previous to the one on the current line is used instead. This
1324 allows one to navigate back to examine the previous version of a line.
1324 allows one to navigate back to examine the previous version of a line.
1325
1325
1326 The filetype of the HGCommand scratch buffer is set to 'HGAnnotate', to
1326 The filetype of the HGCommand scratch buffer is set to 'HGAnnotate', to
1327 take advantage of the bundled syntax file.
1327 take advantage of the bundled syntax file.
1328
1328
1329
1329
1330 :HGCommit[!] *:HGCommit*
1330 :HGCommit[!] *:HGCommit*
1331
1331
1332 If called with arguments, this performs "hg commit" using the arguments as
1332 If called with arguments, this performs "hg commit" using the arguments as
1333 the log message.
1333 the log message.
1334
1334
1335 If '!' is used with no arguments, an empty log message is committed.
1335 If '!' is used with no arguments, an empty log message is committed.
1336
1336
1337 If called with no arguments, this is a two-step command. The first step
1337 If called with no arguments, this is a two-step command. The first step
1338 opens a buffer to accept a log message. When that buffer is written, it is
1338 opens a buffer to accept a log message. When that buffer is written, it is
1339 automatically closed and the file is committed using the information from
1339 automatically closed and the file is committed using the information from
1340 that log message. The commit can be abandoned if the log message buffer is
1340 that log message. The commit can be abandoned if the log message buffer is
1341 deleted or wiped before being written.
1341 deleted or wiped before being written.
1342
1342
1343 Alternatively, the mapping that is used to invoke :HGCommit (by default
1343 Alternatively, the mapping that is used to invoke :HGCommit (by default
1344 <Leader>hgc) can be used in the log message buffer to immediately commit.
1344 <Leader>hgc) can be used in the log message buffer to immediately commit.
1345 This is useful if the |HGCommandCommitOnWrite| variable is set to 0 to
1345 This is useful if the |HGCommandCommitOnWrite| variable is set to 0 to
1346 disable the normal commit-on-write behavior.
1346 disable the normal commit-on-write behavior.
1347
1347
1348 :HGDiff *:HGDiff*
1348 :HGDiff *:HGDiff*
1349
1349
1350 With no arguments, this performs "hg diff" on the current file against the
1350 With no arguments, this performs "hg diff" on the current file against the
1351 current repository version.
1351 current repository version.
1352
1352
1353 With one argument, "hg diff" is performed on the current file against the
1353 With one argument, "hg diff" is performed on the current file against the
1354 specified revision.
1354 specified revision.
1355
1355
1356 With two arguments, hg diff is performed between the specified revisions of
1356 With two arguments, hg diff is performed between the specified revisions of
1357 the current file.
1357 the current file.
1358
1358
1359 This command uses the 'HGCommandDiffOpt' variable to specify diff options.
1359 This command uses the 'HGCommandDiffOpt' variable to specify diff options.
1360 If that variable does not exist, then 'wbBc' is assumed. If you wish to
1360 If that variable does not exist, then 'wbBc' is assumed. If you wish to
1361 have no options, then set it to the empty string.
1361 have no options, then set it to the empty string.
1362
1362
1363
1363
1364 :HGGotoOriginal *:HGGotoOriginal*
1364 :HGGotoOriginal *:HGGotoOriginal*
1365
1365
1366 This command returns the current window to the source buffer, if the
1366 This command returns the current window to the source buffer, if the
1367 current buffer is a HG command output buffer.
1367 current buffer is a HG command output buffer.
1368
1368
1369 :HGGotoOriginal!
1369 :HGGotoOriginal!
1370
1370
1371 Like ":HGGotoOriginal" but also executes :bufwipeout on all HG command
1371 Like ":HGGotoOriginal" but also executes :bufwipeout on all HG command
1372 output buffers for the source buffer.
1372 output buffers for the source buffer.
1373
1373
1374 :HGLog *:HGLog*
1374 :HGLog *:HGLog*
1375
1375
1376 Performs "hg log" on the current file.
1376 Performs "hg log" on the current file.
1377
1377
1378 If an argument is given, it is passed as an argument to the "-r" option of
1378 If an argument is given, it is passed as an argument to the "-r" option of
1379 "hg log".
1379 "hg log".
1380
1380
1381 :HGRevert *:HGRevert*
1381 :HGRevert *:HGRevert*
1382
1382
1383 Replaces the current file with the most recent version from the repository
1383 Replaces the current file with the most recent version from the repository
1384 in order to wipe out any undesired changes.
1384 in order to wipe out any undesired changes.
1385
1385
1386 :HGReview *:HGReview*
1386 :HGReview *:HGReview*
1387
1387
1388 Retrieves a particular version of the current file. If no argument is
1388 Retrieves a particular version of the current file. If no argument is
1389 given, the most recent version of the file on the current branch is
1389 given, the most recent version of the file on the current branch is
1390 retrieved. Otherwise, the specified version is retrieved.
1390 retrieved. Otherwise, the specified version is retrieved.
1391
1391
1392 :HGStatus *:HGStatus*
1392 :HGStatus *:HGStatus*
1393
1393
1394 Performs "hg status" on the current file.
1394 Performs "hg status" on the current file.
1395
1395
1396 :HGUpdate *:HGUpdate*
1396 :HGUpdate *:HGUpdate*
1397
1397
1398 Performs "hg update" on the current file. This intentionally does not
1398 Performs "hg update" on the current file. This intentionally does not
1399 automatically reload the current buffer, though vim should prompt the user
1399 automatically reload the current buffer, though vim should prompt the user
1400 to do so if the underlying file is altered by this command.
1400 to do so if the underlying file is altered by this command.
1401
1401
1402 :HGVimDiff *:HGVimDiff*
1402 :HGVimDiff *:HGVimDiff*
1403
1403
1404 With no arguments, this prompts the user for a revision and then uses
1404 With no arguments, this prompts the user for a revision and then uses
1405 vimdiff to display the differences between the current file and the
1405 vimdiff to display the differences between the current file and the
1406 specified revision. If no revision is specified, the most recent version
1406 specified revision. If no revision is specified, the most recent version
1407 of the file on the current branch is used.
1407 of the file on the current branch is used.
1408
1408
1409 With one argument, that argument is used as the revision as above. With
1409 With one argument, that argument is used as the revision as above. With
1410 two arguments, the differences between the two revisions is displayed using
1410 two arguments, the differences between the two revisions is displayed using
1411 vimdiff.
1411 vimdiff.
1412
1412
1413 With either zero or one argument, the original buffer is used to perform
1413 With either zero or one argument, the original buffer is used to perform
1414 the vimdiff. When the other buffer is closed, the original buffer will be
1414 the vimdiff. When the other buffer is closed, the original buffer will be
1415 returned to normal mode.
1415 returned to normal mode.
1416
1416
1417 Once vimdiff mode is started using the above methods, additional vimdiff
1417 Once vimdiff mode is started using the above methods, additional vimdiff
1418 buffers may be added by passing a single version argument to the command.
1418 buffers may be added by passing a single version argument to the command.
1419 There may be up to 4 vimdiff buffers total.
1419 There may be up to 4 vimdiff buffers total.
1420
1420
1421 Using the 2-argument form of the command resets the vimdiff to only those 2
1421 Using the 2-argument form of the command resets the vimdiff to only those 2
1422 versions. Additionally, invoking the command on a different file will
1422 versions. Additionally, invoking the command on a different file will
1423 close the previous vimdiff buffers.
1423 close the previous vimdiff buffers.
1424
1424
1425
1425
1426 4.2 Mappings *hgcommand-mappings*
1426 4.2 Mappings *hgcommand-mappings*
1427
1427
1428 By default, a mapping is defined for each command. These mappings execute
1428 By default, a mapping is defined for each command. These mappings execute
1429 the default (no-argument) form of each command.
1429 the default (no-argument) form of each command.
1430
1430
1431 <Leader>hga HGAdd
1431 <Leader>hga HGAdd
1432 <Leader>hgn HGAnnotate
1432 <Leader>hgn HGAnnotate
1433 <Leader>hgc HGCommit
1433 <Leader>hgc HGCommit
1434 <Leader>hgd HGDiff
1434 <Leader>hgd HGDiff
1435 <Leader>hgg HGGotoOriginal
1435 <Leader>hgg HGGotoOriginal
1436 <Leader>hgG HGGotoOriginal!
1436 <Leader>hgG HGGotoOriginal!
1437 <Leader>hgl HGLog
1437 <Leader>hgl HGLog
1438 <Leader>hgr HGReview
1438 <Leader>hgr HGReview
1439 <Leader>hgs HGStatus
1439 <Leader>hgs HGStatus
1440 <Leader>hgu HGUpdate
1440 <Leader>hgu HGUpdate
1441 <Leader>hgv HGVimDiff
1441 <Leader>hgv HGVimDiff
1442
1442
1443 *hgcommand-mappings-override*
1443 *hgcommand-mappings-override*
1444
1444
1445 The default mappings can be overridden by user-provided instead by mapping
1445 The default mappings can be overridden by user-provided instead by mapping
1446 to <Plug>CommandName. This is especially useful when these mappings
1446 to <Plug>CommandName. This is especially useful when these mappings
1447 collide with other existing mappings (vim will warn of this during plugin
1447 collide with other existing mappings (vim will warn of this during plugin
1448 initialization, but will not clobber the existing mappings).
1448 initialization, but will not clobber the existing mappings).
1449
1449
1450 For instance, to override the default mapping for :HGAdd to set it to
1450 For instance, to override the default mapping for :HGAdd to set it to
1451 '\add', add the following to the vimrc: >
1451 '\add', add the following to the vimrc: >
1452
1452
1453 nmap \add <Plug>HGAdd
1453 nmap \add <Plug>HGAdd
1454 <
1454 <
1455 4.3 Automatic buffer variables *hgcommand-buffer-variables*
1455 4.3 Automatic buffer variables *hgcommand-buffer-variables*
1456
1456
1457 Several buffer variables are defined in each HGCommand result buffer.
1457 Several buffer variables are defined in each HGCommand result buffer.
1458 These may be useful for additional customization in callbacks defined in
1458 These may be useful for additional customization in callbacks defined in
1459 the event handlers (please see |hgcommand-events|).
1459 the event handlers (please see |hgcommand-events|).
1460
1460
1461 The following variables are automatically defined:
1461 The following variables are automatically defined:
1462
1462
1463 b:hgOrigBuffNR *b:hgOrigBuffNR*
1463 b:hgOrigBuffNR *b:hgOrigBuffNR*
1464
1464
1465 This variable is set to the buffer number of the source file.
1465 This variable is set to the buffer number of the source file.
1466
1466
1467 b:hgcmd *b:hgcmd*
1467 b:hgcmd *b:hgcmd*
1468
1468
1469 This variable is set to the name of the hg command that created the result
1469 This variable is set to the name of the hg command that created the result
1470 buffer.
1470 buffer.
1471 ==============================================================================
1471 ==============================================================================
1472
1472
1473 5. Configuration and customization *hgcommand-customize*
1473 5. Configuration and customization *hgcommand-customize*
1474 *hgcommand-config*
1474 *hgcommand-config*
1475
1475
1476 The HGCommand plugin can be configured in two ways: by setting
1476 The HGCommand plugin can be configured in two ways: by setting
1477 configuration variables (see |hgcommand-options|) or by defining HGCommand
1477 configuration variables (see |hgcommand-options|) or by defining HGCommand
1478 event handlers (see |hgcommand-events|). Additionally, the HGCommand
1478 event handlers (see |hgcommand-events|). Additionally, the HGCommand
1479 plugin provides several option for naming the HG result buffers (see
1479 plugin provides several option for naming the HG result buffers (see
1480 |hgcommand-naming|) and supported a customized status line (see
1480 |hgcommand-naming|) and supported a customized status line (see
1481 |hgcommand-statusline| and |hgcommand-buffer-management|).
1481 |hgcommand-statusline| and |hgcommand-buffer-management|).
1482
1482
1483 5.1 HGCommand configuration variables *hgcommand-options*
1483 5.1 HGCommand configuration variables *hgcommand-options*
1484
1484
1485 Several variables affect the plugin's behavior. These variables are
1485 Several variables affect the plugin's behavior. These variables are
1486 checked at time of execution, and may be defined at the window, buffer, or
1486 checked at time of execution, and may be defined at the window, buffer, or
1487 global level and are checked in that order of precedence.
1487 global level and are checked in that order of precedence.
1488
1488
1489
1489
1490 The following variables are available:
1490 The following variables are available:
1491
1491
1492 |HGCommandAnnotateParent|
1492 |HGCommandAnnotateParent|
1493 |HGCommandCommitOnWrite|
1493 |HGCommandCommitOnWrite|
1494 |HGCommandHGExec|
1494 |HGCommandHGExec|
1495 |HGCommandDeleteOnHide|
1495 |HGCommandDeleteOnHide|
1496 |HGCommandDiffOpt|
1496 |HGCommandDiffOpt|
1497 |HGCommandDiffSplit|
1497 |HGCommandDiffSplit|
1498 |HGCommandEdit|
1498 |HGCommandEdit|
1499 |HGCommandEnableBufferSetup|
1499 |HGCommandEnableBufferSetup|
1500 |HGCommandInteractive|
1500 |HGCommandInteractive|
1501 |HGCommandNameMarker|
1501 |HGCommandNameMarker|
1502 |HGCommandNameResultBuffers|
1502 |HGCommandNameResultBuffers|
1503 |HGCommandSplit|
1503 |HGCommandSplit|
1504
1504
1505 HGCommandAnnotateParent *HGCommandAnnotateParent*
1505 HGCommandAnnotateParent *HGCommandAnnotateParent*
1506
1506
1507 This variable, if set to a non-zero value, causes the zero-argument form of
1507 This variable, if set to a non-zero value, causes the zero-argument form of
1508 HGAnnotate when invoked on a HGAnnotate buffer to go to the version
1508 HGAnnotate when invoked on a HGAnnotate buffer to go to the version
1509 previous to that displayed on the current line. If not set, it defaults to
1509 previous to that displayed on the current line. If not set, it defaults to
1510 0.
1510 0.
1511
1511
1512 HGCommandCommitOnWrite *HGCommandCommitOnWrite*
1512 HGCommandCommitOnWrite *HGCommandCommitOnWrite*
1513
1513
1514 This variable, if set to a non-zero value, causes the pending hg commit to
1514 This variable, if set to a non-zero value, causes the pending hg commit to
1515 take place immediately as soon as the log message buffer is written. If
1515 take place immediately as soon as the log message buffer is written. If
1516 set to zero, only the HGCommit mapping will cause the pending commit to
1516 set to zero, only the HGCommit mapping will cause the pending commit to
1517 occur. If not set, it defaults to 1.
1517 occur. If not set, it defaults to 1.
1518
1518
1519 HGCommandHGExec *HGCommandHGExec*
1519 HGCommandHGExec *HGCommandHGExec*
1520
1520
1521 This variable controls the executable used for all HG commands. If not
1521 This variable controls the executable used for all HG commands. If not
1522 set, it defaults to "hg".
1522 set, it defaults to "hg".
1523
1523
1524 HGCommandDeleteOnHide *HGCommandDeleteOnHide*
1524 HGCommandDeleteOnHide *HGCommandDeleteOnHide*
1525
1525
1526 This variable, if set to a non-zero value, causes the temporary HG result
1526 This variable, if set to a non-zero value, causes the temporary HG result
1527 buffers to automatically delete themselves when hidden.
1527 buffers to automatically delete themselves when hidden.
1528
1528
1529 HGCommandDiffOpt *HGCommandDiffOpt*
1529 HGCommandDiffOpt *HGCommandDiffOpt*
1530
1530
1531 This variable, if set, determines the options passed to the diff command of
1531 This variable, if set, determines the options passed to the diff command of
1532 HG. If not set, it defaults to 'w'.
1532 HG. If not set, it defaults to 'w'.
1533
1533
1534 HGCommandDiffSplit *HGCommandDiffSplit*
1534 HGCommandDiffSplit *HGCommandDiffSplit*
1535
1535
1536 This variable overrides the |HGCommandSplit| variable, but only for buffers
1536 This variable overrides the |HGCommandSplit| variable, but only for buffers
1537 created with |:HGVimDiff|.
1537 created with |:HGVimDiff|.
1538
1538
1539 HGCommandEdit *HGCommandEdit*
1539 HGCommandEdit *HGCommandEdit*
1540
1540
1541 This variable controls whether the original buffer is replaced ('edit') or
1541 This variable controls whether the original buffer is replaced ('edit') or
1542 split ('split'). If not set, it defaults to 'edit'.
1542 split ('split'). If not set, it defaults to 'edit'.
1543
1543
1544 HGCommandEnableBufferSetup *HGCommandEnableBufferSetup*
1544 HGCommandEnableBufferSetup *HGCommandEnableBufferSetup*
1545
1545
1546 This variable, if set to a non-zero value, activates HG buffer management
1546 This variable, if set to a non-zero value, activates HG buffer management
1547 mode see (|hgcommand-buffer-management|). This mode means that three
1547 mode see (|hgcommand-buffer-management|). This mode means that three
1548 buffer variables, 'HGRepository', 'HGRevision' and 'HGBranch', are set if
1548 buffer variables, 'HGRepository', 'HGRevision' and 'HGBranch', are set if
1549 the file is HG-controlled. This is useful for displaying version
1549 the file is HG-controlled. This is useful for displaying version
1550 information in the status bar.
1550 information in the status bar.
1551
1551
1552 HGCommandInteractive *HGCommandInteractive*
1552 HGCommandInteractive *HGCommandInteractive*
1553
1553
1554 This variable, if set to a non-zero value, causes appropriate commands (for
1554 This variable, if set to a non-zero value, causes appropriate commands (for
1555 the moment, only |:HGReview|) to query the user for a revision to use
1555 the moment, only |:HGReview|) to query the user for a revision to use
1556 instead of the current revision if none is specified.
1556 instead of the current revision if none is specified.
1557
1557
1558 HGCommandNameMarker *HGCommandNameMarker*
1558 HGCommandNameMarker *HGCommandNameMarker*
1559
1559
1560 This variable, if set, configures the special attention-getting characters
1560 This variable, if set, configures the special attention-getting characters
1561 that appear on either side of the hg buffer type in the buffer name. This
1561 that appear on either side of the hg buffer type in the buffer name. This
1562 has no effect unless |HGCommandNameResultBuffers| is set to a true value.
1562 has no effect unless |HGCommandNameResultBuffers| is set to a true value.
1563 If not set, it defaults to '_'.
1563 If not set, it defaults to '_'.
1564
1564
1565 HGCommandNameResultBuffers *HGCommandNameResultBuffers*
1565 HGCommandNameResultBuffers *HGCommandNameResultBuffers*
1566
1566
1567 This variable, if set to a true value, causes the hg result buffers to be
1567 This variable, if set to a true value, causes the hg result buffers to be
1568 named in the old way ('<source file name> _<hg command>_'). If not set or
1568 named in the old way ('<source file name> _<hg command>_'). If not set or
1569 set to a false value, the result buffer is nameless.
1569 set to a false value, the result buffer is nameless.
1570
1570
1571 HGCommandSplit *HGCommandSplit*
1571 HGCommandSplit *HGCommandSplit*
1572
1572
1573 This variable controls the orientation of the various window splits that
1573 This variable controls the orientation of the various window splits that
1574 may occur (such as with HGVimDiff, when using a HG command on a HG command
1574 may occur (such as with HGVimDiff, when using a HG command on a HG command
1575 buffer, or when the |HGCommandEdit| variable is set to 'split'. If set to
1575 buffer, or when the |HGCommandEdit| variable is set to 'split'. If set to
1576 'horizontal', the resulting windows will be on stacked on top of one
1576 'horizontal', the resulting windows will be on stacked on top of one
1577 another. If set to 'vertical', the resulting windows will be side-by-side.
1577 another. If set to 'vertical', the resulting windows will be side-by-side.
1578 If not set, it defaults to 'horizontal' for all but HGVimDiff windows.
1578 If not set, it defaults to 'horizontal' for all but HGVimDiff windows.
1579
1579
1580 5.2 HGCommand events *hgcommand-events*
1580 5.2 HGCommand events *hgcommand-events*
1581
1581
1582 For additional customization, HGCommand can trigger user-defined events.
1582 For additional customization, HGCommand can trigger user-defined events.
1583 Event handlers are provided by defining User event autocommands (see
1583 Event handlers are provided by defining User event autocommands (see
1584 |autocommand|, |User|) in the HGCommand group with patterns matching the
1584 |autocommand|, |User|) in the HGCommand group with patterns matching the
1585 event name.
1585 event name.
1586
1586
1587 For instance, the following could be added to the vimrc to provide a 'q'
1587 For instance, the following could be added to the vimrc to provide a 'q'
1588 mapping to quit a HGCommand scratch buffer: >
1588 mapping to quit a HGCommand scratch buffer: >
1589
1589
1590 augroup HGCommand
1590 augroup HGCommand
1591 au HGCommand User HGBufferCreated silent! nmap <unique> <buffer> q:
1591 au HGCommand User HGBufferCreated silent! nmap <unique> <buffer> q:
1592 bwipeout<cr>
1592 bwipeout<cr>
1593 augroup END
1593 augroup END
1594 <
1594 <
1595
1595
1596 The following hooks are available:
1596 The following hooks are available:
1597
1597
1598 HGBufferCreated This event is fired just after a hg command result
1598 HGBufferCreated This event is fired just after a hg command result
1599 buffer is created and filled with the result of a hg
1599 buffer is created and filled with the result of a hg
1600 command. It is executed within the context of the HG
1600 command. It is executed within the context of the HG
1601 command buffer. The HGCommand buffer variables may be
1601 command buffer. The HGCommand buffer variables may be
1602 useful for handlers of this event (please see
1602 useful for handlers of this event (please see
1603 |hgcommand-buffer-variables|).
1603 |hgcommand-buffer-variables|).
1604
1604
1605 HGBufferSetup This event is fired just after HG buffer setup occurs,
1605 HGBufferSetup This event is fired just after HG buffer setup occurs,
1606 if enabled.
1606 if enabled.
1607
1607
1608 HGPluginInit This event is fired when the HGCommand plugin first
1608 HGPluginInit This event is fired when the HGCommand plugin first
1609 loads.
1609 loads.
1610
1610
1611 HGPluginFinish This event is fired just after the HGCommand plugin
1611 HGPluginFinish This event is fired just after the HGCommand plugin
1612 loads.
1612 loads.
1613
1613
1614 HGVimDiffFinish This event is fired just after the HGVimDiff command
1614 HGVimDiffFinish This event is fired just after the HGVimDiff command
1615 executes to allow customization of, for instance,
1615 executes to allow customization of, for instance,
1616 window placement and focus.
1616 window placement and focus.
1617
1617
1618 5.3 HGCommand buffer naming *hgcommand-naming*
1618 5.3 HGCommand buffer naming *hgcommand-naming*
1619
1619
1620 By default, the buffers containing the result of HG commands are nameless
1620 By default, the buffers containing the result of HG commands are nameless
1621 scratch buffers. It is intended that buffer variables of those buffers be
1621 scratch buffers. It is intended that buffer variables of those buffers be
1622 used to customize the statusline option so that the user may fully control
1622 used to customize the statusline option so that the user may fully control
1623 the display of result buffers.
1623 the display of result buffers.
1624
1624
1625 If the old-style naming is desired, please enable the
1625 If the old-style naming is desired, please enable the
1626 |HGCommandNameResultBuffers| variable. Then, each result buffer will
1626 |HGCommandNameResultBuffers| variable. Then, each result buffer will
1627 receive a unique name that includes the source file name, the HG command,
1627 receive a unique name that includes the source file name, the HG command,
1628 and any extra data (such as revision numbers) that were part of the
1628 and any extra data (such as revision numbers) that were part of the
1629 command.
1629 command.
1630
1630
1631 5.4 HGCommand status line support *hgcommand-statusline*
1631 5.4 HGCommand status line support *hgcommand-statusline*
1632
1632
1633 It is intended that the user will customize the |'statusline'| option to
1633 It is intended that the user will customize the |'statusline'| option to
1634 include HG result buffer attributes. A sample function that may be used in
1634 include HG result buffer attributes. A sample function that may be used in
1635 the |'statusline'| option is provided by the plugin, HGGetStatusLine(). In
1635 the |'statusline'| option is provided by the plugin, HGGetStatusLine(). In
1636 order to use that function in the status line, do something like the
1636 order to use that function in the status line, do something like the
1637 following: >
1637 following: >
1638
1638
1639 set statusline=%<%f\ %{HGGetStatusLine()}\ %h%m%r%=%l,%c%V\ %P
1639 set statusline=%<%f\ %{HGGetStatusLine()}\ %h%m%r%=%l,%c%V\ %P
1640 <
1640 <
1641 of which %{HGGetStatusLine()} is the relevant portion.
1641 of which %{HGGetStatusLine()} is the relevant portion.
1642
1642
1643 The sample HGGetStatusLine() function handles both HG result buffers and
1643 The sample HGGetStatusLine() function handles both HG result buffers and
1644 HG-managed files if HGCommand buffer management is enabled (please see
1644 HG-managed files if HGCommand buffer management is enabled (please see
1645 |hgcommand-buffer-management|).
1645 |hgcommand-buffer-management|).
1646
1646
1647 5.5 HGCommand buffer management *hgcommand-buffer-management*
1647 5.5 HGCommand buffer management *hgcommand-buffer-management*
1648
1648
1649 The HGCommand plugin can operate in buffer management mode, which means
1649 The HGCommand plugin can operate in buffer management mode, which means
1650 that it attempts to set two buffer variables ('HGRevision' and 'HGBranch')
1650 that it attempts to set two buffer variables ('HGRevision' and 'HGBranch')
1651 upon entry into a buffer. This is rather slow because it means that 'hg
1651 upon entry into a buffer. This is rather slow because it means that 'hg
1652 status' will be invoked at each entry into a buffer (during the |BufEnter|
1652 status' will be invoked at each entry into a buffer (during the |BufEnter|
1653 autocommand).
1653 autocommand).
1654
1654
1655 This mode is enabled by default. In order to disable it, set the
1655 This mode is enabled by default. In order to disable it, set the
1656 |HGCommandEnableBufferSetup| variable to a false (zero) value. Enabling
1656 |HGCommandEnableBufferSetup| variable to a false (zero) value. Enabling
1657 this mode simply provides the buffer variables mentioned above. The user
1657 this mode simply provides the buffer variables mentioned above. The user
1658 must explicitly include those in the |'statusline'| option if they are to
1658 must explicitly include those in the |'statusline'| option if they are to
1659 appear in the status line (but see |hgcommand-statusline| for a simple way
1659 appear in the status line (but see |hgcommand-statusline| for a simple way
1660 to do that).
1660 to do that).
1661
1661
1662 ==============================================================================
1662 ==============================================================================
1663 9. Tips *hgcommand-tips*
1663 9. Tips *hgcommand-tips*
1664
1664
1665 9.1 Split window annotation, by Michael Anderson >
1665 9.1 Split window annotation, by Michael Anderson >
1666
1666
1667 :nmap <Leader>hgN :vs<CR><C-w>h<Leader>hgn:vertical res 40<CR>
1667 :nmap <Leader>hgN :vs<CR><C-w>h<Leader>hgn:vertical res 40<CR>
1668 \ggdddd:set scb<CR>:set nowrap<CR><C-w>lgg:set scb<CR>
1668 \ggdddd:set scb<CR>:set nowrap<CR><C-w>lgg:set scb<CR>
1669 \:set nowrap<CR>
1669 \:set nowrap<CR>
1670 <
1670 <
1671
1671
1672 This splits the buffer vertically, puts an annotation on the left (minus
1672 This splits the buffer vertically, puts an annotation on the left (minus
1673 the header) with the width set to 40. An editable/normal copy is placed on
1673 the header) with the width set to 40. An editable/normal copy is placed on
1674 the right. The two versions are scroll locked so they move as one. and
1674 the right. The two versions are scroll locked so they move as one. and
1675 wrapping is turned off so that the lines line up correctly. The advantages
1675 wrapping is turned off so that the lines line up correctly. The advantages
1676 are...
1676 are...
1677
1677
1678 1) You get a versioning on the right.
1678 1) You get a versioning on the right.
1679 2) You can still edit your own code.
1679 2) You can still edit your own code.
1680 3) Your own code still has syntax highlighting.
1680 3) Your own code still has syntax highlighting.
1681
1681
1682 ==============================================================================
1682 ==============================================================================
1683
1683
1684 8. Known bugs *hgcommand-bugs*
1684 8. Known bugs *hgcommand-bugs*
1685
1685
1686 Please let me know if you run across any.
1686 Please let me know if you run across any.
1687
1687
1688 HGVimDiff, when using the original (real) source buffer as one of the diff
1688 HGVimDiff, when using the original (real) source buffer as one of the diff
1689 buffers, uses some hacks to try to restore the state of the original buffer
1689 buffers, uses some hacks to try to restore the state of the original buffer
1690 when the scratch buffer containing the other version is destroyed. There
1690 when the scratch buffer containing the other version is destroyed. There
1691 may still be bugs in here, depending on many configuration details.
1691 may still be bugs in here, depending on many configuration details.
1692
1692
1693 ==============================================================================
1693 ==============================================================================
1694
1694
1695 9. TODO *hgcommand-todo*
1695 9. TODO *hgcommand-todo*
1696
1696
1697 Integrate symlink tracking once HG will support them.
1697 Integrate symlink tracking once HG will support them.
1698 ==============================================================================
1698 ==============================================================================
1699 === END_DOC
1699 === END_DOC
1700 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1700 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1701 " v im:tw=78:ts=8:ft=help:norl:
1701 " v im:tw=78:ts=8:ft=help:norl:
1702 " vim600: set foldmethod=marker tabstop=8 shiftwidth=2 softtabstop=2 smartindent smarttab :
1702 " vim600: set foldmethod=marker tabstop=8 shiftwidth=2 softtabstop=2 smartindent smarttab :
1703 "fileencoding=iso-8859-15
1703 "fileencoding=iso-8859-15
@@ -1,868 +1,868 b''
1 " VIM plugin for doing single, multi-patch or diff code reviews {{{
1 " VIM plugin for doing single, multi-patch or diff code reviews {{{
2 " Home: http://www.vim.org/scripts/script.php?script_id=1563
2 " Home: http://www.vim.org/scripts/script.php?script_id=1563
3
3
4 " Version : 0.2.2 "{{{
4 " Version : 0.2.2 "{{{
5 " Author : Manpreet Singh < junkblocker@yahoo.com >
5 " Author : Manpreet Singh < junkblocker@yahoo.com >
6 " Copyright : 2006-2010 by Manpreet Singh
6 " Copyright : 2006-2010 by Manpreet Singh
7 " License : This file is placed in the public domain.
7 " License : This file is placed in the public domain.
8 " No warranties express or implied. Use at your own risk.
8 " No warranties express or implied. Use at your own risk.
9 "
9 "
10 " Changelog :
10 " Changelog :
11 "
11 "
12 " 0.2.2 - Security fixes by removing custom tempfile creation
12 " 0.2.2 - Security fixes by removing custom tempfile creation
13 " - Removed need for DiffReviewCleanup/PatchReviewCleanup
13 " - Removed need for DiffReviewCleanup/PatchReviewCleanup
14 " - Better command execution error detection and display
14 " - Better command execution error detection and display
15 " - Improved diff view and folding by ignoring modelines
15 " - Improved diff view and folding by ignoring modelines
16 " - Improved tab labels display
16 " - Improved tab labels display
17 "
17 "
18 " 0.2.1 - Minor temp directory autodetection logic and cleanup
18 " 0.2.1 - Minor temp directory autodetection logic and cleanup
19 "
19 "
20 " 0.2 - Removed the need for filterdiff by implemeting it in pure vim script
20 " 0.2 - Removed the need for filterdiff by implementing it in pure vim script
21 " - Added DiffReview command for reverse (changed repository to
21 " - Added DiffReview command for reverse (changed repository to
22 " pristine state) reviews.
22 " pristine state) reviews.
23 " (PatchReview does pristine repository to patch review)
23 " (PatchReview does pristine repository to patch review)
24 " - DiffReview does automatic detection and generation of diffs for
24 " - DiffReview does automatic detection and generation of diffs for
25 " various Source Control systems
25 " various Source Control systems
26 " - Skip load if VIM 7.0 or higher unavailable
26 " - Skip load if VIM 7.0 or higher unavailable
27 "
27 "
28 " 0.1 - First released
28 " 0.1 - First released
29 "}}}
29 "}}}
30
30
31 " Documentation: "{{{
31 " Documentation: "{{{
32 " ===========================================================================
32 " ===========================================================================
33 " This plugin allows single or multiple, patch or diff based code reviews to
33 " This plugin allows single or multiple, patch or diff based code reviews to
34 " be easily done in VIM. VIM has :diffpatch command to do single file reviews
34 " be easily done in VIM. VIM has :diffpatch command to do single file reviews
35 " but a) can not handle patch files containing multiple patches or b) do
35 " but a) can not handle patch files containing multiple patches or b) do
36 " automated diff generation for various version control systems. This plugin
36 " automated diff generation for various version control systems. This plugin
37 " attempts to provide those functionalities. It opens each changed / added or
37 " attempts to provide those functionalities. It opens each changed / added or
38 " removed file diff in new tabs.
38 " removed file diff in new tabs.
39 "
39 "
40 " Installing:
40 " Installing:
41 "
41 "
42 " For a quick start, unzip patchreview.zip into your ~/.vim directory and
42 " For a quick start, unzip patchreview.zip into your ~/.vim directory and
43 " restart Vim.
43 " restart Vim.
44 "
44 "
45 " Details:
45 " Details:
46 "
46 "
47 " Requirements:
47 " Requirements:
48 "
48 "
49 " 1) VIM 7.0 or higher built with +diff option.
49 " 1) VIM 7.0 or higher built with +diff option.
50 "
50 "
51 " 2) A gnu compatible patch command installed. This is the standard patch
51 " 2) A gnu compatible patch command installed. This is the standard patch
52 " command on Linux, Mac OS X, *BSD, Cygwin or /usr/bin/gpatch on newer
52 " command on Linux, Mac OS X, *BSD, Cygwin or /usr/bin/gpatch on newer
53 " Solaris.
53 " Solaris.
54 "
54 "
55 " 3) Optional (but recommended for speed)
55 " 3) Optional (but recommended for speed)
56 "
56 "
57 " Install patchutils ( http://cyberelk.net/tim/patchutils/ ) for your
57 " Install patchutils ( http://cyberelk.net/tim/patchutils/ ) for your
58 " OS. For windows it is available from Cygwin
58 " OS. For windows it is available from Cygwin
59 "
59 "
60 " http://www.cygwin.com
60 " http://www.cygwin.com
61 "
61 "
62 " or GnuWin32
62 " or GnuWin32
63 "
63 "
64 " http://gnuwin32.sourceforge.net/
64 " http://gnuwin32.sourceforge.net/
65 "
65 "
66 " Install:
66 " Install:
67 "
67 "
68 " 1) Extract the zip in your $HOME/.vim or $VIM/vimfiles directory and
68 " 1) Extract the zip in your $HOME/.vim or $VIM/vimfiles directory and
69 " restart vim. The directory location relevant to your platform can be
69 " restart vim. The directory location relevant to your platform can be
70 " seen by running :help add-global-plugin in vim.
70 " seen by running :help add-global-plugin in vim.
71 "
71 "
72 " 2) Restart vim.
72 " 2) Restart vim.
73 "
73 "
74 " Configuration:
74 " Configuration:
75 "
75 "
76 " Optionally, specify the locations to these filterdiff and patch commands
76 " Optionally, specify the locations to these filterdiff and patch commands
77 " and location of a temporary directory to use in your .vimrc.
77 " and location of a temporary directory to use in your .vimrc.
78 "
78 "
79 " let g:patchreview_patch = '/path/to/gnu/patch'
79 " let g:patchreview_patch = '/path/to/gnu/patch'
80 "
80 "
81 " " If you are using filterdiff
81 " " If you are using filterdiff
82 " let g:patchreview_filterdiff = '/path/to/filterdiff'
82 " let g:patchreview_filterdiff = '/path/to/filterdiff'
83 "
83 "
84 "
84 "
85 " Usage:
85 " Usage:
86 "
86 "
87 " Please see :help patchreview or :help diffreview for details.
87 " Please see :help patchreview or :help diffreview for details.
88 "
88 "
89 ""}}}
89 ""}}}
90
90
91 " Enabled only during development
91 " Enabled only during development
92 " unlet! g:loaded_patchreview " DEBUG
92 " unlet! g:loaded_patchreview " DEBUG
93 " unlet! g:patchreview_patch " DEBUG
93 " unlet! g:patchreview_patch " DEBUG
94 " unlet! g:patchreview_filterdiff " DEBUG
94 " unlet! g:patchreview_filterdiff " DEBUG
95 " let g:patchreview_patch = 'patch' " DEBUG
95 " let g:patchreview_patch = 'patch' " DEBUG
96
96
97 if v:version < 700
97 if v:version < 700
98 finish
98 finish
99 endif
99 endif
100 if ! has('diff')
100 if ! has('diff')
101 call confirm('patchreview.vim plugin needs (G)VIM built with +diff support to work.')
101 call confirm('patchreview.vim plugin needs (G)VIM built with +diff support to work.')
102 finish
102 finish
103 endif
103 endif
104
104
105 " load only once
105 " load only once
106 if (! exists('g:patchreview_debug') && exists('g:loaded_patchreview')) || &compatible
106 if (! exists('g:patchreview_debug') && exists('g:loaded_patchreview')) || &compatible
107 finish
107 finish
108 endif
108 endif
109 let g:loaded_patchreview="0.2.2"
109 let g:loaded_patchreview="0.2.2"
110
110
111 let s:msgbufname = '-PatchReviewMessages-'
111 let s:msgbufname = '-PatchReviewMessages-'
112
112
113 function! <SID>Debug(str) "{{{
113 function! <SID>Debug(str) "{{{
114 if exists('g:patchreview_debug')
114 if exists('g:patchreview_debug')
115 Pecho 'DEBUG: ' . a:str
115 Pecho 'DEBUG: ' . a:str
116 endif
116 endif
117 endfunction
117 endfunction
118 command! -nargs=+ -complete=expression Debug call s:Debug(<args>)
118 command! -nargs=+ -complete=expression Debug call s:Debug(<args>)
119 "}}}
119 "}}}
120
120
121 function! <SID>PR_wipeMsgBuf() "{{{
121 function! <SID>PR_wipeMsgBuf() "{{{
122 let winnum = bufwinnr(s:msgbufname)
122 let winnum = bufwinnr(s:msgbufname)
123 if winnum != -1 " If the window is already open, jump to it
123 if winnum != -1 " If the window is already open, jump to it
124 let cur_winnr = winnr()
124 let cur_winnr = winnr()
125 if winnr() != winnum
125 if winnr() != winnum
126 exe winnum . 'wincmd w'
126 exe winnum . 'wincmd w'
127 exe 'bw'
127 exe 'bw'
128 exe cur_winnr . 'wincmd w'
128 exe cur_winnr . 'wincmd w'
129 endif
129 endif
130 endif
130 endif
131 endfunction
131 endfunction
132 "}}}
132 "}}}
133
133
134 function! <SID>Pecho(...) "{{{
134 function! <SID>Pecho(...) "{{{
135 " Usage: Pecho(msg, [return_to_original_window_flag])
135 " Usage: Pecho(msg, [return_to_original_window_flag])
136 " default return_to_original_window_flag = 0
136 " default return_to_original_window_flag = 0
137 "
137 "
138 let cur_winnr = winnr()
138 let cur_winnr = winnr()
139 let winnum = bufwinnr(s:msgbufname)
139 let winnum = bufwinnr(s:msgbufname)
140 if winnum != -1 " If the window is already open, jump to it
140 if winnum != -1 " If the window is already open, jump to it
141 if winnr() != winnum
141 if winnr() != winnum
142 exe winnum . 'wincmd w'
142 exe winnum . 'wincmd w'
143 endif
143 endif
144 else
144 else
145 let bufnum = bufnr(s:msgbufname)
145 let bufnum = bufnr(s:msgbufname)
146 if bufnum == -1
146 if bufnum == -1
147 let wcmd = s:msgbufname
147 let wcmd = s:msgbufname
148 else
148 else
149 let wcmd = '+buffer' . bufnum
149 let wcmd = '+buffer' . bufnum
150 endif
150 endif
151 exe 'silent! botright 5split ' . wcmd
151 exe 'silent! botright 5split ' . wcmd
152 endif
152 endif
153 setlocal modifiable
153 setlocal modifiable
154 setlocal buftype=nofile
154 setlocal buftype=nofile
155 setlocal bufhidden=delete
155 setlocal bufhidden=delete
156 setlocal noswapfile
156 setlocal noswapfile
157 setlocal nowrap
157 setlocal nowrap
158 setlocal nobuflisted
158 setlocal nobuflisted
159 if a:0 != 0
159 if a:0 != 0
160 silent! $put =a:1
160 silent! $put =a:1
161 endif
161 endif
162 exe ':$'
162 exe ':$'
163 setlocal nomodifiable
163 setlocal nomodifiable
164 if a:0 > 1 && a:2
164 if a:0 > 1 && a:2
165 exe cur_winnr . 'wincmd w'
165 exe cur_winnr . 'wincmd w'
166 endif
166 endif
167 endfunction
167 endfunction
168
168
169 command! -nargs=+ -complete=expression Pecho call s:Pecho(<args>)
169 command! -nargs=+ -complete=expression Pecho call s:Pecho(<args>)
170 "}}}
170 "}}}
171
171
172 function! <SID>PR_checkBinary(BinaryName) "{{{
172 function! <SID>PR_checkBinary(BinaryName) "{{{
173 " Verify that BinaryName is specified or available
173 " Verify that BinaryName is specified or available
174 if ! exists('g:patchreview_' . a:BinaryName)
174 if ! exists('g:patchreview_' . a:BinaryName)
175 if executable(a:BinaryName)
175 if executable(a:BinaryName)
176 let g:patchreview_{a:BinaryName} = a:BinaryName
176 let g:patchreview_{a:BinaryName} = a:BinaryName
177 return 1
177 return 1
178 else
178 else
179 Pecho 'g:patchreview_' . a:BinaryName . ' is not defined and ' . a:BinaryName . ' command could not be found on path.'
179 Pecho 'g:patchreview_' . a:BinaryName . ' is not defined and ' . a:BinaryName . ' command could not be found on path.'
180 Pecho 'Please define it in your .vimrc.'
180 Pecho 'Please define it in your .vimrc.'
181 return 0
181 return 0
182 endif
182 endif
183 elseif ! executable(g:patchreview_{a:BinaryName})
183 elseif ! executable(g:patchreview_{a:BinaryName})
184 Pecho 'Specified g:patchreview_' . a:BinaryName . ' [' . g:patchreview_{a:BinaryName} . '] is not executable.'
184 Pecho 'Specified g:patchreview_' . a:BinaryName . ' [' . g:patchreview_{a:BinaryName} . '] is not executable.'
185 return 0
185 return 0
186 else
186 else
187 return 1
187 return 1
188 endif
188 endif
189 endfunction
189 endfunction
190 "}}}
190 "}}}
191
191
192 function! <SID>ExtractDiffsNative(...) "{{{
192 function! <SID>ExtractDiffsNative(...) "{{{
193 " Sets g:patches = {'reason':'', 'patch':[
193 " Sets g:patches = {'reason':'', 'patch':[
194 " {
194 " {
195 " 'filename': filepath
195 " 'filename': filepath
196 " 'type' : '+' | '-' | '!'
196 " 'type' : '+' | '-' | '!'
197 " 'content' : patch text for this file
197 " 'content' : patch text for this file
198 " },
198 " },
199 " ...
199 " ...
200 " ]}
200 " ]}
201 let g:patches = {'reason' : '', 'patch' : []}
201 let g:patches = {'reason' : '', 'patch' : []}
202 " TODO : User pointers into lines list rather then use collect
202 " TODO : User pointers into lines list rather then use collect
203 if a:0 == 0
203 if a:0 == 0
204 let g:patches['reason'] = "ExtractDiffsNative expects at least a patchfile argument"
204 let g:patches['reason'] = "ExtractDiffsNative expects at least a patchfile argument"
205 return
205 return
206 endif
206 endif
207 let patchfile = expand(a:1, ':p')
207 let patchfile = expand(a:1, ':p')
208 if a:0 > 1
208 if a:0 > 1
209 let patch = a:2
209 let patch = a:2
210 endif
210 endif
211 if ! filereadable(patchfile)
211 if ! filereadable(patchfile)
212 let g:patches['reason'] = "File " . patchfile . " is not readable"
212 let g:patches['reason'] = "File " . patchfile . " is not readable"
213 return
213 return
214 endif
214 endif
215 unlet! filterdiffcmd
215 unlet! filterdiffcmd
216 let filterdiffcmd = '' . g:patchreview_filterdiff . ' --list -s ' . patchfile
216 let filterdiffcmd = '' . g:patchreview_filterdiff . ' --list -s ' . patchfile
217 let fileslist = split(system(filterdiffcmd), '[\r\n]')
217 let fileslist = split(system(filterdiffcmd), '[\r\n]')
218 for filewithchangetype in fileslist
218 for filewithchangetype in fileslist
219 if filewithchangetype !~ '^[!+-] '
219 if filewithchangetype !~ '^[!+-] '
220 Pecho '*** Skipping review generation due to unknown change for [' . filewithchangetype . ']'
220 Pecho '*** Skipping review generation due to unknown change for [' . filewithchangetype . ']'
221 continue
221 continue
222 endif
222 endif
223
223
224 unlet! this_patch
224 unlet! this_patch
225 let this_patch = {}
225 let this_patch = {}
226
226
227 unlet! relpath
227 unlet! relpath
228 let relpath = substitute(filewithchangetype, '^. ', '', '')
228 let relpath = substitute(filewithchangetype, '^. ', '', '')
229
229
230 let this_patch['filename'] = relpath
230 let this_patch['filename'] = relpath
231
231
232 if filewithchangetype =~ '^! '
232 if filewithchangetype =~ '^! '
233 let this_patch['type'] = '!'
233 let this_patch['type'] = '!'
234 elseif filewithchangetype =~ '^+ '
234 elseif filewithchangetype =~ '^+ '
235 let this_patch['type'] = '+'
235 let this_patch['type'] = '+'
236 elseif filewithchangetype =~ '^- '
236 elseif filewithchangetype =~ '^- '
237 let this_patch['type'] = '-'
237 let this_patch['type'] = '-'
238 endif
238 endif
239
239
240 unlet! filterdiffcmd
240 unlet! filterdiffcmd
241 let filterdiffcmd = '' . g:patchreview_filterdiff . ' -i ' . relpath . ' ' . patchfile
241 let filterdiffcmd = '' . g:patchreview_filterdiff . ' -i ' . relpath . ' ' . patchfile
242 let this_patch['content'] = split(system(filterdiffcmd), '[\n\r]')
242 let this_patch['content'] = split(system(filterdiffcmd), '[\n\r]')
243 let g:patches['patch'] += [this_patch]
243 let g:patches['patch'] += [this_patch]
244 Debug "Patch collected for " . relpath
244 Debug "Patch collected for " . relpath
245 endfor
245 endfor
246 endfunction
246 endfunction
247 "}}}
247 "}}}
248
248
249 function! <SID>ExtractDiffsPureVim(...) "{{{
249 function! <SID>ExtractDiffsPureVim(...) "{{{
250 " Sets g:patches = {'reason':'', 'patch':[
250 " Sets g:patches = {'reason':'', 'patch':[
251 " {
251 " {
252 " 'filename': filepath
252 " 'filename': filepath
253 " 'type' : '+' | '-' | '!'
253 " 'type' : '+' | '-' | '!'
254 " 'content' : patch text for this file
254 " 'content' : patch text for this file
255 " },
255 " },
256 " ...
256 " ...
257 " ]}
257 " ]}
258 let g:patches = {'reason' : '', 'patch' : []}
258 let g:patches = {'reason' : '', 'patch' : []}
259 " TODO : User pointers into lines list rather then use collect
259 " TODO : User pointers into lines list rather then use collect
260 if a:0 == 0
260 if a:0 == 0
261 let g:patches['reason'] = "ExtractDiffsPureVim expects at least a patchfile argument"
261 let g:patches['reason'] = "ExtractDiffsPureVim expects at least a patchfile argument"
262 return
262 return
263 endif
263 endif
264 let patchfile = expand(a:1, ':p')
264 let patchfile = expand(a:1, ':p')
265 if a:0 > 1
265 if a:0 > 1
266 let patch = a:2
266 let patch = a:2
267 endif
267 endif
268 if ! filereadable(patchfile)
268 if ! filereadable(patchfile)
269 let g:patches['reason'] = "File " . patchfile . " is not readable"
269 let g:patches['reason'] = "File " . patchfile . " is not readable"
270 return
270 return
271 endif
271 endif
272 call s:PR_wipeMsgBuf()
272 call s:PR_wipeMsgBuf()
273 let collect = []
273 let collect = []
274 let linum = 0
274 let linum = 0
275 let lines = readfile(patchfile)
275 let lines = readfile(patchfile)
276 let linescount = len(lines)
276 let linescount = len(lines)
277 State 'START'
277 State 'START'
278 while linum < linescount
278 while linum < linescount
279 let line = lines[linum]
279 let line = lines[linum]
280 let linum += 1
280 let linum += 1
281 if State() == 'START'
281 if State() == 'START'
282 let mat = matchlist(line, '^--- \([^\t]\+\).*$')
282 let mat = matchlist(line, '^--- \([^\t]\+\).*$')
283 if ! empty(mat) && mat[1] != ''
283 if ! empty(mat) && mat[1] != ''
284 State 'MAYBE_UNIFIED_DIFF'
284 State 'MAYBE_UNIFIED_DIFF'
285 let p_first_file = mat[1]
285 let p_first_file = mat[1]
286 let collect = [line]
286 let collect = [line]
287 Debug line . State()
287 Debug line . State()
288 continue
288 continue
289 endif
289 endif
290 let mat = matchlist(line, '^\*\*\* \([^\t]\+\).*$')
290 let mat = matchlist(line, '^\*\*\* \([^\t]\+\).*$')
291 if ! empty(mat) && mat[1] != ''
291 if ! empty(mat) && mat[1] != ''
292 State 'MAYBE_CONTEXT_DIFF'
292 State 'MAYBE_CONTEXT_DIFF'
293 let p_first_file = mat[1]
293 let p_first_file = mat[1]
294 let collect = [line]
294 let collect = [line]
295 Debug line . State()
295 Debug line . State()
296 continue
296 continue
297 endif
297 endif
298 continue
298 continue
299 elseif State() == 'MAYBE_CONTEXT_DIFF'
299 elseif State() == 'MAYBE_CONTEXT_DIFF'
300 let mat = matchlist(line, '^--- \([^\t]\+\).*$')
300 let mat = matchlist(line, '^--- \([^\t]\+\).*$')
301 if empty(mat) || mat[1] == ''
301 if empty(mat) || mat[1] == ''
302 State 'START'
302 State 'START'
303 let linum -= 1
303 let linum -= 1
304 continue
304 continue
305 Debug 'Back to square one ' . line()
305 Debug 'Back to square one ' . line()
306 endif
306 endif
307 let p_second_file = mat[1]
307 let p_second_file = mat[1]
308 if p_first_file == '/dev/null'
308 if p_first_file == '/dev/null'
309 if p_second_file == '/dev/null'
309 if p_second_file == '/dev/null'
310 let g:patches['reason'] = "Malformed diff found at line " . linum
310 let g:patches['reason'] = "Malformed diff found at line " . linum
311 return
311 return
312 endif
312 endif
313 let p_type = '+'
313 let p_type = '+'
314 let filepath = p_second_file
314 let filepath = p_second_file
315 else
315 else
316 if p_second_file == '/dev/null'
316 if p_second_file == '/dev/null'
317 let p_type = '-'
317 let p_type = '-'
318 let filepath = p_first_file
318 let filepath = p_first_file
319 else
319 else
320 let p_type = '!'
320 let p_type = '!'
321 let filepath = p_first_file
321 let filepath = p_first_file
322 endif
322 endif
323 endif
323 endif
324 State 'EXPECT_15_STARS'
324 State 'EXPECT_15_STARS'
325 let collect += [line]
325 let collect += [line]
326 Debug line . State()
326 Debug line . State()
327 elseif State() == 'EXPECT_15_STARS'
327 elseif State() == 'EXPECT_15_STARS'
328 if line !~ '^*\{15}$'
328 if line !~ '^*\{15}$'
329 State 'START'
329 State 'START'
330 let linum -= 1
330 let linum -= 1
331 Debug line . State()
331 Debug line . State()
332 continue
332 continue
333 endif
333 endif
334 State 'EXPECT_CONTEXT_CHUNK_HEADER_1'
334 State 'EXPECT_CONTEXT_CHUNK_HEADER_1'
335 let collect += [line]
335 let collect += [line]
336 Debug line . State()
336 Debug line . State()
337 elseif State() == 'EXPECT_CONTEXT_CHUNK_HEADER_1'
337 elseif State() == 'EXPECT_CONTEXT_CHUNK_HEADER_1'
338 let mat = matchlist(line, '^\*\*\* \(\d\+,\)\?\(\d\+\) \*\*\*\*$')
338 let mat = matchlist(line, '^\*\*\* \(\d\+,\)\?\(\d\+\) \*\*\*\*$')
339 if empty(mat) || mat[1] == ''
339 if empty(mat) || mat[1] == ''
340 State 'START'
340 State 'START'
341 let linum -= 1
341 let linum -= 1
342 Debug line . State()
342 Debug line . State()
343 continue
343 continue
344 endif
344 endif
345 let collect += [line]
345 let collect += [line]
346 State 'SKIP_CONTEXT_STUFF_1'
346 State 'SKIP_CONTEXT_STUFF_1'
347 Debug line . State()
347 Debug line . State()
348 continue
348 continue
349 elseif State() == 'SKIP_CONTEXT_STUFF_1'
349 elseif State() == 'SKIP_CONTEXT_STUFF_1'
350 if line !~ '^[ !+].*$'
350 if line !~ '^[ !+].*$'
351 let mat = matchlist(line, '^--- \(\d\+\),\(\d\+\) ----$')
351 let mat = matchlist(line, '^--- \(\d\+\),\(\d\+\) ----$')
352 if ! empty(mat) && mat[1] != '' && mat[2] != ''
352 if ! empty(mat) && mat[1] != '' && mat[2] != ''
353 let goal_count = mat[2] - mat[1] + 1
353 let goal_count = mat[2] - mat[1] + 1
354 let c_count = 0
354 let c_count = 0
355 State 'READ_CONTEXT_CHUNK'
355 State 'READ_CONTEXT_CHUNK'
356 let collect += [line]
356 let collect += [line]
357 Debug line . State() . " Goal count set to " . goal_count
357 Debug line . State() . " Goal count set to " . goal_count
358 continue
358 continue
359 endif
359 endif
360 State 'START'
360 State 'START'
361 let linum -= 1
361 let linum -= 1
362 Debug line . State()
362 Debug line . State()
363 continue
363 continue
364 endif
364 endif
365 let collect += [line]
365 let collect += [line]
366 continue
366 continue
367 elseif State() == 'READ_CONTEXT_CHUNK'
367 elseif State() == 'READ_CONTEXT_CHUNK'
368 let c_count += 1
368 let c_count += 1
369 if c_count == goal_count
369 if c_count == goal_count
370 let collect += [line]
370 let collect += [line]
371 State 'BACKSLASH_OR_CRANGE_EOF'
371 State 'BACKSLASH_OR_CRANGE_EOF'
372 continue
372 continue
373 else " goal not met yet
373 else " goal not met yet
374 let mat = matchlist(line, '^\([\\!+ ]\).*$')
374 let mat = matchlist(line, '^\([\\!+ ]\).*$')
375 if empty(mat) || mat[1] == ''
375 if empty(mat) || mat[1] == ''
376 let linum -= 1
376 let linum -= 1
377 State 'START'
377 State 'START'
378 Debug line . State()
378 Debug line . State()
379 continue
379 continue
380 endif
380 endif
381 let collect += [line]
381 let collect += [line]
382 continue
382 continue
383 endif
383 endif
384 elseif State() == 'BACKSLASH_OR_CRANGE_EOF'
384 elseif State() == 'BACKSLASH_OR_CRANGE_EOF'
385 if line =~ '^\\ No newline.*$' " XXX: Can we go to another chunk from here??
385 if line =~ '^\\ No newline.*$' " XXX: Can we go to another chunk from here??
386 let collect += [line]
386 let collect += [line]
387 let this_patch = {}
387 let this_patch = {}
388 let this_patch['filename'] = filepath
388 let this_patch['filename'] = filepath
389 let this_patch['type'] = p_type
389 let this_patch['type'] = p_type
390 let this_patch['content'] = collect
390 let this_patch['content'] = collect
391 let g:patches['patch'] += [this_patch]
391 let g:patches['patch'] += [this_patch]
392 Debug "Patch collected for " . filepath
392 Debug "Patch collected for " . filepath
393 State 'START'
393 State 'START'
394 continue
394 continue
395 endif
395 endif
396 if line =~ '^\*\{15}$'
396 if line =~ '^\*\{15}$'
397 let collect += [line]
397 let collect += [line]
398 State 'EXPECT_CONTEXT_CHUNK_HEADER_1'
398 State 'EXPECT_CONTEXT_CHUNK_HEADER_1'
399 Debug line . State()
399 Debug line . State()
400 continue
400 continue
401 endif
401 endif
402 let this_patch = {}
402 let this_patch = {}
403 let this_patch['filename'] = filepath
403 let this_patch['filename'] = filepath
404 let this_patch['type'] = p_type
404 let this_patch['type'] = p_type
405 let this_patch['content'] = collect
405 let this_patch['content'] = collect
406 let g:patches['patch'] += [this_patch]
406 let g:patches['patch'] += [this_patch]
407 let linum -= 1
407 let linum -= 1
408 State 'START'
408 State 'START'
409 Debug "Patch collected for " . filepath
409 Debug "Patch collected for " . filepath
410 Debug line . State()
410 Debug line . State()
411 continue
411 continue
412 elseif State() == 'MAYBE_UNIFIED_DIFF'
412 elseif State() == 'MAYBE_UNIFIED_DIFF'
413 let mat = matchlist(line, '^+++ \([^\t]\+\).*$')
413 let mat = matchlist(line, '^+++ \([^\t]\+\).*$')
414 if empty(mat) || mat[1] == ''
414 if empty(mat) || mat[1] == ''
415 State 'START'
415 State 'START'
416 let linum -= 1
416 let linum -= 1
417 Debug line . State()
417 Debug line . State()
418 continue
418 continue
419 endif
419 endif
420 let p_second_file = mat[1]
420 let p_second_file = mat[1]
421 if p_first_file == '/dev/null'
421 if p_first_file == '/dev/null'
422 if p_second_file == '/dev/null'
422 if p_second_file == '/dev/null'
423 let g:patches['reason'] = "Malformed diff found at line " . linum
423 let g:patches['reason'] = "Malformed diff found at line " . linum
424 return
424 return
425 endif
425 endif
426 let p_type = '+'
426 let p_type = '+'
427 let filepath = p_second_file
427 let filepath = p_second_file
428 else
428 else
429 if p_second_file == '/dev/null'
429 if p_second_file == '/dev/null'
430 let p_type = '-'
430 let p_type = '-'
431 let filepath = p_first_file
431 let filepath = p_first_file
432 else
432 else
433 let p_type = '!'
433 let p_type = '!'
434 let filepath = p_first_file
434 let filepath = p_first_file
435 endif
435 endif
436 endif
436 endif
437 State 'EXPECT_UNIFIED_RANGE_CHUNK'
437 State 'EXPECT_UNIFIED_RANGE_CHUNK'
438 let collect += [line]
438 let collect += [line]
439 Debug line . State()
439 Debug line . State()
440 continue
440 continue
441 elseif State() == 'EXPECT_UNIFIED_RANGE_CHUNK'
441 elseif State() == 'EXPECT_UNIFIED_RANGE_CHUNK'
442 let mat = matchlist(line, '^@@ -\(\d\+,\)\?\(\d\+\) +\(\d\+,\)\?\(\d\+\) @@$')
442 let mat = matchlist(line, '^@@ -\(\d\+,\)\?\(\d\+\) +\(\d\+,\)\?\(\d\+\) @@$')
443 if ! empty(mat)
443 if ! empty(mat)
444 let old_goal_count = mat[2]
444 let old_goal_count = mat[2]
445 let new_goal_count = mat[4]
445 let new_goal_count = mat[4]
446 let o_count = 0
446 let o_count = 0
447 let n_count = 0
447 let n_count = 0
448 Debug "Goal count set to " . old_goal_count . ', ' . new_goal_count
448 Debug "Goal count set to " . old_goal_count . ', ' . new_goal_count
449 State 'READ_UNIFIED_CHUNK'
449 State 'READ_UNIFIED_CHUNK'
450 let collect += [line]
450 let collect += [line]
451 Debug line . State()
451 Debug line . State()
452 continue
452 continue
453 endif
453 endif
454 State 'START'
454 State 'START'
455 Debug line . State()
455 Debug line . State()
456 continue
456 continue
457 elseif State() == 'READ_UNIFIED_CHUNK'
457 elseif State() == 'READ_UNIFIED_CHUNK'
458 if o_count == old_goal_count && n_count == new_goal_count
458 if o_count == old_goal_count && n_count == new_goal_count
459 if line =~ '^\\.*$' " XXX: Can we go to another chunk from here??
459 if line =~ '^\\.*$' " XXX: Can we go to another chunk from here??
460 let collect += [line]
460 let collect += [line]
461 let this_patch = {}
461 let this_patch = {}
462 let this_patch['filename'] = filepath
462 let this_patch['filename'] = filepath
463 let this_patch['type'] = p_type
463 let this_patch['type'] = p_type
464 let this_patch['content'] = collect
464 let this_patch['content'] = collect
465 let g:patches['patch'] += [this_patch]
465 let g:patches['patch'] += [this_patch]
466 Debug "Patch collected for " . filepath
466 Debug "Patch collected for " . filepath
467 State 'START'
467 State 'START'
468 continue
468 continue
469 endif
469 endif
470 let mat = matchlist(line, '^@@ -\(\d\+,\)\?\(\d\+\) +\(\d\+,\)\?\(\d\+\) @@$')
470 let mat = matchlist(line, '^@@ -\(\d\+,\)\?\(\d\+\) +\(\d\+,\)\?\(\d\+\) @@$')
471 if ! empty(mat)
471 if ! empty(mat)
472 let old_goal_count = mat[2]
472 let old_goal_count = mat[2]
473 let new_goal_count = mat[4]
473 let new_goal_count = mat[4]
474 let o_count = 0
474 let o_count = 0
475 let n_count = 0
475 let n_count = 0
476 Debug "Goal count set to " . old_goal_count . ', ' . new_goal_count
476 Debug "Goal count set to " . old_goal_count . ', ' . new_goal_count
477 let collect += [line]
477 let collect += [line]
478 Debug line . State()
478 Debug line . State()
479 continue
479 continue
480 endif
480 endif
481 let this_patch = {}
481 let this_patch = {}
482 let this_patch['filename'] = filepath
482 let this_patch['filename'] = filepath
483 let this_patch['type'] = p_type
483 let this_patch['type'] = p_type
484 let this_patch['content'] = collect
484 let this_patch['content'] = collect
485 let g:patches['patch'] += [this_patch]
485 let g:patches['patch'] += [this_patch]
486 Debug "Patch collected for " . filepath
486 Debug "Patch collected for " . filepath
487 let linum -= 1
487 let linum -= 1
488 State 'START'
488 State 'START'
489 Debug line . State()
489 Debug line . State()
490 continue
490 continue
491 else " goal not met yet
491 else " goal not met yet
492 let mat = matchlist(line, '^\([\\+ -]\).*$')
492 let mat = matchlist(line, '^\([\\+ -]\).*$')
493 if empty(mat) || mat[1] == ''
493 if empty(mat) || mat[1] == ''
494 let linum -= 1
494 let linum -= 1
495 State 'START'
495 State 'START'
496 continue
496 continue
497 endif
497 endif
498 let chr = mat[1]
498 let chr = mat[1]
499 if chr == '+'
499 if chr == '+'
500 let n_count += 1
500 let n_count += 1
501 endif
501 endif
502 if chr == ' '
502 if chr == ' '
503 let o_count += 1
503 let o_count += 1
504 let n_count += 1
504 let n_count += 1
505 endif
505 endif
506 if chr == '-'
506 if chr == '-'
507 let o_count += 1
507 let o_count += 1
508 endif
508 endif
509 let collect += [line]
509 let collect += [line]
510 Debug line . State()
510 Debug line . State()
511 continue
511 continue
512 endif
512 endif
513 else
513 else
514 let g:patches['reason'] = "Internal error: Do not use the plugin anymore and if possible please send the diff or patch file you tried it with to Manpreet Singh <junkblocker@yahoo.com>"
514 let g:patches['reason'] = "Internal error: Do not use the plugin anymore and if possible please send the diff or patch file you tried it with to Manpreet Singh <junkblocker@yahoo.com>"
515 return
515 return
516 endif
516 endif
517 endwhile
517 endwhile
518 "Pecho State()
518 "Pecho State()
519 if (State() == 'READ_CONTEXT_CHUNK' && c_count == goal_count) || (State() == 'READ_UNIFIED_CHUNK' && n_count == new_goal_count && o_count == old_goal_count)
519 if (State() == 'READ_CONTEXT_CHUNK' && c_count == goal_count) || (State() == 'READ_UNIFIED_CHUNK' && n_count == new_goal_count && o_count == old_goal_count)
520 let this_patch = {}
520 let this_patch = {}
521 let this_patch['filename'] = filepath
521 let this_patch['filename'] = filepath
522 let this_patch['type'] = p_type
522 let this_patch['type'] = p_type
523 let this_patch['content'] = collect
523 let this_patch['content'] = collect
524 let g:patches['patch'] += [this_patch]
524 let g:patches['patch'] += [this_patch]
525 Debug "Patch collected for " . filepath
525 Debug "Patch collected for " . filepath
526 endif
526 endif
527 return
527 return
528 endfunction
528 endfunction
529 "}}}
529 "}}}
530
530
531 function! State(...) " For easy manipulation of diff extraction state "{{{
531 function! State(...) " For easy manipulation of diff extraction state "{{{
532 if a:0 != 0
532 if a:0 != 0
533 let s:STATE = a:1
533 let s:STATE = a:1
534 else
534 else
535 if ! exists('s:STATE')
535 if ! exists('s:STATE')
536 let s:STATE = 'START'
536 let s:STATE = 'START'
537 endif
537 endif
538 return s:STATE
538 return s:STATE
539 endif
539 endif
540 endfunction
540 endfunction
541 com! -nargs=+ -complete=expression State call State(<args>)
541 com! -nargs=+ -complete=expression State call State(<args>)
542 "}}}
542 "}}}
543
543
544 function! <SID>PatchReview(...) "{{{
544 function! <SID>PatchReview(...) "{{{
545 let s:save_shortmess = &shortmess
545 let s:save_shortmess = &shortmess
546 let s:save_aw = &autowrite
546 let s:save_aw = &autowrite
547 let s:save_awa = &autowriteall
547 let s:save_awa = &autowriteall
548 set shortmess=aW
548 set shortmess=aW
549 call s:PR_wipeMsgBuf()
549 call s:PR_wipeMsgBuf()
550 let s:reviewmode = 'patch'
550 let s:reviewmode = 'patch'
551 call s:_GenericReview(a:000)
551 call s:_GenericReview(a:000)
552 let &autowriteall = s:save_awa
552 let &autowriteall = s:save_awa
553 let &autowrite = s:save_aw
553 let &autowrite = s:save_aw
554 let &shortmess = s:save_shortmess
554 let &shortmess = s:save_shortmess
555 endfunction
555 endfunction
556 "}}}
556 "}}}
557
557
558 function! <SID>_GenericReview(argslist) "{{{
558 function! <SID>_GenericReview(argslist) "{{{
559 " diff mode:
559 " diff mode:
560 " arg1 = patchfile
560 " arg1 = patchfile
561 " arg2 = strip count
561 " arg2 = strip count
562 " patch mode:
562 " patch mode:
563 " arg1 = patchfile
563 " arg1 = patchfile
564 " arg2 = strip count
564 " arg2 = strip count
565 " arg3 = directory
565 " arg3 = directory
566
566
567 " VIM 7+ required
567 " VIM 7+ required
568 if version < 700
568 if version < 700
569 Pecho 'This plugin needs VIM 7 or higher'
569 Pecho 'This plugin needs VIM 7 or higher'
570 return
570 return
571 endif
571 endif
572
572
573 " +diff required
573 " +diff required
574 if ! has('diff')
574 if ! has('diff')
575 Pecho 'This plugin needs VIM built with +diff feature.'
575 Pecho 'This plugin needs VIM built with +diff feature.'
576 return
576 return
577 endif
577 endif
578
578
579
579
580 if s:reviewmode == 'diff'
580 if s:reviewmode == 'diff'
581 let patch_R_option = ' -t -R '
581 let patch_R_option = ' -t -R '
582 elseif s:reviewmode == 'patch'
582 elseif s:reviewmode == 'patch'
583 let patch_R_option = ''
583 let patch_R_option = ''
584 else
584 else
585 Pecho 'Fatal internal error in patchreview.vim plugin'
585 Pecho 'Fatal internal error in patchreview.vim plugin'
586 return
586 return
587 endif
587 endif
588
588
589 " Check passed arguments
589 " Check passed arguments
590 if len(a:argslist) == 0
590 if len(a:argslist) == 0
591 Pecho 'PatchReview command needs at least one argument specifying a patchfile path.'
591 Pecho 'PatchReview command needs at least one argument specifying a patchfile path.'
592 return
592 return
593 endif
593 endif
594 let StripCount = 0
594 let StripCount = 0
595 if len(a:argslist) >= 1 && ((s:reviewmode == 'patch' && len(a:argslist) <= 3) || (s:reviewmode == 'diff' && len(a:argslist) == 2))
595 if len(a:argslist) >= 1 && ((s:reviewmode == 'patch' && len(a:argslist) <= 3) || (s:reviewmode == 'diff' && len(a:argslist) == 2))
596 let PatchFilePath = expand(a:argslist[0], ':p')
596 let PatchFilePath = expand(a:argslist[0], ':p')
597 if ! filereadable(PatchFilePath)
597 if ! filereadable(PatchFilePath)
598 Pecho 'File [' . PatchFilePath . '] is not accessible.'
598 Pecho 'File [' . PatchFilePath . '] is not accessible.'
599 return
599 return
600 endif
600 endif
601 if len(a:argslist) >= 2 && s:reviewmode == 'patch'
601 if len(a:argslist) >= 2 && s:reviewmode == 'patch'
602 let s:SrcDirectory = expand(a:argslist[1], ':p')
602 let s:SrcDirectory = expand(a:argslist[1], ':p')
603 if ! isdirectory(s:SrcDirectory)
603 if ! isdirectory(s:SrcDirectory)
604 Pecho '[' . s:SrcDirectory . '] is not a directory'
604 Pecho '[' . s:SrcDirectory . '] is not a directory'
605 return
605 return
606 endif
606 endif
607 try
607 try
608 " Command line has already escaped the path
608 " Command line has already escaped the path
609 exe 'cd ' . s:SrcDirectory
609 exe 'cd ' . s:SrcDirectory
610 catch /^.*E344.*/
610 catch /^.*E344.*/
611 Pecho 'Could not change to directory [' . s:SrcDirectory . ']'
611 Pecho 'Could not change to directory [' . s:SrcDirectory . ']'
612 return
612 return
613 endtry
613 endtry
614 endif
614 endif
615 if s:reviewmode == 'diff'
615 if s:reviewmode == 'diff'
616 " passed in by default
616 " passed in by default
617 let StripCount = eval(a:argslist[1])
617 let StripCount = eval(a:argslist[1])
618 elseif s:reviewmode == 'patch'
618 elseif s:reviewmode == 'patch'
619 let StripCount = 1
619 let StripCount = 1
620 " optional strip count
620 " optional strip count
621 if len(a:argslist) == 3
621 if len(a:argslist) == 3
622 let StripCount = eval(a:argslist[2])
622 let StripCount = eval(a:argslist[2])
623 endif
623 endif
624 endif
624 endif
625 else
625 else
626 if s:reviewmode == 'patch'
626 if s:reviewmode == 'patch'
627 Pecho 'PatchReview command needs at most three arguments: patchfile path, optional source directory path and optional strip count.'
627 Pecho 'PatchReview command needs at most three arguments: patchfile path, optional source directory path and optional strip count.'
628 elseif s:reviewmode == 'diff'
628 elseif s:reviewmode == 'diff'
629 Pecho 'DiffReview command accepts no arguments.'
629 Pecho 'DiffReview command accepts no arguments.'
630 endif
630 endif
631 return
631 return
632 endif
632 endif
633
633
634 " Verify that patch command and temporary directory are available or specified
634 " Verify that patch command and temporary directory are available or specified
635 if ! s:PR_checkBinary('patch')
635 if ! s:PR_checkBinary('patch')
636 return
636 return
637 endif
637 endif
638
638
639 " Requirements met, now execute
639 " Requirements met, now execute
640 let PatchFilePath = fnamemodify(PatchFilePath, ':p')
640 let PatchFilePath = fnamemodify(PatchFilePath, ':p')
641 if s:reviewmode == 'patch'
641 if s:reviewmode == 'patch'
642 Pecho 'Patch file : ' . PatchFilePath
642 Pecho 'Patch file : ' . PatchFilePath
643 endif
643 endif
644 Pecho 'Source directory: ' . getcwd()
644 Pecho 'Source directory: ' . getcwd()
645 Pecho '------------------'
645 Pecho '------------------'
646 if s:PR_checkBinary('filterdiff')
646 if s:PR_checkBinary('filterdiff')
647 Debug "Using filterdiff"
647 Debug "Using filterdiff"
648 call s:ExtractDiffsNative(PatchFilePath)
648 call s:ExtractDiffsNative(PatchFilePath)
649 else
649 else
650 Debug "Using own diff extraction (slower)"
650 Debug "Using own diff extraction (slower)"
651 call s:ExtractDiffsPureVim(PatchFilePath)
651 call s:ExtractDiffsPureVim(PatchFilePath)
652 endif
652 endif
653 for patch in g:patches['patch']
653 for patch in g:patches['patch']
654 if patch.type !~ '^[!+-]$'
654 if patch.type !~ '^[!+-]$'
655 Pecho '*** Skipping review generation due to unknown change [' . patch.type . ']', 1
655 Pecho '*** Skipping review generation due to unknown change [' . patch.type . ']', 1
656 continue
656 continue
657 endif
657 endif
658 unlet! relpath
658 unlet! relpath
659 let relpath = patch.filename
659 let relpath = patch.filename
660 " XXX: svn diff and hg diff produce different kind of outputs, one requires
660 " XXX: svn diff and hg diff produce different kind of outputs, one requires
661 " XXX: stripping but the other doesn't. We need to take care of that
661 " XXX: stripping but the other doesn't. We need to take care of that
662 let stripmore = StripCount
662 let stripmore = StripCount
663 let StrippedRelativeFilePath = relpath
663 let StrippedRelativeFilePath = relpath
664 while stripmore > 0
664 while stripmore > 0
665 " strip one
665 " strip one
666 let StrippedRelativeFilePath = substitute(StrippedRelativeFilePath, '^[^\\\/]\+[^\\\/]*[\\\/]' , '' , '')
666 let StrippedRelativeFilePath = substitute(StrippedRelativeFilePath, '^[^\\\/]\+[^\\\/]*[\\\/]' , '' , '')
667 let stripmore -= 1
667 let stripmore -= 1
668 endwhile
668 endwhile
669 if patch.type == '!'
669 if patch.type == '!'
670 if s:reviewmode == 'patch'
670 if s:reviewmode == 'patch'
671 let msgtype = 'Patch modifies file: '
671 let msgtype = 'Patch modifies file: '
672 elseif s:reviewmode == 'diff'
672 elseif s:reviewmode == 'diff'
673 let msgtype = 'File has changes: '
673 let msgtype = 'File has changes: '
674 endif
674 endif
675 elseif patch.type == '+'
675 elseif patch.type == '+'
676 if s:reviewmode == 'patch'
676 if s:reviewmode == 'patch'
677 let msgtype = 'Patch adds file : '
677 let msgtype = 'Patch adds file : '
678 elseif s:reviewmode == 'diff'
678 elseif s:reviewmode == 'diff'
679 let msgtype = 'New file : '
679 let msgtype = 'New file : '
680 endif
680 endif
681 elseif patch.type == '-'
681 elseif patch.type == '-'
682 if s:reviewmode == 'patch'
682 if s:reviewmode == 'patch'
683 let msgtype = 'Patch removes file : '
683 let msgtype = 'Patch removes file : '
684 elseif s:reviewmode == 'diff'
684 elseif s:reviewmode == 'diff'
685 let msgtype = 'Removed file : '
685 let msgtype = 'Removed file : '
686 endif
686 endif
687 endif
687 endif
688 let bufnum = bufnr(relpath)
688 let bufnum = bufnr(relpath)
689 if buflisted(bufnum) && getbufvar(bufnum, '&mod')
689 if buflisted(bufnum) && getbufvar(bufnum, '&mod')
690 Pecho 'Old buffer for file [' . relpath . '] exists in modified state. Skipping review.', 1
690 Pecho 'Old buffer for file [' . relpath . '] exists in modified state. Skipping review.', 1
691 continue
691 continue
692 endif
692 endif
693 let tmpname = tempname()
693 let tmpname = tempname()
694
694
695 " write patch for patch.filename into tmpname
695 " write patch for patch.filename into tmpname
696 call writefile(patch.content, tmpname)
696 call writefile(patch.content, tmpname)
697 if patch.type == '+' && s:reviewmode == 'patch'
697 if patch.type == '+' && s:reviewmode == 'patch'
698 let inputfile = ''
698 let inputfile = ''
699 let patchcmd = '!' . g:patchreview_patch . patch_R_option . ' -o "' . tmpname . '.file" "' . inputfile . '" < "' . tmpname . '"'
699 let patchcmd = '!' . g:patchreview_patch . patch_R_option . ' -o "' . tmpname . '.file" "' . inputfile . '" < "' . tmpname . '"'
700 elseif patch.type == '+' && s:reviewmode == 'diff'
700 elseif patch.type == '+' && s:reviewmode == 'diff'
701 let inputfile = ''
701 let inputfile = ''
702 unlet! patchcmd
702 unlet! patchcmd
703 else
703 else
704 let inputfile = expand(StrippedRelativeFilePath, ':p')
704 let inputfile = expand(StrippedRelativeFilePath, ':p')
705 let patchcmd = '!' . g:patchreview_patch . patch_R_option . ' -o "' . tmpname . '.file" "' . inputfile . '" < "' . tmpname . '"'
705 let patchcmd = '!' . g:patchreview_patch . patch_R_option . ' -o "' . tmpname . '.file" "' . inputfile . '" < "' . tmpname . '"'
706 endif
706 endif
707 if exists('patchcmd')
707 if exists('patchcmd')
708 let v:errmsg = ''
708 let v:errmsg = ''
709 Debug patchcmd
709 Debug patchcmd
710 silent exe patchcmd
710 silent exe patchcmd
711 if v:errmsg != '' || v:shell_error
711 if v:errmsg != '' || v:shell_error
712 Pecho 'ERROR: Could not execute patch command.'
712 Pecho 'ERROR: Could not execute patch command.'
713 Pecho 'ERROR: ' . patchcmd
713 Pecho 'ERROR: ' . patchcmd
714 Pecho 'ERROR: ' . v:errmsg
714 Pecho 'ERROR: ' . v:errmsg
715 Pecho 'ERROR: Diff skipped.'
715 Pecho 'ERROR: Diff skipped.'
716 continue
716 continue
717 endif
717 endif
718 endif
718 endif
719 call delete(tmpname)
719 call delete(tmpname)
720 let s:origtabpagenr = tabpagenr()
720 let s:origtabpagenr = tabpagenr()
721 silent! exe 'tabedit ' . StrippedRelativeFilePath
721 silent! exe 'tabedit ' . StrippedRelativeFilePath
722 if exists('patchcmd')
722 if exists('patchcmd')
723 " modelines in loaded files mess with diff comparision
723 " modelines in loaded files mess with diff comparision
724 let s:keep_modeline=&modeline
724 let s:keep_modeline=&modeline
725 let &modeline=0
725 let &modeline=0
726 silent! exe 'vert diffsplit ' . tmpname . '.file'
726 silent! exe 'vert diffsplit ' . tmpname . '.file'
727 setlocal buftype=nofile
727 setlocal buftype=nofile
728 setlocal noswapfile
728 setlocal noswapfile
729 setlocal syntax=none
729 setlocal syntax=none
730 setlocal bufhidden=delete
730 setlocal bufhidden=delete
731 setlocal nobuflisted
731 setlocal nobuflisted
732 setlocal modifiable
732 setlocal modifiable
733 setlocal nowrap
733 setlocal nowrap
734 " Remove buffer name
734 " Remove buffer name
735 silent! 0f
735 silent! 0f
736 " Switch to original to get a nice tab title
736 " Switch to original to get a nice tab title
737 silent! wincmd p
737 silent! wincmd p
738 let &modeline=s:keep_modeline
738 let &modeline=s:keep_modeline
739 else
739 else
740 silent! exe 'vnew'
740 silent! exe 'vnew'
741 endif
741 endif
742 if filereadable(tmpname . '.file.rej')
742 if filereadable(tmpname . '.file.rej')
743 silent! exe 'topleft 5split ' . tmpname . '.file.rej'
743 silent! exe 'topleft 5split ' . tmpname . '.file.rej'
744 Pecho msgtype . '*** REJECTED *** ' . relpath, 1
744 Pecho msgtype . '*** REJECTED *** ' . relpath, 1
745 else
745 else
746 Pecho msgtype . ' ' . relpath, 1
746 Pecho msgtype . ' ' . relpath, 1
747 endif
747 endif
748 silent! exe 'tabn ' . s:origtabpagenr
748 silent! exe 'tabn ' . s:origtabpagenr
749 endfor
749 endfor
750 Pecho '-----'
750 Pecho '-----'
751 Pecho 'Done.'
751 Pecho 'Done.'
752
752
753 endfunction
753 endfunction
754 "}}}
754 "}}}
755
755
756 function! <SID>DiffReview(...) "{{{
756 function! <SID>DiffReview(...) "{{{
757 let s:save_shortmess = &shortmess
757 let s:save_shortmess = &shortmess
758 set shortmess=aW
758 set shortmess=aW
759 call s:PR_wipeMsgBuf()
759 call s:PR_wipeMsgBuf()
760
760
761 let vcsdict = {
761 let vcsdict = {
762 \'Mercurial' : {'dir' : '.hg', 'binary' : 'hg', 'diffargs' : 'diff' , 'strip' : 1},
762 \'Mercurial' : {'dir' : '.hg', 'binary' : 'hg', 'diffargs' : 'diff' , 'strip' : 1},
763 \'Bazaar-NG' : {'dir' : '.bzr', 'binary' : 'bzr', 'diffargs' : 'diff' , 'strip' : 0},
763 \'Bazaar-NG' : {'dir' : '.bzr', 'binary' : 'bzr', 'diffargs' : 'diff' , 'strip' : 0},
764 \'monotone' : {'dir' : '_MTN', 'binary' : 'mtn', 'diffargs' : 'diff --unified', 'strip' : 0},
764 \'monotone' : {'dir' : '_MTN', 'binary' : 'mtn', 'diffargs' : 'diff --unified', 'strip' : 0},
765 \'Subversion' : {'dir' : '.svn', 'binary' : 'svn', 'diffargs' : 'diff' , 'strip' : 0},
765 \'Subversion' : {'dir' : '.svn', 'binary' : 'svn', 'diffargs' : 'diff' , 'strip' : 0},
766 \'cvs' : {'dir' : 'CVS', 'binary' : 'cvs', 'diffargs' : '-q diff -u' , 'strip' : 0},
766 \'cvs' : {'dir' : 'CVS', 'binary' : 'cvs', 'diffargs' : '-q diff -u' , 'strip' : 0},
767 \}
767 \}
768
768
769 unlet! s:theDiffCmd
769 unlet! s:theDiffCmd
770 unlet! l:vcs
770 unlet! l:vcs
771 if ! exists('g:patchreview_diffcmd')
771 if ! exists('g:patchreview_diffcmd')
772 for key in keys(vcsdict)
772 for key in keys(vcsdict)
773 if isdirectory(vcsdict[key]['dir'])
773 if isdirectory(vcsdict[key]['dir'])
774 if ! s:PR_checkBinary(vcsdict[key]['binary'])
774 if ! s:PR_checkBinary(vcsdict[key]['binary'])
775 Pecho 'Current directory looks like a ' . vcsdict[key] . ' repository but ' . vcsdist[key]['binary'] . ' command was not found on path.'
775 Pecho 'Current directory looks like a ' . vcsdict[key] . ' repository but ' . vcsdist[key]['binary'] . ' command was not found on path.'
776 let &shortmess = s:save_shortmess
776 let &shortmess = s:save_shortmess
777 return
777 return
778 else
778 else
779 let s:theDiffCmd = vcsdict[key]['binary'] . ' ' . vcsdict[key]['diffargs']
779 let s:theDiffCmd = vcsdict[key]['binary'] . ' ' . vcsdict[key]['diffargs']
780 let strip = vcsdict[key]['strip']
780 let strip = vcsdict[key]['strip']
781
781
782 Pecho 'Using [' . s:theDiffCmd . '] to generate diffs for this ' . key . ' review.'
782 Pecho 'Using [' . s:theDiffCmd . '] to generate diffs for this ' . key . ' review.'
783 let &shortmess = s:save_shortmess
783 let &shortmess = s:save_shortmess
784 let l:vcs = vcsdict[key]['binary']
784 let l:vcs = vcsdict[key]['binary']
785 break
785 break
786 endif
786 endif
787 else
787 else
788 continue
788 continue
789 endif
789 endif
790 endfor
790 endfor
791 else
791 else
792 let s:theDiffCmd = g:patchreview_diffcmd
792 let s:theDiffCmd = g:patchreview_diffcmd
793 let strip = 0
793 let strip = 0
794 endif
794 endif
795 if ! exists('s:theDiffCmd')
795 if ! exists('s:theDiffCmd')
796 Pecho 'Please define g:patchreview_diffcmd and make sure you are in a VCS controlled top directory.'
796 Pecho 'Please define g:patchreview_diffcmd and make sure you are in a VCS controlled top directory.'
797 let &shortmess = s:save_shortmess
797 let &shortmess = s:save_shortmess
798 return
798 return
799 endif
799 endif
800
800
801 let outfile = tempname()
801 let outfile = tempname()
802 let cmd = s:theDiffCmd . ' > "' . outfile . '"'
802 let cmd = s:theDiffCmd . ' > "' . outfile . '"'
803 let v:errmsg = ''
803 let v:errmsg = ''
804 let cout = system(cmd)
804 let cout = system(cmd)
805 if v:errmsg == '' && exists('l:vcs') && l:vcs == 'cvs' && v:shell_error == 1
805 if v:errmsg == '' && exists('l:vcs') && l:vcs == 'cvs' && v:shell_error == 1
806 " Ignoring CVS non-error
806 " Ignoring CVS non-error
807 elseif v:errmsg != '' || v:shell_error
807 elseif v:errmsg != '' || v:shell_error
808 Pecho v:errmsg
808 Pecho v:errmsg
809 Pecho 'Could not execute [' . s:theDiffCmd . ']'
809 Pecho 'Could not execute [' . s:theDiffCmd . ']'
810 Pecho 'Error code: ' . v:shell_error
810 Pecho 'Error code: ' . v:shell_error
811 Pecho cout
811 Pecho cout
812 Pecho 'Diff review aborted.'
812 Pecho 'Diff review aborted.'
813 let &shortmess = s:save_shortmess
813 let &shortmess = s:save_shortmess
814 return
814 return
815 endif
815 endif
816 let s:reviewmode = 'diff'
816 let s:reviewmode = 'diff'
817 call s:_GenericReview([outfile, strip])
817 call s:_GenericReview([outfile, strip])
818 let &shortmess = s:save_shortmess
818 let &shortmess = s:save_shortmess
819 endfunction
819 endfunction
820 "}}}
820 "}}}
821
821
822 " End user commands "{{{
822 " End user commands "{{{
823 "============================================================================
823 "============================================================================
824 " :PatchReview
824 " :PatchReview
825 command! -nargs=* -complete=file PatchReview call s:PatchReview (<f-args>)
825 command! -nargs=* -complete=file PatchReview call s:PatchReview (<f-args>)
826
826
827 " :DiffReview
827 " :DiffReview
828 command! -nargs=0 DiffReview call s:DiffReview()
828 command! -nargs=0 DiffReview call s:DiffReview()
829 "}}}
829 "}}}
830
830
831 " Development "{{{
831 " Development "{{{
832 if exists('g:patchreview_debug')
832 if exists('g:patchreview_debug')
833 " Tests
833 " Tests
834 function! <SID>PRExtractTestNative(...)
834 function! <SID>PRExtractTestNative(...)
835 "let patchfiles = glob(expand(a:1) . '/?*')
835 "let patchfiles = glob(expand(a:1) . '/?*')
836 "for fname in split(patchfiles)
836 "for fname in split(patchfiles)
837 call s:PR_wipeMsgBuf()
837 call s:PR_wipeMsgBuf()
838 let fname = a:1
838 let fname = a:1
839 call s:ExtractDiffsNative(fname)
839 call s:ExtractDiffsNative(fname)
840 for patch in g:patches['patch']
840 for patch in g:patches['patch']
841 for line in patch.content
841 for line in patch.content
842 Pecho line
842 Pecho line
843 endfor
843 endfor
844 endfor
844 endfor
845 "endfor
845 "endfor
846 endfunction
846 endfunction
847
847
848 function! <SID>PRExtractTestVim(...)
848 function! <SID>PRExtractTestVim(...)
849 "let patchfiles = glob(expand(a:1) . '/?*')
849 "let patchfiles = glob(expand(a:1) . '/?*')
850 "for fname in split(patchfiles)
850 "for fname in split(patchfiles)
851 call s:PR_wipeMsgBuf()
851 call s:PR_wipeMsgBuf()
852 let fname = a:1
852 let fname = a:1
853 call s:ExtractDiffsPureVim(fname)
853 call s:ExtractDiffsPureVim(fname)
854 for patch in g:patches['patch']
854 for patch in g:patches['patch']
855 for line in patch.content
855 for line in patch.content
856 Pecho line
856 Pecho line
857 endfor
857 endfor
858 endfor
858 endfor
859 "endfor
859 "endfor
860 endfunction
860 endfunction
861
861
862 command! -nargs=+ -complete=file PRTestVim call s:PRExtractTestVim(<f-args>)
862 command! -nargs=+ -complete=file PRTestVim call s:PRExtractTestVim(<f-args>)
863 command! -nargs=+ -complete=file PRTestNative call s:PRExtractTestNative(<f-args>)
863 command! -nargs=+ -complete=file PRTestNative call s:PRExtractTestNative(<f-args>)
864 endif
864 endif
865 "}}}
865 "}}}
866
866
867 " modeline
867 " modeline
868 " vim: set et fdl=0 fdm=marker fenc=latin ff=unix ft=vim sw=2 sts=0 ts=2 textwidth=78 nowrap :
868 " vim: set et fdl=0 fdm=marker fenc=latin ff=unix ft=vim sw=2 sts=0 ts=2 textwidth=78 nowrap :
@@ -1,1110 +1,1110 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # $Id: manpage.py 6110 2009-08-31 14:40:33Z grubert $
2 # $Id: manpage.py 6110 2009-08-31 14:40:33Z grubert $
3 # Author: Engelbert Gruber <grubert@users.sourceforge.net>
3 # Author: Engelbert Gruber <grubert@users.sourceforge.net>
4 # Copyright: This module is put into the public domain.
4 # Copyright: This module is put into the public domain.
5
5
6 """
6 """
7 Simple man page writer for reStructuredText.
7 Simple man page writer for reStructuredText.
8
8
9 Man pages (short for "manual pages") contain system documentation on unix-like
9 Man pages (short for "manual pages") contain system documentation on unix-like
10 systems. The pages are grouped in numbered sections:
10 systems. The pages are grouped in numbered sections:
11
11
12 1 executable programs and shell commands
12 1 executable programs and shell commands
13 2 system calls
13 2 system calls
14 3 library functions
14 3 library functions
15 4 special files
15 4 special files
16 5 file formats
16 5 file formats
17 6 games
17 6 games
18 7 miscellaneous
18 7 miscellaneous
19 8 system administration
19 8 system administration
20
20
21 Man pages are written *troff*, a text file formatting system.
21 Man pages are written *troff*, a text file formatting system.
22
22
23 See http://www.tldp.org/HOWTO/Man-Page for a start.
23 See http://www.tldp.org/HOWTO/Man-Page for a start.
24
24
25 Man pages have no subsection only parts.
25 Man pages have no subsection only parts.
26 Standard parts
26 Standard parts
27
27
28 NAME ,
28 NAME ,
29 SYNOPSIS ,
29 SYNOPSIS ,
30 DESCRIPTION ,
30 DESCRIPTION ,
31 OPTIONS ,
31 OPTIONS ,
32 FILES ,
32 FILES ,
33 SEE ALSO ,
33 SEE ALSO ,
34 BUGS ,
34 BUGS ,
35
35
36 and
36 and
37
37
38 AUTHOR .
38 AUTHOR .
39
39
40 A unix-like system keeps an index of the DESCRIPTIONs, which is accesable
40 A unix-like system keeps an index of the DESCRIPTIONs, which is accesable
41 by the command whatis or apropos.
41 by the command whatis or apropos.
42
42
43 """
43 """
44
44
45 __docformat__ = 'reStructuredText'
45 __docformat__ = 'reStructuredText'
46
46
47 import re
47 import re
48
48
49 from docutils import nodes, writers, languages
49 from docutils import nodes, writers, languages
50 try:
50 try:
51 import roman
51 import roman
52 except ImportError:
52 except ImportError:
53 from docutils.utils import roman
53 from docutils.utils import roman
54 import inspect
54 import inspect
55
55
56 FIELD_LIST_INDENT = 7
56 FIELD_LIST_INDENT = 7
57 DEFINITION_LIST_INDENT = 7
57 DEFINITION_LIST_INDENT = 7
58 OPTION_LIST_INDENT = 7
58 OPTION_LIST_INDENT = 7
59 BLOCKQOUTE_INDENT = 3.5
59 BLOCKQOUTE_INDENT = 3.5
60
60
61 # Define two macros so man/roff can calculate the
61 # Define two macros so man/roff can calculate the
62 # indent/unindent margins by itself
62 # indent/unindent margins by itself
63 MACRO_DEF = (r""".
63 MACRO_DEF = (r""".
64 .nr rst2man-indent-level 0
64 .nr rst2man-indent-level 0
65 .
65 .
66 .de1 rstReportMargin
66 .de1 rstReportMargin
67 \\$1 \\n[an-margin]
67 \\$1 \\n[an-margin]
68 level \\n[rst2man-indent-level]
68 level \\n[rst2man-indent-level]
69 level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
69 level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
70 -
70 -
71 \\n[rst2man-indent0]
71 \\n[rst2man-indent0]
72 \\n[rst2man-indent1]
72 \\n[rst2man-indent1]
73 \\n[rst2man-indent2]
73 \\n[rst2man-indent2]
74 ..
74 ..
75 .de1 INDENT
75 .de1 INDENT
76 .\" .rstReportMargin pre:
76 .\" .rstReportMargin pre:
77 . RS \\$1
77 . RS \\$1
78 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
78 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
79 . nr rst2man-indent-level +1
79 . nr rst2man-indent-level +1
80 .\" .rstReportMargin post:
80 .\" .rstReportMargin post:
81 ..
81 ..
82 .de UNINDENT
82 .de UNINDENT
83 . RE
83 . RE
84 .\" indent \\n[an-margin]
84 .\" indent \\n[an-margin]
85 .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
85 .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
86 .nr rst2man-indent-level -1
86 .nr rst2man-indent-level -1
87 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
87 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
88 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
88 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
89 ..
89 ..
90 """)
90 """)
91
91
92 class Writer(writers.Writer):
92 class Writer(writers.Writer):
93
93
94 supported = ('manpage')
94 supported = ('manpage')
95 """Formats this writer supports."""
95 """Formats this writer supports."""
96
96
97 output = None
97 output = None
98 """Final translated form of `document`."""
98 """Final translated form of `document`."""
99
99
100 def __init__(self):
100 def __init__(self):
101 writers.Writer.__init__(self)
101 writers.Writer.__init__(self)
102 self.translator_class = Translator
102 self.translator_class = Translator
103
103
104 def translate(self):
104 def translate(self):
105 visitor = self.translator_class(self.document)
105 visitor = self.translator_class(self.document)
106 self.document.walkabout(visitor)
106 self.document.walkabout(visitor)
107 self.output = visitor.astext()
107 self.output = visitor.astext()
108
108
109
109
110 class Table(object):
110 class Table(object):
111 def __init__(self):
111 def __init__(self):
112 self._rows = []
112 self._rows = []
113 self._options = ['center']
113 self._options = ['center']
114 self._tab_char = '\t'
114 self._tab_char = '\t'
115 self._coldefs = []
115 self._coldefs = []
116 def new_row(self):
116 def new_row(self):
117 self._rows.append([])
117 self._rows.append([])
118 def append_separator(self, separator):
118 def append_separator(self, separator):
119 """Append the separator for table head."""
119 """Append the separator for table head."""
120 self._rows.append([separator])
120 self._rows.append([separator])
121 def append_cell(self, cell_lines):
121 def append_cell(self, cell_lines):
122 """cell_lines is an array of lines"""
122 """cell_lines is an array of lines"""
123 start = 0
123 start = 0
124 if len(cell_lines) > 0 and cell_lines[0] == '.sp\n':
124 if len(cell_lines) > 0 and cell_lines[0] == '.sp\n':
125 start = 1
125 start = 1
126 self._rows[-1].append(cell_lines[start:])
126 self._rows[-1].append(cell_lines[start:])
127 if len(self._coldefs) < len(self._rows[-1]):
127 if len(self._coldefs) < len(self._rows[-1]):
128 self._coldefs.append('l')
128 self._coldefs.append('l')
129 def _minimize_cell(self, cell_lines):
129 def _minimize_cell(self, cell_lines):
130 """Remove leading and trailing blank and ``.sp`` lines"""
130 """Remove leading and trailing blank and ``.sp`` lines"""
131 while (cell_lines and cell_lines[0] in ('\n', '.sp\n')):
131 while (cell_lines and cell_lines[0] in ('\n', '.sp\n')):
132 del cell_lines[0]
132 del cell_lines[0]
133 while (cell_lines and cell_lines[-1] in ('\n', '.sp\n')):
133 while (cell_lines and cell_lines[-1] in ('\n', '.sp\n')):
134 del cell_lines[-1]
134 del cell_lines[-1]
135 def as_list(self):
135 def as_list(self):
136 text = ['.TS\n']
136 text = ['.TS\n']
137 text.append(' '.join(self._options) + ';\n')
137 text.append(' '.join(self._options) + ';\n')
138 text.append('|%s|.\n' % ('|'.join(self._coldefs)))
138 text.append('|%s|.\n' % ('|'.join(self._coldefs)))
139 for row in self._rows:
139 for row in self._rows:
140 # row = array of cells. cell = array of lines.
140 # row = array of cells. cell = array of lines.
141 text.append('_\n') # line above
141 text.append('_\n') # line above
142 text.append('T{\n')
142 text.append('T{\n')
143 for i in range(len(row)):
143 for i in range(len(row)):
144 cell = row[i]
144 cell = row[i]
145 self._minimize_cell(cell)
145 self._minimize_cell(cell)
146 text.extend(cell)
146 text.extend(cell)
147 if not text[-1].endswith('\n'):
147 if not text[-1].endswith('\n'):
148 text[-1] += '\n'
148 text[-1] += '\n'
149 if i < len(row)-1:
149 if i < len(row)-1:
150 text.append('T}'+self._tab_char+'T{\n')
150 text.append('T}'+self._tab_char+'T{\n')
151 else:
151 else:
152 text.append('T}\n')
152 text.append('T}\n')
153 text.append('_\n')
153 text.append('_\n')
154 text.append('.TE\n')
154 text.append('.TE\n')
155 return text
155 return text
156
156
157 class Translator(nodes.NodeVisitor):
157 class Translator(nodes.NodeVisitor):
158 """"""
158 """"""
159
159
160 words_and_spaces = re.compile(r'\S+| +|\n')
160 words_and_spaces = re.compile(r'\S+| +|\n')
161 document_start = """Man page generated from reStructeredText."""
161 document_start = """Man page generated from reStructuredText."""
162
162
163 def __init__(self, document):
163 def __init__(self, document):
164 nodes.NodeVisitor.__init__(self, document)
164 nodes.NodeVisitor.__init__(self, document)
165 self.settings = settings = document.settings
165 self.settings = settings = document.settings
166 lcode = settings.language_code
166 lcode = settings.language_code
167 arglen = len(inspect.getargspec(languages.get_language)[0])
167 arglen = len(inspect.getargspec(languages.get_language)[0])
168 if arglen == 2:
168 if arglen == 2:
169 self.language = languages.get_language(lcode,
169 self.language = languages.get_language(lcode,
170 self.document.reporter)
170 self.document.reporter)
171 else:
171 else:
172 self.language = languages.get_language(lcode)
172 self.language = languages.get_language(lcode)
173 self.head = []
173 self.head = []
174 self.body = []
174 self.body = []
175 self.foot = []
175 self.foot = []
176 self.section_level = 0
176 self.section_level = 0
177 self.context = []
177 self.context = []
178 self.topic_class = ''
178 self.topic_class = ''
179 self.colspecs = []
179 self.colspecs = []
180 self.compact_p = 1
180 self.compact_p = 1
181 self.compact_simple = None
181 self.compact_simple = None
182 # the list style "*" bullet or "#" numbered
182 # the list style "*" bullet or "#" numbered
183 self._list_char = []
183 self._list_char = []
184 # writing the header .TH and .SH NAME is postboned after
184 # writing the header .TH and .SH NAME is postboned after
185 # docinfo.
185 # docinfo.
186 self._docinfo = {
186 self._docinfo = {
187 "title" : "", "title_upper": "",
187 "title" : "", "title_upper": "",
188 "subtitle" : "",
188 "subtitle" : "",
189 "manual_section" : "", "manual_group" : "",
189 "manual_section" : "", "manual_group" : "",
190 "author" : [],
190 "author" : [],
191 "date" : "",
191 "date" : "",
192 "copyright" : "",
192 "copyright" : "",
193 "version" : "",
193 "version" : "",
194 }
194 }
195 self._docinfo_keys = [] # a list to keep the sequence as in source.
195 self._docinfo_keys = [] # a list to keep the sequence as in source.
196 self._docinfo_names = {} # to get name from text not normalized.
196 self._docinfo_names = {} # to get name from text not normalized.
197 self._in_docinfo = None
197 self._in_docinfo = None
198 self._active_table = None
198 self._active_table = None
199 self._in_literal = False
199 self._in_literal = False
200 self.header_written = 0
200 self.header_written = 0
201 self._line_block = 0
201 self._line_block = 0
202 self.authors = []
202 self.authors = []
203 self.section_level = 0
203 self.section_level = 0
204 self._indent = [0]
204 self._indent = [0]
205 # central definition of simple processing rules
205 # central definition of simple processing rules
206 # what to output on : visit, depart
206 # what to output on : visit, depart
207 # Do not use paragraph requests ``.PP`` because these set indentation.
207 # Do not use paragraph requests ``.PP`` because these set indentation.
208 # use ``.sp``. Remove superfluous ``.sp`` in ``astext``.
208 # use ``.sp``. Remove superfluous ``.sp`` in ``astext``.
209 #
209 #
210 # Fonts are put on a stack, the top one is used.
210 # Fonts are put on a stack, the top one is used.
211 # ``.ft P`` or ``\\fP`` pop from stack.
211 # ``.ft P`` or ``\\fP`` pop from stack.
212 # ``B`` bold, ``I`` italic, ``R`` roman should be available.
212 # ``B`` bold, ``I`` italic, ``R`` roman should be available.
213 # Hopefully ``C`` courier too.
213 # Hopefully ``C`` courier too.
214 self.defs = {
214 self.defs = {
215 'indent' : ('.INDENT %.1f\n', '.UNINDENT\n'),
215 'indent' : ('.INDENT %.1f\n', '.UNINDENT\n'),
216 'definition_list_item' : ('.TP', ''),
216 'definition_list_item' : ('.TP', ''),
217 'field_name' : ('.TP\n.B ', '\n'),
217 'field_name' : ('.TP\n.B ', '\n'),
218 'literal' : ('\\fB', '\\fP'),
218 'literal' : ('\\fB', '\\fP'),
219 'literal_block' : ('.sp\n.nf\n.ft C\n', '\n.ft P\n.fi\n'),
219 'literal_block' : ('.sp\n.nf\n.ft C\n', '\n.ft P\n.fi\n'),
220
220
221 'option_list_item' : ('.TP\n', ''),
221 'option_list_item' : ('.TP\n', ''),
222
222
223 'reference' : (r'\%', r'\:'),
223 'reference' : (r'\%', r'\:'),
224 'emphasis': ('\\fI', '\\fP'),
224 'emphasis': ('\\fI', '\\fP'),
225 'strong' : ('\\fB', '\\fP'),
225 'strong' : ('\\fB', '\\fP'),
226 'term' : ('\n.B ', '\n'),
226 'term' : ('\n.B ', '\n'),
227 'title_reference' : ('\\fI', '\\fP'),
227 'title_reference' : ('\\fI', '\\fP'),
228
228
229 'topic-title' : ('.SS ',),
229 'topic-title' : ('.SS ',),
230 'sidebar-title' : ('.SS ',),
230 'sidebar-title' : ('.SS ',),
231
231
232 'problematic' : ('\n.nf\n', '\n.fi\n'),
232 'problematic' : ('\n.nf\n', '\n.fi\n'),
233 }
233 }
234 # NOTE don't specify the newline before a dot-command, but ensure
234 # NOTE don't specify the newline before a dot-command, but ensure
235 # it is there.
235 # it is there.
236
236
237 def comment_begin(self, text):
237 def comment_begin(self, text):
238 """Return commented version of the passed text WITHOUT end of
238 """Return commented version of the passed text WITHOUT end of
239 line/comment."""
239 line/comment."""
240 prefix = '.\\" '
240 prefix = '.\\" '
241 out_text = ''.join(
241 out_text = ''.join(
242 [(prefix + in_line + '\n')
242 [(prefix + in_line + '\n')
243 for in_line in text.split('\n')])
243 for in_line in text.split('\n')])
244 return out_text
244 return out_text
245
245
246 def comment(self, text):
246 def comment(self, text):
247 """Return commented version of the passed text."""
247 """Return commented version of the passed text."""
248 return self.comment_begin(text)+'.\n'
248 return self.comment_begin(text)+'.\n'
249
249
250 def ensure_eol(self):
250 def ensure_eol(self):
251 """Ensure the last line in body is terminated by new line."""
251 """Ensure the last line in body is terminated by new line."""
252 if self.body[-1][-1] != '\n':
252 if self.body[-1][-1] != '\n':
253 self.body.append('\n')
253 self.body.append('\n')
254
254
255 def astext(self):
255 def astext(self):
256 """Return the final formatted document as a string."""
256 """Return the final formatted document as a string."""
257 if not self.header_written:
257 if not self.header_written:
258 # ensure we get a ".TH" as viewers require it.
258 # ensure we get a ".TH" as viewers require it.
259 self.head.append(self.header())
259 self.head.append(self.header())
260 # filter body
260 # filter body
261 for i in xrange(len(self.body)-1, 0, -1):
261 for i in xrange(len(self.body)-1, 0, -1):
262 # remove superfluous vertical gaps.
262 # remove superfluous vertical gaps.
263 if self.body[i] == '.sp\n':
263 if self.body[i] == '.sp\n':
264 if self.body[i - 1][:4] in ('.BI ','.IP '):
264 if self.body[i - 1][:4] in ('.BI ','.IP '):
265 self.body[i] = '.\n'
265 self.body[i] = '.\n'
266 elif (self.body[i - 1][:3] == '.B ' and
266 elif (self.body[i - 1][:3] == '.B ' and
267 self.body[i - 2][:4] == '.TP\n'):
267 self.body[i - 2][:4] == '.TP\n'):
268 self.body[i] = '.\n'
268 self.body[i] = '.\n'
269 elif (self.body[i - 1] == '\n' and
269 elif (self.body[i - 1] == '\n' and
270 self.body[i - 2][0] != '.' and
270 self.body[i - 2][0] != '.' and
271 (self.body[i - 3][:7] == '.TP\n.B '
271 (self.body[i - 3][:7] == '.TP\n.B '
272 or self.body[i - 3][:4] == '\n.B ')
272 or self.body[i - 3][:4] == '\n.B ')
273 ):
273 ):
274 self.body[i] = '.\n'
274 self.body[i] = '.\n'
275 return ''.join(self.head + self.body + self.foot)
275 return ''.join(self.head + self.body + self.foot)
276
276
277 def deunicode(self, text):
277 def deunicode(self, text):
278 text = text.replace(u'\xa0', '\\ ')
278 text = text.replace(u'\xa0', '\\ ')
279 text = text.replace(u'\u2020', '\\(dg')
279 text = text.replace(u'\u2020', '\\(dg')
280 return text
280 return text
281
281
282 def visit_Text(self, node):
282 def visit_Text(self, node):
283 text = node.astext()
283 text = node.astext()
284 text = text.replace('\\','\\e')
284 text = text.replace('\\','\\e')
285 replace_pairs = [
285 replace_pairs = [
286 (u'-', ur'\-'),
286 (u'-', ur'\-'),
287 (u'\'', ur'\(aq'),
287 (u'\'', ur'\(aq'),
288 (u'´', ur'\''),
288 (u'´', ur'\''),
289 (u'`', ur'\(ga'),
289 (u'`', ur'\(ga'),
290 ]
290 ]
291 for (in_char, out_markup) in replace_pairs:
291 for (in_char, out_markup) in replace_pairs:
292 text = text.replace(in_char, out_markup)
292 text = text.replace(in_char, out_markup)
293 # unicode
293 # unicode
294 text = self.deunicode(text)
294 text = self.deunicode(text)
295 if self._in_literal:
295 if self._in_literal:
296 # prevent interpretation of "." at line start
296 # prevent interpretation of "." at line start
297 if text[0] == '.':
297 if text[0] == '.':
298 text = '\\&' + text
298 text = '\\&' + text
299 text = text.replace('\n.', '\n\\&.')
299 text = text.replace('\n.', '\n\\&.')
300 self.body.append(text)
300 self.body.append(text)
301
301
302 def depart_Text(self, node):
302 def depart_Text(self, node):
303 pass
303 pass
304
304
305 def list_start(self, node):
305 def list_start(self, node):
306 class enum_char(object):
306 class enum_char(object):
307 enum_style = {
307 enum_style = {
308 'bullet' : '\\(bu',
308 'bullet' : '\\(bu',
309 'emdash' : '\\(em',
309 'emdash' : '\\(em',
310 }
310 }
311
311
312 def __init__(self, style):
312 def __init__(self, style):
313 self._style = style
313 self._style = style
314 if 'start' in node:
314 if 'start' in node:
315 self._cnt = node['start'] - 1
315 self._cnt = node['start'] - 1
316 else:
316 else:
317 self._cnt = 0
317 self._cnt = 0
318 self._indent = 2
318 self._indent = 2
319 if style == 'arabic':
319 if style == 'arabic':
320 # indentation depends on number of childrens
320 # indentation depends on number of childrens
321 # and start value.
321 # and start value.
322 self._indent = len(str(len(node.children)))
322 self._indent = len(str(len(node.children)))
323 self._indent += len(str(self._cnt)) + 1
323 self._indent += len(str(self._cnt)) + 1
324 elif style == 'loweralpha':
324 elif style == 'loweralpha':
325 self._cnt += ord('a') - 1
325 self._cnt += ord('a') - 1
326 self._indent = 3
326 self._indent = 3
327 elif style == 'upperalpha':
327 elif style == 'upperalpha':
328 self._cnt += ord('A') - 1
328 self._cnt += ord('A') - 1
329 self._indent = 3
329 self._indent = 3
330 elif style.endswith('roman'):
330 elif style.endswith('roman'):
331 self._indent = 5
331 self._indent = 5
332
332
333 def next(self):
333 def next(self):
334 if self._style == 'bullet':
334 if self._style == 'bullet':
335 return self.enum_style[self._style]
335 return self.enum_style[self._style]
336 elif self._style == 'emdash':
336 elif self._style == 'emdash':
337 return self.enum_style[self._style]
337 return self.enum_style[self._style]
338 self._cnt += 1
338 self._cnt += 1
339 # TODO add prefix postfix
339 # TODO add prefix postfix
340 if self._style == 'arabic':
340 if self._style == 'arabic':
341 return "%d." % self._cnt
341 return "%d." % self._cnt
342 elif self._style in ('loweralpha', 'upperalpha'):
342 elif self._style in ('loweralpha', 'upperalpha'):
343 return "%c." % self._cnt
343 return "%c." % self._cnt
344 elif self._style.endswith('roman'):
344 elif self._style.endswith('roman'):
345 res = roman.toRoman(self._cnt) + '.'
345 res = roman.toRoman(self._cnt) + '.'
346 if self._style.startswith('upper'):
346 if self._style.startswith('upper'):
347 return res.upper()
347 return res.upper()
348 return res.lower()
348 return res.lower()
349 else:
349 else:
350 return "%d." % self._cnt
350 return "%d." % self._cnt
351 def get_width(self):
351 def get_width(self):
352 return self._indent
352 return self._indent
353 def __repr__(self):
353 def __repr__(self):
354 return 'enum_style-%s' % list(self._style)
354 return 'enum_style-%s' % list(self._style)
355
355
356 if 'enumtype' in node:
356 if 'enumtype' in node:
357 self._list_char.append(enum_char(node['enumtype']))
357 self._list_char.append(enum_char(node['enumtype']))
358 else:
358 else:
359 self._list_char.append(enum_char('bullet'))
359 self._list_char.append(enum_char('bullet'))
360 if len(self._list_char) > 1:
360 if len(self._list_char) > 1:
361 # indent nested lists
361 # indent nested lists
362 self.indent(self._list_char[-2].get_width())
362 self.indent(self._list_char[-2].get_width())
363 else:
363 else:
364 self.indent(self._list_char[-1].get_width())
364 self.indent(self._list_char[-1].get_width())
365
365
366 def list_end(self):
366 def list_end(self):
367 self.dedent()
367 self.dedent()
368 self._list_char.pop()
368 self._list_char.pop()
369
369
370 def header(self):
370 def header(self):
371 tmpl = (".TH %(title_upper)s %(manual_section)s"
371 tmpl = (".TH %(title_upper)s %(manual_section)s"
372 " \"%(date)s\" \"%(version)s\" \"%(manual_group)s\"\n"
372 " \"%(date)s\" \"%(version)s\" \"%(manual_group)s\"\n"
373 ".SH NAME\n"
373 ".SH NAME\n"
374 "%(title)s \- %(subtitle)s\n")
374 "%(title)s \- %(subtitle)s\n")
375 return tmpl % self._docinfo
375 return tmpl % self._docinfo
376
376
377 def append_header(self):
377 def append_header(self):
378 """append header with .TH and .SH NAME"""
378 """append header with .TH and .SH NAME"""
379 # NOTE before everything
379 # NOTE before everything
380 # .TH title_upper section date source manual
380 # .TH title_upper section date source manual
381 if self.header_written:
381 if self.header_written:
382 return
382 return
383 self.body.append(self.header())
383 self.body.append(self.header())
384 self.body.append(MACRO_DEF)
384 self.body.append(MACRO_DEF)
385 self.header_written = 1
385 self.header_written = 1
386
386
387 def visit_address(self, node):
387 def visit_address(self, node):
388 self.visit_docinfo_item(node, 'address')
388 self.visit_docinfo_item(node, 'address')
389
389
390 def depart_address(self, node):
390 def depart_address(self, node):
391 pass
391 pass
392
392
393 def visit_admonition(self, node, name=None):
393 def visit_admonition(self, node, name=None):
394 if name:
394 if name:
395 self.body.append('.IP %s\n' %
395 self.body.append('.IP %s\n' %
396 self.language.labels.get(name, name))
396 self.language.labels.get(name, name))
397
397
398 def depart_admonition(self, node):
398 def depart_admonition(self, node):
399 self.body.append('.RE\n')
399 self.body.append('.RE\n')
400
400
401 def visit_attention(self, node):
401 def visit_attention(self, node):
402 self.visit_admonition(node, 'attention')
402 self.visit_admonition(node, 'attention')
403
403
404 depart_attention = depart_admonition
404 depart_attention = depart_admonition
405
405
406 def visit_docinfo_item(self, node, name):
406 def visit_docinfo_item(self, node, name):
407 if name == 'author':
407 if name == 'author':
408 self._docinfo[name].append(node.astext())
408 self._docinfo[name].append(node.astext())
409 else:
409 else:
410 self._docinfo[name] = node.astext()
410 self._docinfo[name] = node.astext()
411 self._docinfo_keys.append(name)
411 self._docinfo_keys.append(name)
412 raise nodes.SkipNode
412 raise nodes.SkipNode
413
413
414 def depart_docinfo_item(self, node):
414 def depart_docinfo_item(self, node):
415 pass
415 pass
416
416
417 def visit_author(self, node):
417 def visit_author(self, node):
418 self.visit_docinfo_item(node, 'author')
418 self.visit_docinfo_item(node, 'author')
419
419
420 depart_author = depart_docinfo_item
420 depart_author = depart_docinfo_item
421
421
422 def visit_authors(self, node):
422 def visit_authors(self, node):
423 # _author is called anyway.
423 # _author is called anyway.
424 pass
424 pass
425
425
426 def depart_authors(self, node):
426 def depart_authors(self, node):
427 pass
427 pass
428
428
429 def visit_block_quote(self, node):
429 def visit_block_quote(self, node):
430 # BUG/HACK: indent alway uses the _last_ indention,
430 # BUG/HACK: indent alway uses the _last_ indention,
431 # thus we need two of them.
431 # thus we need two of them.
432 self.indent(BLOCKQOUTE_INDENT)
432 self.indent(BLOCKQOUTE_INDENT)
433 self.indent(0)
433 self.indent(0)
434
434
435 def depart_block_quote(self, node):
435 def depart_block_quote(self, node):
436 self.dedent()
436 self.dedent()
437 self.dedent()
437 self.dedent()
438
438
439 def visit_bullet_list(self, node):
439 def visit_bullet_list(self, node):
440 self.list_start(node)
440 self.list_start(node)
441
441
442 def depart_bullet_list(self, node):
442 def depart_bullet_list(self, node):
443 self.list_end()
443 self.list_end()
444
444
445 def visit_caption(self, node):
445 def visit_caption(self, node):
446 pass
446 pass
447
447
448 def depart_caption(self, node):
448 def depart_caption(self, node):
449 pass
449 pass
450
450
451 def visit_caution(self, node):
451 def visit_caution(self, node):
452 self.visit_admonition(node, 'caution')
452 self.visit_admonition(node, 'caution')
453
453
454 depart_caution = depart_admonition
454 depart_caution = depart_admonition
455
455
456 def visit_citation(self, node):
456 def visit_citation(self, node):
457 num, text = node.astext().split(None, 1)
457 num, text = node.astext().split(None, 1)
458 num = num.strip()
458 num = num.strip()
459 self.body.append('.IP [%s] 5\n' % num)
459 self.body.append('.IP [%s] 5\n' % num)
460
460
461 def depart_citation(self, node):
461 def depart_citation(self, node):
462 pass
462 pass
463
463
464 def visit_citation_reference(self, node):
464 def visit_citation_reference(self, node):
465 self.body.append('['+node.astext()+']')
465 self.body.append('['+node.astext()+']')
466 raise nodes.SkipNode
466 raise nodes.SkipNode
467
467
468 def visit_classifier(self, node):
468 def visit_classifier(self, node):
469 pass
469 pass
470
470
471 def depart_classifier(self, node):
471 def depart_classifier(self, node):
472 pass
472 pass
473
473
474 def visit_colspec(self, node):
474 def visit_colspec(self, node):
475 self.colspecs.append(node)
475 self.colspecs.append(node)
476
476
477 def depart_colspec(self, node):
477 def depart_colspec(self, node):
478 pass
478 pass
479
479
480 def write_colspecs(self):
480 def write_colspecs(self):
481 self.body.append("%s.\n" % ('L '*len(self.colspecs)))
481 self.body.append("%s.\n" % ('L '*len(self.colspecs)))
482
482
483 def visit_comment(self, node,
483 def visit_comment(self, node,
484 sub=re.compile('-(?=-)').sub):
484 sub=re.compile('-(?=-)').sub):
485 self.body.append(self.comment(node.astext()))
485 self.body.append(self.comment(node.astext()))
486 raise nodes.SkipNode
486 raise nodes.SkipNode
487
487
488 def visit_contact(self, node):
488 def visit_contact(self, node):
489 self.visit_docinfo_item(node, 'contact')
489 self.visit_docinfo_item(node, 'contact')
490
490
491 depart_contact = depart_docinfo_item
491 depart_contact = depart_docinfo_item
492
492
493 def visit_container(self, node):
493 def visit_container(self, node):
494 pass
494 pass
495
495
496 def depart_container(self, node):
496 def depart_container(self, node):
497 pass
497 pass
498
498
499 def visit_compound(self, node):
499 def visit_compound(self, node):
500 pass
500 pass
501
501
502 def depart_compound(self, node):
502 def depart_compound(self, node):
503 pass
503 pass
504
504
505 def visit_copyright(self, node):
505 def visit_copyright(self, node):
506 self.visit_docinfo_item(node, 'copyright')
506 self.visit_docinfo_item(node, 'copyright')
507
507
508 def visit_danger(self, node):
508 def visit_danger(self, node):
509 self.visit_admonition(node, 'danger')
509 self.visit_admonition(node, 'danger')
510
510
511 depart_danger = depart_admonition
511 depart_danger = depart_admonition
512
512
513 def visit_date(self, node):
513 def visit_date(self, node):
514 self.visit_docinfo_item(node, 'date')
514 self.visit_docinfo_item(node, 'date')
515
515
516 def visit_decoration(self, node):
516 def visit_decoration(self, node):
517 pass
517 pass
518
518
519 def depart_decoration(self, node):
519 def depart_decoration(self, node):
520 pass
520 pass
521
521
522 def visit_definition(self, node):
522 def visit_definition(self, node):
523 pass
523 pass
524
524
525 def depart_definition(self, node):
525 def depart_definition(self, node):
526 pass
526 pass
527
527
528 def visit_definition_list(self, node):
528 def visit_definition_list(self, node):
529 self.indent(DEFINITION_LIST_INDENT)
529 self.indent(DEFINITION_LIST_INDENT)
530
530
531 def depart_definition_list(self, node):
531 def depart_definition_list(self, node):
532 self.dedent()
532 self.dedent()
533
533
534 def visit_definition_list_item(self, node):
534 def visit_definition_list_item(self, node):
535 self.body.append(self.defs['definition_list_item'][0])
535 self.body.append(self.defs['definition_list_item'][0])
536
536
537 def depart_definition_list_item(self, node):
537 def depart_definition_list_item(self, node):
538 self.body.append(self.defs['definition_list_item'][1])
538 self.body.append(self.defs['definition_list_item'][1])
539
539
540 def visit_description(self, node):
540 def visit_description(self, node):
541 pass
541 pass
542
542
543 def depart_description(self, node):
543 def depart_description(self, node):
544 pass
544 pass
545
545
546 def visit_docinfo(self, node):
546 def visit_docinfo(self, node):
547 self._in_docinfo = 1
547 self._in_docinfo = 1
548
548
549 def depart_docinfo(self, node):
549 def depart_docinfo(self, node):
550 self._in_docinfo = None
550 self._in_docinfo = None
551 # NOTE nothing should be written before this
551 # NOTE nothing should be written before this
552 self.append_header()
552 self.append_header()
553
553
554 def visit_doctest_block(self, node):
554 def visit_doctest_block(self, node):
555 self.body.append(self.defs['literal_block'][0])
555 self.body.append(self.defs['literal_block'][0])
556 self._in_literal = True
556 self._in_literal = True
557
557
558 def depart_doctest_block(self, node):
558 def depart_doctest_block(self, node):
559 self._in_literal = False
559 self._in_literal = False
560 self.body.append(self.defs['literal_block'][1])
560 self.body.append(self.defs['literal_block'][1])
561
561
562 def visit_document(self, node):
562 def visit_document(self, node):
563 # no blank line between comment and header.
563 # no blank line between comment and header.
564 self.body.append(self.comment(self.document_start).rstrip()+'\n')
564 self.body.append(self.comment(self.document_start).rstrip()+'\n')
565 # writing header is postboned
565 # writing header is postboned
566 self.header_written = 0
566 self.header_written = 0
567
567
568 def depart_document(self, node):
568 def depart_document(self, node):
569 if self._docinfo['author']:
569 if self._docinfo['author']:
570 self.body.append('.SH AUTHOR\n%s\n'
570 self.body.append('.SH AUTHOR\n%s\n'
571 % ', '.join(self._docinfo['author']))
571 % ', '.join(self._docinfo['author']))
572 skip = ('author', 'copyright', 'date',
572 skip = ('author', 'copyright', 'date',
573 'manual_group', 'manual_section',
573 'manual_group', 'manual_section',
574 'subtitle',
574 'subtitle',
575 'title', 'title_upper', 'version')
575 'title', 'title_upper', 'version')
576 for name in self._docinfo_keys:
576 for name in self._docinfo_keys:
577 if name == 'address':
577 if name == 'address':
578 self.body.append("\n%s:\n%s%s.nf\n%s\n.fi\n%s%s" % (
578 self.body.append("\n%s:\n%s%s.nf\n%s\n.fi\n%s%s" % (
579 self.language.labels.get(name, name),
579 self.language.labels.get(name, name),
580 self.defs['indent'][0] % 0,
580 self.defs['indent'][0] % 0,
581 self.defs['indent'][0] % BLOCKQOUTE_INDENT,
581 self.defs['indent'][0] % BLOCKQOUTE_INDENT,
582 self._docinfo[name],
582 self._docinfo[name],
583 self.defs['indent'][1],
583 self.defs['indent'][1],
584 self.defs['indent'][1]))
584 self.defs['indent'][1]))
585 elif name not in skip:
585 elif name not in skip:
586 if name in self._docinfo_names:
586 if name in self._docinfo_names:
587 label = self._docinfo_names[name]
587 label = self._docinfo_names[name]
588 else:
588 else:
589 label = self.language.labels.get(name, name)
589 label = self.language.labels.get(name, name)
590 self.body.append("\n%s: %s\n" % (label, self._docinfo[name]))
590 self.body.append("\n%s: %s\n" % (label, self._docinfo[name]))
591 if self._docinfo['copyright']:
591 if self._docinfo['copyright']:
592 self.body.append('.SH COPYRIGHT\n%s\n'
592 self.body.append('.SH COPYRIGHT\n%s\n'
593 % self._docinfo['copyright'])
593 % self._docinfo['copyright'])
594 self.body.append(self.comment(
594 self.body.append(self.comment(
595 'Generated by docutils manpage writer.\n'))
595 'Generated by docutils manpage writer.\n'))
596
596
597 def visit_emphasis(self, node):
597 def visit_emphasis(self, node):
598 self.body.append(self.defs['emphasis'][0])
598 self.body.append(self.defs['emphasis'][0])
599
599
600 def depart_emphasis(self, node):
600 def depart_emphasis(self, node):
601 self.body.append(self.defs['emphasis'][1])
601 self.body.append(self.defs['emphasis'][1])
602
602
603 def visit_entry(self, node):
603 def visit_entry(self, node):
604 # a cell in a table row
604 # a cell in a table row
605 if 'morerows' in node:
605 if 'morerows' in node:
606 self.document.reporter.warning('"table row spanning" not supported',
606 self.document.reporter.warning('"table row spanning" not supported',
607 base_node=node)
607 base_node=node)
608 if 'morecols' in node:
608 if 'morecols' in node:
609 self.document.reporter.warning(
609 self.document.reporter.warning(
610 '"table cell spanning" not supported', base_node=node)
610 '"table cell spanning" not supported', base_node=node)
611 self.context.append(len(self.body))
611 self.context.append(len(self.body))
612
612
613 def depart_entry(self, node):
613 def depart_entry(self, node):
614 start = self.context.pop()
614 start = self.context.pop()
615 self._active_table.append_cell(self.body[start:])
615 self._active_table.append_cell(self.body[start:])
616 del self.body[start:]
616 del self.body[start:]
617
617
618 def visit_enumerated_list(self, node):
618 def visit_enumerated_list(self, node):
619 self.list_start(node)
619 self.list_start(node)
620
620
621 def depart_enumerated_list(self, node):
621 def depart_enumerated_list(self, node):
622 self.list_end()
622 self.list_end()
623
623
624 def visit_error(self, node):
624 def visit_error(self, node):
625 self.visit_admonition(node, 'error')
625 self.visit_admonition(node, 'error')
626
626
627 depart_error = depart_admonition
627 depart_error = depart_admonition
628
628
629 def visit_field(self, node):
629 def visit_field(self, node):
630 pass
630 pass
631
631
632 def depart_field(self, node):
632 def depart_field(self, node):
633 pass
633 pass
634
634
635 def visit_field_body(self, node):
635 def visit_field_body(self, node):
636 if self._in_docinfo:
636 if self._in_docinfo:
637 name_normalized = self._field_name.lower().replace(" ","_")
637 name_normalized = self._field_name.lower().replace(" ","_")
638 self._docinfo_names[name_normalized] = self._field_name
638 self._docinfo_names[name_normalized] = self._field_name
639 self.visit_docinfo_item(node, name_normalized)
639 self.visit_docinfo_item(node, name_normalized)
640 raise nodes.SkipNode
640 raise nodes.SkipNode
641
641
642 def depart_field_body(self, node):
642 def depart_field_body(self, node):
643 pass
643 pass
644
644
645 def visit_field_list(self, node):
645 def visit_field_list(self, node):
646 self.indent(FIELD_LIST_INDENT)
646 self.indent(FIELD_LIST_INDENT)
647
647
648 def depart_field_list(self, node):
648 def depart_field_list(self, node):
649 self.dedent()
649 self.dedent()
650
650
651 def visit_field_name(self, node):
651 def visit_field_name(self, node):
652 if self._in_docinfo:
652 if self._in_docinfo:
653 self._field_name = node.astext()
653 self._field_name = node.astext()
654 raise nodes.SkipNode
654 raise nodes.SkipNode
655 else:
655 else:
656 self.body.append(self.defs['field_name'][0])
656 self.body.append(self.defs['field_name'][0])
657
657
658 def depart_field_name(self, node):
658 def depart_field_name(self, node):
659 self.body.append(self.defs['field_name'][1])
659 self.body.append(self.defs['field_name'][1])
660
660
661 def visit_figure(self, node):
661 def visit_figure(self, node):
662 self.indent(2.5)
662 self.indent(2.5)
663 self.indent(0)
663 self.indent(0)
664
664
665 def depart_figure(self, node):
665 def depart_figure(self, node):
666 self.dedent()
666 self.dedent()
667 self.dedent()
667 self.dedent()
668
668
669 def visit_footer(self, node):
669 def visit_footer(self, node):
670 self.document.reporter.warning('"footer" not supported',
670 self.document.reporter.warning('"footer" not supported',
671 base_node=node)
671 base_node=node)
672
672
673 def depart_footer(self, node):
673 def depart_footer(self, node):
674 pass
674 pass
675
675
676 def visit_footnote(self, node):
676 def visit_footnote(self, node):
677 num, text = node.astext().split(None, 1)
677 num, text = node.astext().split(None, 1)
678 num = num.strip()
678 num = num.strip()
679 self.body.append('.IP [%s] 5\n' % self.deunicode(num))
679 self.body.append('.IP [%s] 5\n' % self.deunicode(num))
680
680
681 def depart_footnote(self, node):
681 def depart_footnote(self, node):
682 pass
682 pass
683
683
684 def footnote_backrefs(self, node):
684 def footnote_backrefs(self, node):
685 self.document.reporter.warning('"footnote_backrefs" not supported',
685 self.document.reporter.warning('"footnote_backrefs" not supported',
686 base_node=node)
686 base_node=node)
687
687
688 def visit_footnote_reference(self, node):
688 def visit_footnote_reference(self, node):
689 self.body.append('['+self.deunicode(node.astext())+']')
689 self.body.append('['+self.deunicode(node.astext())+']')
690 raise nodes.SkipNode
690 raise nodes.SkipNode
691
691
692 def depart_footnote_reference(self, node):
692 def depart_footnote_reference(self, node):
693 pass
693 pass
694
694
695 def visit_generated(self, node):
695 def visit_generated(self, node):
696 pass
696 pass
697
697
698 def depart_generated(self, node):
698 def depart_generated(self, node):
699 pass
699 pass
700
700
701 def visit_header(self, node):
701 def visit_header(self, node):
702 raise NotImplementedError, node.astext()
702 raise NotImplementedError, node.astext()
703
703
704 def depart_header(self, node):
704 def depart_header(self, node):
705 pass
705 pass
706
706
707 def visit_hint(self, node):
707 def visit_hint(self, node):
708 self.visit_admonition(node, 'hint')
708 self.visit_admonition(node, 'hint')
709
709
710 depart_hint = depart_admonition
710 depart_hint = depart_admonition
711
711
712 def visit_subscript(self, node):
712 def visit_subscript(self, node):
713 self.body.append('\\s-2\\d')
713 self.body.append('\\s-2\\d')
714
714
715 def depart_subscript(self, node):
715 def depart_subscript(self, node):
716 self.body.append('\\u\\s0')
716 self.body.append('\\u\\s0')
717
717
718 def visit_superscript(self, node):
718 def visit_superscript(self, node):
719 self.body.append('\\s-2\\u')
719 self.body.append('\\s-2\\u')
720
720
721 def depart_superscript(self, node):
721 def depart_superscript(self, node):
722 self.body.append('\\d\\s0')
722 self.body.append('\\d\\s0')
723
723
724 def visit_attribution(self, node):
724 def visit_attribution(self, node):
725 self.body.append('\\(em ')
725 self.body.append('\\(em ')
726
726
727 def depart_attribution(self, node):
727 def depart_attribution(self, node):
728 self.body.append('\n')
728 self.body.append('\n')
729
729
730 def visit_image(self, node):
730 def visit_image(self, node):
731 self.document.reporter.warning('"image" not supported',
731 self.document.reporter.warning('"image" not supported',
732 base_node=node)
732 base_node=node)
733 text = []
733 text = []
734 if 'alt' in node.attributes:
734 if 'alt' in node.attributes:
735 text.append(node.attributes['alt'])
735 text.append(node.attributes['alt'])
736 if 'uri' in node.attributes:
736 if 'uri' in node.attributes:
737 text.append(node.attributes['uri'])
737 text.append(node.attributes['uri'])
738 self.body.append('[image: %s]\n' % ('/'.join(text)))
738 self.body.append('[image: %s]\n' % ('/'.join(text)))
739 raise nodes.SkipNode
739 raise nodes.SkipNode
740
740
741 def visit_important(self, node):
741 def visit_important(self, node):
742 self.visit_admonition(node, 'important')
742 self.visit_admonition(node, 'important')
743
743
744 depart_important = depart_admonition
744 depart_important = depart_admonition
745
745
746 def visit_label(self, node):
746 def visit_label(self, node):
747 # footnote and citation
747 # footnote and citation
748 if (isinstance(node.parent, nodes.footnote)
748 if (isinstance(node.parent, nodes.footnote)
749 or isinstance(node.parent, nodes.citation)):
749 or isinstance(node.parent, nodes.citation)):
750 raise nodes.SkipNode
750 raise nodes.SkipNode
751 self.document.reporter.warning('"unsupported "label"',
751 self.document.reporter.warning('"unsupported "label"',
752 base_node=node)
752 base_node=node)
753 self.body.append('[')
753 self.body.append('[')
754
754
755 def depart_label(self, node):
755 def depart_label(self, node):
756 self.body.append(']\n')
756 self.body.append(']\n')
757
757
758 def visit_legend(self, node):
758 def visit_legend(self, node):
759 pass
759 pass
760
760
761 def depart_legend(self, node):
761 def depart_legend(self, node):
762 pass
762 pass
763
763
764 # WHAT should we use .INDENT, .UNINDENT ?
764 # WHAT should we use .INDENT, .UNINDENT ?
765 def visit_line_block(self, node):
765 def visit_line_block(self, node):
766 self._line_block += 1
766 self._line_block += 1
767 if self._line_block == 1:
767 if self._line_block == 1:
768 self.body.append('.sp\n')
768 self.body.append('.sp\n')
769 self.body.append('.nf\n')
769 self.body.append('.nf\n')
770 else:
770 else:
771 self.body.append('.in +2\n')
771 self.body.append('.in +2\n')
772
772
773 def depart_line_block(self, node):
773 def depart_line_block(self, node):
774 self._line_block -= 1
774 self._line_block -= 1
775 if self._line_block == 0:
775 if self._line_block == 0:
776 self.body.append('.fi\n')
776 self.body.append('.fi\n')
777 self.body.append('.sp\n')
777 self.body.append('.sp\n')
778 else:
778 else:
779 self.body.append('.in -2\n')
779 self.body.append('.in -2\n')
780
780
781 def visit_line(self, node):
781 def visit_line(self, node):
782 pass
782 pass
783
783
784 def depart_line(self, node):
784 def depart_line(self, node):
785 self.body.append('\n')
785 self.body.append('\n')
786
786
787 def visit_list_item(self, node):
787 def visit_list_item(self, node):
788 # man 7 man argues to use ".IP" instead of ".TP"
788 # man 7 man argues to use ".IP" instead of ".TP"
789 self.body.append('.IP %s %d\n' % (
789 self.body.append('.IP %s %d\n' % (
790 self._list_char[-1].next(),
790 self._list_char[-1].next(),
791 self._list_char[-1].get_width(),))
791 self._list_char[-1].get_width(),))
792
792
793 def depart_list_item(self, node):
793 def depart_list_item(self, node):
794 pass
794 pass
795
795
796 def visit_literal(self, node):
796 def visit_literal(self, node):
797 self.body.append(self.defs['literal'][0])
797 self.body.append(self.defs['literal'][0])
798
798
799 def depart_literal(self, node):
799 def depart_literal(self, node):
800 self.body.append(self.defs['literal'][1])
800 self.body.append(self.defs['literal'][1])
801
801
802 def visit_literal_block(self, node):
802 def visit_literal_block(self, node):
803 self.body.append(self.defs['literal_block'][0])
803 self.body.append(self.defs['literal_block'][0])
804 self._in_literal = True
804 self._in_literal = True
805
805
806 def depart_literal_block(self, node):
806 def depart_literal_block(self, node):
807 self._in_literal = False
807 self._in_literal = False
808 self.body.append(self.defs['literal_block'][1])
808 self.body.append(self.defs['literal_block'][1])
809
809
810 def visit_meta(self, node):
810 def visit_meta(self, node):
811 raise NotImplementedError, node.astext()
811 raise NotImplementedError, node.astext()
812
812
813 def depart_meta(self, node):
813 def depart_meta(self, node):
814 pass
814 pass
815
815
816 def visit_note(self, node):
816 def visit_note(self, node):
817 self.visit_admonition(node, 'note')
817 self.visit_admonition(node, 'note')
818
818
819 depart_note = depart_admonition
819 depart_note = depart_admonition
820
820
821 def indent(self, by=0.5):
821 def indent(self, by=0.5):
822 # if we are in a section ".SH" there already is a .RS
822 # if we are in a section ".SH" there already is a .RS
823 step = self._indent[-1]
823 step = self._indent[-1]
824 self._indent.append(by)
824 self._indent.append(by)
825 self.body.append(self.defs['indent'][0] % step)
825 self.body.append(self.defs['indent'][0] % step)
826
826
827 def dedent(self):
827 def dedent(self):
828 self._indent.pop()
828 self._indent.pop()
829 self.body.append(self.defs['indent'][1])
829 self.body.append(self.defs['indent'][1])
830
830
831 def visit_option_list(self, node):
831 def visit_option_list(self, node):
832 self.indent(OPTION_LIST_INDENT)
832 self.indent(OPTION_LIST_INDENT)
833
833
834 def depart_option_list(self, node):
834 def depart_option_list(self, node):
835 self.dedent()
835 self.dedent()
836
836
837 def visit_option_list_item(self, node):
837 def visit_option_list_item(self, node):
838 # one item of the list
838 # one item of the list
839 self.body.append(self.defs['option_list_item'][0])
839 self.body.append(self.defs['option_list_item'][0])
840
840
841 def depart_option_list_item(self, node):
841 def depart_option_list_item(self, node):
842 self.body.append(self.defs['option_list_item'][1])
842 self.body.append(self.defs['option_list_item'][1])
843
843
844 def visit_option_group(self, node):
844 def visit_option_group(self, node):
845 # as one option could have several forms it is a group
845 # as one option could have several forms it is a group
846 # options without parameter bold only, .B, -v
846 # options without parameter bold only, .B, -v
847 # options with parameter bold italic, .BI, -f file
847 # options with parameter bold italic, .BI, -f file
848 #
848 #
849 # we do not know if .B or .BI
849 # we do not know if .B or .BI
850 self.context.append('.B') # blind guess
850 self.context.append('.B') # blind guess
851 self.context.append(len(self.body)) # to be able to insert later
851 self.context.append(len(self.body)) # to be able to insert later
852 self.context.append(0) # option counter
852 self.context.append(0) # option counter
853
853
854 def depart_option_group(self, node):
854 def depart_option_group(self, node):
855 self.context.pop() # the counter
855 self.context.pop() # the counter
856 start_position = self.context.pop()
856 start_position = self.context.pop()
857 text = self.body[start_position:]
857 text = self.body[start_position:]
858 del self.body[start_position:]
858 del self.body[start_position:]
859 self.body.append('%s%s\n' % (self.context.pop(), ''.join(text)))
859 self.body.append('%s%s\n' % (self.context.pop(), ''.join(text)))
860
860
861 def visit_option(self, node):
861 def visit_option(self, node):
862 # each form of the option will be presented separately
862 # each form of the option will be presented separately
863 if self.context[-1] > 0:
863 if self.context[-1] > 0:
864 self.body.append(', ')
864 self.body.append(', ')
865 if self.context[-3] == '.BI':
865 if self.context[-3] == '.BI':
866 self.body.append('\\')
866 self.body.append('\\')
867 self.body.append(' ')
867 self.body.append(' ')
868
868
869 def depart_option(self, node):
869 def depart_option(self, node):
870 self.context[-1] += 1
870 self.context[-1] += 1
871
871
872 def visit_option_string(self, node):
872 def visit_option_string(self, node):
873 # do not know if .B or .BI
873 # do not know if .B or .BI
874 pass
874 pass
875
875
876 def depart_option_string(self, node):
876 def depart_option_string(self, node):
877 pass
877 pass
878
878
879 def visit_option_argument(self, node):
879 def visit_option_argument(self, node):
880 self.context[-3] = '.BI' # bold/italic alternate
880 self.context[-3] = '.BI' # bold/italic alternate
881 if node['delimiter'] != ' ':
881 if node['delimiter'] != ' ':
882 self.body.append('\\fB%s ' % node['delimiter'])
882 self.body.append('\\fB%s ' % node['delimiter'])
883 elif self.body[len(self.body)-1].endswith('='):
883 elif self.body[len(self.body)-1].endswith('='):
884 # a blank only means no blank in output, just changing font
884 # a blank only means no blank in output, just changing font
885 self.body.append(' ')
885 self.body.append(' ')
886 else:
886 else:
887 # blank backslash blank, switch font then a blank
887 # blank backslash blank, switch font then a blank
888 self.body.append(' \\ ')
888 self.body.append(' \\ ')
889
889
890 def depart_option_argument(self, node):
890 def depart_option_argument(self, node):
891 pass
891 pass
892
892
893 def visit_organization(self, node):
893 def visit_organization(self, node):
894 self.visit_docinfo_item(node, 'organization')
894 self.visit_docinfo_item(node, 'organization')
895
895
896 def depart_organization(self, node):
896 def depart_organization(self, node):
897 pass
897 pass
898
898
899 def visit_paragraph(self, node):
899 def visit_paragraph(self, node):
900 # ``.PP`` : Start standard indented paragraph.
900 # ``.PP`` : Start standard indented paragraph.
901 # ``.LP`` : Start block paragraph, all except the first.
901 # ``.LP`` : Start block paragraph, all except the first.
902 # ``.P [type]`` : Start paragraph type.
902 # ``.P [type]`` : Start paragraph type.
903 # NOTE dont use paragraph starts because they reset indentation.
903 # NOTE don't use paragraph starts because they reset indentation.
904 # ``.sp`` is only vertical space
904 # ``.sp`` is only vertical space
905 self.ensure_eol()
905 self.ensure_eol()
906 self.body.append('.sp\n')
906 self.body.append('.sp\n')
907
907
908 def depart_paragraph(self, node):
908 def depart_paragraph(self, node):
909 self.body.append('\n')
909 self.body.append('\n')
910
910
911 def visit_problematic(self, node):
911 def visit_problematic(self, node):
912 self.body.append(self.defs['problematic'][0])
912 self.body.append(self.defs['problematic'][0])
913
913
914 def depart_problematic(self, node):
914 def depart_problematic(self, node):
915 self.body.append(self.defs['problematic'][1])
915 self.body.append(self.defs['problematic'][1])
916
916
917 def visit_raw(self, node):
917 def visit_raw(self, node):
918 if node.get('format') == 'manpage':
918 if node.get('format') == 'manpage':
919 self.body.append(node.astext() + "\n")
919 self.body.append(node.astext() + "\n")
920 # Keep non-manpage raw text out of output:
920 # Keep non-manpage raw text out of output:
921 raise nodes.SkipNode
921 raise nodes.SkipNode
922
922
923 def visit_reference(self, node):
923 def visit_reference(self, node):
924 """E.g. link or email address."""
924 """E.g. link or email address."""
925 self.body.append(self.defs['reference'][0])
925 self.body.append(self.defs['reference'][0])
926
926
927 def depart_reference(self, node):
927 def depart_reference(self, node):
928 self.body.append(self.defs['reference'][1])
928 self.body.append(self.defs['reference'][1])
929
929
930 def visit_revision(self, node):
930 def visit_revision(self, node):
931 self.visit_docinfo_item(node, 'revision')
931 self.visit_docinfo_item(node, 'revision')
932
932
933 depart_revision = depart_docinfo_item
933 depart_revision = depart_docinfo_item
934
934
935 def visit_row(self, node):
935 def visit_row(self, node):
936 self._active_table.new_row()
936 self._active_table.new_row()
937
937
938 def depart_row(self, node):
938 def depart_row(self, node):
939 pass
939 pass
940
940
941 def visit_section(self, node):
941 def visit_section(self, node):
942 self.section_level += 1
942 self.section_level += 1
943
943
944 def depart_section(self, node):
944 def depart_section(self, node):
945 self.section_level -= 1
945 self.section_level -= 1
946
946
947 def visit_status(self, node):
947 def visit_status(self, node):
948 self.visit_docinfo_item(node, 'status')
948 self.visit_docinfo_item(node, 'status')
949
949
950 depart_status = depart_docinfo_item
950 depart_status = depart_docinfo_item
951
951
952 def visit_strong(self, node):
952 def visit_strong(self, node):
953 self.body.append(self.defs['strong'][0])
953 self.body.append(self.defs['strong'][0])
954
954
955 def depart_strong(self, node):
955 def depart_strong(self, node):
956 self.body.append(self.defs['strong'][1])
956 self.body.append(self.defs['strong'][1])
957
957
958 def visit_substitution_definition(self, node):
958 def visit_substitution_definition(self, node):
959 """Internal only."""
959 """Internal only."""
960 raise nodes.SkipNode
960 raise nodes.SkipNode
961
961
962 def visit_substitution_reference(self, node):
962 def visit_substitution_reference(self, node):
963 self.document.reporter.warning('"substitution_reference" not supported',
963 self.document.reporter.warning('"substitution_reference" not supported',
964 base_node=node)
964 base_node=node)
965
965
966 def visit_subtitle(self, node):
966 def visit_subtitle(self, node):
967 if isinstance(node.parent, nodes.sidebar):
967 if isinstance(node.parent, nodes.sidebar):
968 self.body.append(self.defs['strong'][0])
968 self.body.append(self.defs['strong'][0])
969 elif isinstance(node.parent, nodes.document):
969 elif isinstance(node.parent, nodes.document):
970 self.visit_docinfo_item(node, 'subtitle')
970 self.visit_docinfo_item(node, 'subtitle')
971 elif isinstance(node.parent, nodes.section):
971 elif isinstance(node.parent, nodes.section):
972 self.body.append(self.defs['strong'][0])
972 self.body.append(self.defs['strong'][0])
973
973
974 def depart_subtitle(self, node):
974 def depart_subtitle(self, node):
975 # document subtitle calls SkipNode
975 # document subtitle calls SkipNode
976 self.body.append(self.defs['strong'][1]+'\n.PP\n')
976 self.body.append(self.defs['strong'][1]+'\n.PP\n')
977
977
978 def visit_system_message(self, node):
978 def visit_system_message(self, node):
979 # TODO add report_level
979 # TODO add report_level
980 #if node['level'] < self.document.reporter['writer'].report_level:
980 #if node['level'] < self.document.reporter['writer'].report_level:
981 # Level is too low to display:
981 # Level is too low to display:
982 # raise nodes.SkipNode
982 # raise nodes.SkipNode
983 attr = {}
983 attr = {}
984 backref_text = ''
984 backref_text = ''
985 if node.hasattr('id'):
985 if node.hasattr('id'):
986 attr['name'] = node['id']
986 attr['name'] = node['id']
987 if node.hasattr('line'):
987 if node.hasattr('line'):
988 line = ', line %s' % node['line']
988 line = ', line %s' % node['line']
989 else:
989 else:
990 line = ''
990 line = ''
991 self.body.append('.IP "System Message: %s/%s (%s:%s)"\n'
991 self.body.append('.IP "System Message: %s/%s (%s:%s)"\n'
992 % (node['type'], node['level'], node['source'], line))
992 % (node['type'], node['level'], node['source'], line))
993
993
994 def depart_system_message(self, node):
994 def depart_system_message(self, node):
995 pass
995 pass
996
996
997 def visit_table(self, node):
997 def visit_table(self, node):
998 self._active_table = Table()
998 self._active_table = Table()
999
999
1000 def depart_table(self, node):
1000 def depart_table(self, node):
1001 self.ensure_eol()
1001 self.ensure_eol()
1002 self.body.extend(self._active_table.as_list())
1002 self.body.extend(self._active_table.as_list())
1003 self._active_table = None
1003 self._active_table = None
1004
1004
1005 def visit_target(self, node):
1005 def visit_target(self, node):
1006 # targets are in-document hyper targets, without any use for man-pages.
1006 # targets are in-document hyper targets, without any use for man-pages.
1007 raise nodes.SkipNode
1007 raise nodes.SkipNode
1008
1008
1009 def visit_tbody(self, node):
1009 def visit_tbody(self, node):
1010 pass
1010 pass
1011
1011
1012 def depart_tbody(self, node):
1012 def depart_tbody(self, node):
1013 pass
1013 pass
1014
1014
1015 def visit_term(self, node):
1015 def visit_term(self, node):
1016 self.body.append(self.defs['term'][0])
1016 self.body.append(self.defs['term'][0])
1017
1017
1018 def depart_term(self, node):
1018 def depart_term(self, node):
1019 self.body.append(self.defs['term'][1])
1019 self.body.append(self.defs['term'][1])
1020
1020
1021 def visit_tgroup(self, node):
1021 def visit_tgroup(self, node):
1022 pass
1022 pass
1023
1023
1024 def depart_tgroup(self, node):
1024 def depart_tgroup(self, node):
1025 pass
1025 pass
1026
1026
1027 def visit_thead(self, node):
1027 def visit_thead(self, node):
1028 # MAYBE double line '='
1028 # MAYBE double line '='
1029 pass
1029 pass
1030
1030
1031 def depart_thead(self, node):
1031 def depart_thead(self, node):
1032 # MAYBE double line '='
1032 # MAYBE double line '='
1033 pass
1033 pass
1034
1034
1035 def visit_tip(self, node):
1035 def visit_tip(self, node):
1036 self.visit_admonition(node, 'tip')
1036 self.visit_admonition(node, 'tip')
1037
1037
1038 depart_tip = depart_admonition
1038 depart_tip = depart_admonition
1039
1039
1040 def visit_title(self, node):
1040 def visit_title(self, node):
1041 if isinstance(node.parent, nodes.topic):
1041 if isinstance(node.parent, nodes.topic):
1042 self.body.append(self.defs['topic-title'][0])
1042 self.body.append(self.defs['topic-title'][0])
1043 elif isinstance(node.parent, nodes.sidebar):
1043 elif isinstance(node.parent, nodes.sidebar):
1044 self.body.append(self.defs['sidebar-title'][0])
1044 self.body.append(self.defs['sidebar-title'][0])
1045 elif isinstance(node.parent, nodes.admonition):
1045 elif isinstance(node.parent, nodes.admonition):
1046 self.body.append('.IP "')
1046 self.body.append('.IP "')
1047 elif self.section_level == 0:
1047 elif self.section_level == 0:
1048 self._docinfo['title'] = node.astext()
1048 self._docinfo['title'] = node.astext()
1049 # document title for .TH
1049 # document title for .TH
1050 self._docinfo['title_upper'] = node.astext().upper()
1050 self._docinfo['title_upper'] = node.astext().upper()
1051 raise nodes.SkipNode
1051 raise nodes.SkipNode
1052 elif self.section_level == 1:
1052 elif self.section_level == 1:
1053 self.body.append('.SH ')
1053 self.body.append('.SH ')
1054 for n in node.traverse(nodes.Text):
1054 for n in node.traverse(nodes.Text):
1055 n.parent.replace(n, nodes.Text(n.astext().upper()))
1055 n.parent.replace(n, nodes.Text(n.astext().upper()))
1056 else:
1056 else:
1057 self.body.append('.SS ')
1057 self.body.append('.SS ')
1058
1058
1059 def depart_title(self, node):
1059 def depart_title(self, node):
1060 if isinstance(node.parent, nodes.admonition):
1060 if isinstance(node.parent, nodes.admonition):
1061 self.body.append('"')
1061 self.body.append('"')
1062 self.body.append('\n')
1062 self.body.append('\n')
1063
1063
1064 def visit_title_reference(self, node):
1064 def visit_title_reference(self, node):
1065 """inline citation reference"""
1065 """inline citation reference"""
1066 self.body.append(self.defs['title_reference'][0])
1066 self.body.append(self.defs['title_reference'][0])
1067
1067
1068 def depart_title_reference(self, node):
1068 def depart_title_reference(self, node):
1069 self.body.append(self.defs['title_reference'][1])
1069 self.body.append(self.defs['title_reference'][1])
1070
1070
1071 def visit_topic(self, node):
1071 def visit_topic(self, node):
1072 pass
1072 pass
1073
1073
1074 def depart_topic(self, node):
1074 def depart_topic(self, node):
1075 pass
1075 pass
1076
1076
1077 def visit_sidebar(self, node):
1077 def visit_sidebar(self, node):
1078 pass
1078 pass
1079
1079
1080 def depart_sidebar(self, node):
1080 def depart_sidebar(self, node):
1081 pass
1081 pass
1082
1082
1083 def visit_rubric(self, node):
1083 def visit_rubric(self, node):
1084 pass
1084 pass
1085
1085
1086 def depart_rubric(self, node):
1086 def depart_rubric(self, node):
1087 pass
1087 pass
1088
1088
1089 def visit_transition(self, node):
1089 def visit_transition(self, node):
1090 # .PP Begin a new paragraph and reset prevailing indent.
1090 # .PP Begin a new paragraph and reset prevailing indent.
1091 # .sp N leaves N lines of blank space.
1091 # .sp N leaves N lines of blank space.
1092 # .ce centers the next line
1092 # .ce centers the next line
1093 self.body.append('\n.sp\n.ce\n----\n')
1093 self.body.append('\n.sp\n.ce\n----\n')
1094
1094
1095 def depart_transition(self, node):
1095 def depart_transition(self, node):
1096 self.body.append('\n.ce 0\n.sp\n')
1096 self.body.append('\n.ce 0\n.sp\n')
1097
1097
1098 def visit_version(self, node):
1098 def visit_version(self, node):
1099 self.visit_docinfo_item(node, 'version')
1099 self.visit_docinfo_item(node, 'version')
1100
1100
1101 def visit_warning(self, node):
1101 def visit_warning(self, node):
1102 self.visit_admonition(node, 'warning')
1102 self.visit_admonition(node, 'warning')
1103
1103
1104 depart_warning = depart_admonition
1104 depart_warning = depart_admonition
1105
1105
1106 def unimplemented_visit(self, node):
1106 def unimplemented_visit(self, node):
1107 raise NotImplementedError('visiting unimplemented node type: %s'
1107 raise NotImplementedError('visiting unimplemented node type: %s'
1108 % node.__class__.__name__)
1108 % node.__class__.__name__)
1109
1109
1110 # vim: set fileencoding=utf-8 et ts=4 ai :
1110 # vim: set fileencoding=utf-8 et ts=4 ai :
@@ -1,915 +1,915 b''
1 # bugzilla.py - bugzilla integration for mercurial
1 # bugzilla.py - bugzilla integration for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2011-2 Jim Hague <jim.hague@acm.org>
4 # Copyright 2011-2 Jim Hague <jim.hague@acm.org>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''hooks for integrating with the Bugzilla bug tracker
9 '''hooks for integrating with the Bugzilla bug tracker
10
10
11 This hook extension adds comments on bugs in Bugzilla when changesets
11 This hook extension adds comments on bugs in Bugzilla when changesets
12 that refer to bugs by Bugzilla ID are seen. The comment is formatted using
12 that refer to bugs by Bugzilla ID are seen. The comment is formatted using
13 the Mercurial template mechanism.
13 the Mercurial template mechanism.
14
14
15 The bug references can optionally include an update for Bugzilla of the
15 The bug references can optionally include an update for Bugzilla of the
16 hours spent working on the bug. Bugs can also be marked fixed.
16 hours spent working on the bug. Bugs can also be marked fixed.
17
17
18 Three basic modes of access to Bugzilla are provided:
18 Three basic modes of access to Bugzilla are provided:
19
19
20 1. Access via the Bugzilla XMLRPC interface. Requires Bugzilla 3.4 or later.
20 1. Access via the Bugzilla XMLRPC interface. Requires Bugzilla 3.4 or later.
21
21
22 2. Check data via the Bugzilla XMLRPC interface and submit bug change
22 2. Check data via the Bugzilla XMLRPC interface and submit bug change
23 via email to Bugzilla email interface. Requires Bugzilla 3.4 or later.
23 via email to Bugzilla email interface. Requires Bugzilla 3.4 or later.
24
24
25 3. Writing directly to the Bugzilla database. Only Bugzilla installations
25 3. Writing directly to the Bugzilla database. Only Bugzilla installations
26 using MySQL are supported. Requires Python MySQLdb.
26 using MySQL are supported. Requires Python MySQLdb.
27
27
28 Writing directly to the database is susceptible to schema changes, and
28 Writing directly to the database is susceptible to schema changes, and
29 relies on a Bugzilla contrib script to send out bug change
29 relies on a Bugzilla contrib script to send out bug change
30 notification emails. This script runs as the user running Mercurial,
30 notification emails. This script runs as the user running Mercurial,
31 must be run on the host with the Bugzilla install, and requires
31 must be run on the host with the Bugzilla install, and requires
32 permission to read Bugzilla configuration details and the necessary
32 permission to read Bugzilla configuration details and the necessary
33 MySQL user and password to have full access rights to the Bugzilla
33 MySQL user and password to have full access rights to the Bugzilla
34 database. For these reasons this access mode is now considered
34 database. For these reasons this access mode is now considered
35 deprecated, and will not be updated for new Bugzilla versions going
35 deprecated, and will not be updated for new Bugzilla versions going
36 forward. Only adding comments is supported in this access mode.
36 forward. Only adding comments is supported in this access mode.
37
37
38 Access via XMLRPC needs a Bugzilla username and password to be specified
38 Access via XMLRPC needs a Bugzilla username and password to be specified
39 in the configuration. Comments are added under that username. Since the
39 in the configuration. Comments are added under that username. Since the
40 configuration must be readable by all Mercurial users, it is recommended
40 configuration must be readable by all Mercurial users, it is recommended
41 that the rights of that user are restricted in Bugzilla to the minimum
41 that the rights of that user are restricted in Bugzilla to the minimum
42 necessary to add comments. Marking bugs fixed requires Bugzilla 4.0 and later.
42 necessary to add comments. Marking bugs fixed requires Bugzilla 4.0 and later.
43
43
44 Access via XMLRPC/email uses XMLRPC to query Bugzilla, but sends
44 Access via XMLRPC/email uses XMLRPC to query Bugzilla, but sends
45 email to the Bugzilla email interface to submit comments to bugs.
45 email to the Bugzilla email interface to submit comments to bugs.
46 The From: address in the email is set to the email address of the Mercurial
46 The From: address in the email is set to the email address of the Mercurial
47 user, so the comment appears to come from the Mercurial user. In the event
47 user, so the comment appears to come from the Mercurial user. In the event
48 that the Mercurial user email is not recognised by Bugzilla as a Bugzilla
48 that the Mercurial user email is not recognized by Bugzilla as a Bugzilla
49 user, the email associated with the Bugzilla username used to log into
49 user, the email associated with the Bugzilla username used to log into
50 Bugzilla is used instead as the source of the comment. Marking bugs fixed
50 Bugzilla is used instead as the source of the comment. Marking bugs fixed
51 works on all supported Bugzilla versions.
51 works on all supported Bugzilla versions.
52
52
53 Configuration items common to all access modes:
53 Configuration items common to all access modes:
54
54
55 bugzilla.version
55 bugzilla.version
56 This access type to use. Values recognised are:
56 The access type to use. Values recognized are:
57
57
58 :``xmlrpc``: Bugzilla XMLRPC interface.
58 :``xmlrpc``: Bugzilla XMLRPC interface.
59 :``xmlrpc+email``: Bugzilla XMLRPC and email interfaces.
59 :``xmlrpc+email``: Bugzilla XMLRPC and email interfaces.
60 :``3.0``: MySQL access, Bugzilla 3.0 and later.
60 :``3.0``: MySQL access, Bugzilla 3.0 and later.
61 :``2.18``: MySQL access, Bugzilla 2.18 and up to but not
61 :``2.18``: MySQL access, Bugzilla 2.18 and up to but not
62 including 3.0.
62 including 3.0.
63 :``2.16``: MySQL access, Bugzilla 2.16 and up to but not
63 :``2.16``: MySQL access, Bugzilla 2.16 and up to but not
64 including 2.18.
64 including 2.18.
65
65
66 bugzilla.regexp
66 bugzilla.regexp
67 Regular expression to match bug IDs for update in changeset commit message.
67 Regular expression to match bug IDs for update in changeset commit message.
68 It must contain one "()" named group ``<ids>`` containing the bug
68 It must contain one "()" named group ``<ids>`` containing the bug
69 IDs separated by non-digit characters. It may also contain
69 IDs separated by non-digit characters. It may also contain
70 a named group ``<hours>`` with a floating-point number giving the
70 a named group ``<hours>`` with a floating-point number giving the
71 hours worked on the bug. If no named groups are present, the first
71 hours worked on the bug. If no named groups are present, the first
72 "()" group is assumed to contain the bug IDs, and work time is not
72 "()" group is assumed to contain the bug IDs, and work time is not
73 updated. The default expression matches ``Bug 1234``, ``Bug no. 1234``,
73 updated. The default expression matches ``Bug 1234``, ``Bug no. 1234``,
74 ``Bug number 1234``, ``Bugs 1234,5678``, ``Bug 1234 and 5678`` and
74 ``Bug number 1234``, ``Bugs 1234,5678``, ``Bug 1234 and 5678`` and
75 variations thereof, followed by an hours number prefixed by ``h`` or
75 variations thereof, followed by an hours number prefixed by ``h`` or
76 ``hours``, e.g. ``hours 1.5``. Matching is case insensitive.
76 ``hours``, e.g. ``hours 1.5``. Matching is case insensitive.
77
77
78 bugzilla.fixregexp
78 bugzilla.fixregexp
79 Regular expression to match bug IDs for marking fixed in changeset
79 Regular expression to match bug IDs for marking fixed in changeset
80 commit message. This must contain a "()" named group ``<ids>` containing
80 commit message. This must contain a "()" named group ``<ids>` containing
81 the bug IDs separated by non-digit characters. It may also contain
81 the bug IDs separated by non-digit characters. It may also contain
82 a named group ``<hours>`` with a floating-point number giving the
82 a named group ``<hours>`` with a floating-point number giving the
83 hours worked on the bug. If no named groups are present, the first
83 hours worked on the bug. If no named groups are present, the first
84 "()" group is assumed to contain the bug IDs, and work time is not
84 "()" group is assumed to contain the bug IDs, and work time is not
85 updated. The default expression matches ``Fixes 1234``, ``Fixes bug 1234``,
85 updated. The default expression matches ``Fixes 1234``, ``Fixes bug 1234``,
86 ``Fixes bugs 1234,5678``, ``Fixes 1234 and 5678`` and
86 ``Fixes bugs 1234,5678``, ``Fixes 1234 and 5678`` and
87 variations thereof, followed by an hours number prefixed by ``h`` or
87 variations thereof, followed by an hours number prefixed by ``h`` or
88 ``hours``, e.g. ``hours 1.5``. Matching is case insensitive.
88 ``hours``, e.g. ``hours 1.5``. Matching is case insensitive.
89
89
90 bugzilla.fixstatus
90 bugzilla.fixstatus
91 The status to set a bug to when marking fixed. Default ``RESOLVED``.
91 The status to set a bug to when marking fixed. Default ``RESOLVED``.
92
92
93 bugzilla.fixresolution
93 bugzilla.fixresolution
94 The resolution to set a bug to when marking fixed. Default ``FIXED``.
94 The resolution to set a bug to when marking fixed. Default ``FIXED``.
95
95
96 bugzilla.style
96 bugzilla.style
97 The style file to use when formatting comments.
97 The style file to use when formatting comments.
98
98
99 bugzilla.template
99 bugzilla.template
100 Template to use when formatting comments. Overrides style if
100 Template to use when formatting comments. Overrides style if
101 specified. In addition to the usual Mercurial keywords, the
101 specified. In addition to the usual Mercurial keywords, the
102 extension specifies:
102 extension specifies:
103
103
104 :``{bug}``: The Bugzilla bug ID.
104 :``{bug}``: The Bugzilla bug ID.
105 :``{root}``: The full pathname of the Mercurial repository.
105 :``{root}``: The full pathname of the Mercurial repository.
106 :``{webroot}``: Stripped pathname of the Mercurial repository.
106 :``{webroot}``: Stripped pathname of the Mercurial repository.
107 :``{hgweb}``: Base URL for browsing Mercurial repositories.
107 :``{hgweb}``: Base URL for browsing Mercurial repositories.
108
108
109 Default ``changeset {node|short} in repo {root} refers to bug
109 Default ``changeset {node|short} in repo {root} refers to bug
110 {bug}.\\ndetails:\\n\\t{desc|tabindent}``
110 {bug}.\\ndetails:\\n\\t{desc|tabindent}``
111
111
112 bugzilla.strip
112 bugzilla.strip
113 The number of path separator characters to strip from the front of
113 The number of path separator characters to strip from the front of
114 the Mercurial repository path (``{root}`` in templates) to produce
114 the Mercurial repository path (``{root}`` in templates) to produce
115 ``{webroot}``. For example, a repository with ``{root}``
115 ``{webroot}``. For example, a repository with ``{root}``
116 ``/var/local/my-project`` with a strip of 2 gives a value for
116 ``/var/local/my-project`` with a strip of 2 gives a value for
117 ``{webroot}`` of ``my-project``. Default 0.
117 ``{webroot}`` of ``my-project``. Default 0.
118
118
119 web.baseurl
119 web.baseurl
120 Base URL for browsing Mercurial repositories. Referenced from
120 Base URL for browsing Mercurial repositories. Referenced from
121 templates as ``{hgweb}``.
121 templates as ``{hgweb}``.
122
122
123 Configuration items common to XMLRPC+email and MySQL access modes:
123 Configuration items common to XMLRPC+email and MySQL access modes:
124
124
125 bugzilla.usermap
125 bugzilla.usermap
126 Path of file containing Mercurial committer email to Bugzilla user email
126 Path of file containing Mercurial committer email to Bugzilla user email
127 mappings. If specified, the file should contain one mapping per
127 mappings. If specified, the file should contain one mapping per
128 line::
128 line::
129
129
130 committer = Bugzilla user
130 committer = Bugzilla user
131
131
132 See also the ``[usermap]`` section.
132 See also the ``[usermap]`` section.
133
133
134 The ``[usermap]`` section is used to specify mappings of Mercurial
134 The ``[usermap]`` section is used to specify mappings of Mercurial
135 committer email to Bugzilla user email. See also ``bugzilla.usermap``.
135 committer email to Bugzilla user email. See also ``bugzilla.usermap``.
136 Contains entries of the form ``committer = Bugzilla user``.
136 Contains entries of the form ``committer = Bugzilla user``.
137
137
138 XMLRPC access mode configuration:
138 XMLRPC access mode configuration:
139
139
140 bugzilla.bzurl
140 bugzilla.bzurl
141 The base URL for the Bugzilla installation.
141 The base URL for the Bugzilla installation.
142 Default ``http://localhost/bugzilla``.
142 Default ``http://localhost/bugzilla``.
143
143
144 bugzilla.user
144 bugzilla.user
145 The username to use to log into Bugzilla via XMLRPC. Default
145 The username to use to log into Bugzilla via XMLRPC. Default
146 ``bugs``.
146 ``bugs``.
147
147
148 bugzilla.password
148 bugzilla.password
149 The password for Bugzilla login.
149 The password for Bugzilla login.
150
150
151 XMLRPC+email access mode uses the XMLRPC access mode configuration items,
151 XMLRPC+email access mode uses the XMLRPC access mode configuration items,
152 and also:
152 and also:
153
153
154 bugzilla.bzemail
154 bugzilla.bzemail
155 The Bugzilla email address.
155 The Bugzilla email address.
156
156
157 In addition, the Mercurial email settings must be configured. See the
157 In addition, the Mercurial email settings must be configured. See the
158 documentation in hgrc(5), sections ``[email]`` and ``[smtp]``.
158 documentation in hgrc(5), sections ``[email]`` and ``[smtp]``.
159
159
160 MySQL access mode configuration:
160 MySQL access mode configuration:
161
161
162 bugzilla.host
162 bugzilla.host
163 Hostname of the MySQL server holding the Bugzilla database.
163 Hostname of the MySQL server holding the Bugzilla database.
164 Default ``localhost``.
164 Default ``localhost``.
165
165
166 bugzilla.db
166 bugzilla.db
167 Name of the Bugzilla database in MySQL. Default ``bugs``.
167 Name of the Bugzilla database in MySQL. Default ``bugs``.
168
168
169 bugzilla.user
169 bugzilla.user
170 Username to use to access MySQL server. Default ``bugs``.
170 Username to use to access MySQL server. Default ``bugs``.
171
171
172 bugzilla.password
172 bugzilla.password
173 Password to use to access MySQL server.
173 Password to use to access MySQL server.
174
174
175 bugzilla.timeout
175 bugzilla.timeout
176 Database connection timeout (seconds). Default 5.
176 Database connection timeout (seconds). Default 5.
177
177
178 bugzilla.bzuser
178 bugzilla.bzuser
179 Fallback Bugzilla user name to record comments with, if changeset
179 Fallback Bugzilla user name to record comments with, if changeset
180 committer cannot be found as a Bugzilla user.
180 committer cannot be found as a Bugzilla user.
181
181
182 bugzilla.bzdir
182 bugzilla.bzdir
183 Bugzilla install directory. Used by default notify. Default
183 Bugzilla install directory. Used by default notify. Default
184 ``/var/www/html/bugzilla``.
184 ``/var/www/html/bugzilla``.
185
185
186 bugzilla.notify
186 bugzilla.notify
187 The command to run to get Bugzilla to send bug change notification
187 The command to run to get Bugzilla to send bug change notification
188 emails. Substitutes from a map with 3 keys, ``bzdir``, ``id`` (bug
188 emails. Substitutes from a map with 3 keys, ``bzdir``, ``id`` (bug
189 id) and ``user`` (committer bugzilla email). Default depends on
189 id) and ``user`` (committer bugzilla email). Default depends on
190 version; from 2.18 it is "cd %(bzdir)s && perl -T
190 version; from 2.18 it is "cd %(bzdir)s && perl -T
191 contrib/sendbugmail.pl %(id)s %(user)s".
191 contrib/sendbugmail.pl %(id)s %(user)s".
192
192
193 Activating the extension::
193 Activating the extension::
194
194
195 [extensions]
195 [extensions]
196 bugzilla =
196 bugzilla =
197
197
198 [hooks]
198 [hooks]
199 # run bugzilla hook on every change pulled or pushed in here
199 # run bugzilla hook on every change pulled or pushed in here
200 incoming.bugzilla = python:hgext.bugzilla.hook
200 incoming.bugzilla = python:hgext.bugzilla.hook
201
201
202 Example configurations:
202 Example configurations:
203
203
204 XMLRPC example configuration. This uses the Bugzilla at
204 XMLRPC example configuration. This uses the Bugzilla at
205 ``http://my-project.org/bugzilla``, logging in as user
205 ``http://my-project.org/bugzilla``, logging in as user
206 ``bugmail@my-project.org`` with password ``plugh``. It is used with a
206 ``bugmail@my-project.org`` with password ``plugh``. It is used with a
207 collection of Mercurial repositories in ``/var/local/hg/repos/``,
207 collection of Mercurial repositories in ``/var/local/hg/repos/``,
208 with a web interface at ``http://my-project.org/hg``. ::
208 with a web interface at ``http://my-project.org/hg``. ::
209
209
210 [bugzilla]
210 [bugzilla]
211 bzurl=http://my-project.org/bugzilla
211 bzurl=http://my-project.org/bugzilla
212 user=bugmail@my-project.org
212 user=bugmail@my-project.org
213 password=plugh
213 password=plugh
214 version=xmlrpc
214 version=xmlrpc
215 template=Changeset {node|short} in {root|basename}.
215 template=Changeset {node|short} in {root|basename}.
216 {hgweb}/{webroot}/rev/{node|short}\\n
216 {hgweb}/{webroot}/rev/{node|short}\\n
217 {desc}\\n
217 {desc}\\n
218 strip=5
218 strip=5
219
219
220 [web]
220 [web]
221 baseurl=http://my-project.org/hg
221 baseurl=http://my-project.org/hg
222
222
223 XMLRPC+email example configuration. This uses the Bugzilla at
223 XMLRPC+email example configuration. This uses the Bugzilla at
224 ``http://my-project.org/bugzilla``, logging in as user
224 ``http://my-project.org/bugzilla``, logging in as user
225 ``bugmail@my-project.org`` with password ``plugh``. It is used with a
225 ``bugmail@my-project.org`` with password ``plugh``. It is used with a
226 collection of Mercurial repositories in ``/var/local/hg/repos/``,
226 collection of Mercurial repositories in ``/var/local/hg/repos/``,
227 with a web interface at ``http://my-project.org/hg``. Bug comments
227 with a web interface at ``http://my-project.org/hg``. Bug comments
228 are sent to the Bugzilla email address
228 are sent to the Bugzilla email address
229 ``bugzilla@my-project.org``. ::
229 ``bugzilla@my-project.org``. ::
230
230
231 [bugzilla]
231 [bugzilla]
232 bzurl=http://my-project.org/bugzilla
232 bzurl=http://my-project.org/bugzilla
233 user=bugmail@my-project.org
233 user=bugmail@my-project.org
234 password=plugh
234 password=plugh
235 version=xmlrpc
235 version=xmlrpc
236 bzemail=bugzilla@my-project.org
236 bzemail=bugzilla@my-project.org
237 template=Changeset {node|short} in {root|basename}.
237 template=Changeset {node|short} in {root|basename}.
238 {hgweb}/{webroot}/rev/{node|short}\\n
238 {hgweb}/{webroot}/rev/{node|short}\\n
239 {desc}\\n
239 {desc}\\n
240 strip=5
240 strip=5
241
241
242 [web]
242 [web]
243 baseurl=http://my-project.org/hg
243 baseurl=http://my-project.org/hg
244
244
245 [usermap]
245 [usermap]
246 user@emaildomain.com=user.name@bugzilladomain.com
246 user@emaildomain.com=user.name@bugzilladomain.com
247
247
248 MySQL example configuration. This has a local Bugzilla 3.2 installation
248 MySQL example configuration. This has a local Bugzilla 3.2 installation
249 in ``/opt/bugzilla-3.2``. The MySQL database is on ``localhost``,
249 in ``/opt/bugzilla-3.2``. The MySQL database is on ``localhost``,
250 the Bugzilla database name is ``bugs`` and MySQL is
250 the Bugzilla database name is ``bugs`` and MySQL is
251 accessed with MySQL username ``bugs`` password ``XYZZY``. It is used
251 accessed with MySQL username ``bugs`` password ``XYZZY``. It is used
252 with a collection of Mercurial repositories in ``/var/local/hg/repos/``,
252 with a collection of Mercurial repositories in ``/var/local/hg/repos/``,
253 with a web interface at ``http://my-project.org/hg``. ::
253 with a web interface at ``http://my-project.org/hg``. ::
254
254
255 [bugzilla]
255 [bugzilla]
256 host=localhost
256 host=localhost
257 password=XYZZY
257 password=XYZZY
258 version=3.0
258 version=3.0
259 bzuser=unknown@domain.com
259 bzuser=unknown@domain.com
260 bzdir=/opt/bugzilla-3.2
260 bzdir=/opt/bugzilla-3.2
261 template=Changeset {node|short} in {root|basename}.
261 template=Changeset {node|short} in {root|basename}.
262 {hgweb}/{webroot}/rev/{node|short}\\n
262 {hgweb}/{webroot}/rev/{node|short}\\n
263 {desc}\\n
263 {desc}\\n
264 strip=5
264 strip=5
265
265
266 [web]
266 [web]
267 baseurl=http://my-project.org/hg
267 baseurl=http://my-project.org/hg
268
268
269 [usermap]
269 [usermap]
270 user@emaildomain.com=user.name@bugzilladomain.com
270 user@emaildomain.com=user.name@bugzilladomain.com
271
271
272 All the above add a comment to the Bugzilla bug record of the form::
272 All the above add a comment to the Bugzilla bug record of the form::
273
273
274 Changeset 3b16791d6642 in repository-name.
274 Changeset 3b16791d6642 in repository-name.
275 http://my-project.org/hg/repository-name/rev/3b16791d6642
275 http://my-project.org/hg/repository-name/rev/3b16791d6642
276
276
277 Changeset commit comment. Bug 1234.
277 Changeset commit comment. Bug 1234.
278 '''
278 '''
279
279
280 from mercurial.i18n import _
280 from mercurial.i18n import _
281 from mercurial.node import short
281 from mercurial.node import short
282 from mercurial import cmdutil, mail, templater, util
282 from mercurial import cmdutil, mail, templater, util
283 import re, time, urlparse, xmlrpclib
283 import re, time, urlparse, xmlrpclib
284
284
285 testedwith = 'internal'
285 testedwith = 'internal'
286
286
287 class bzaccess(object):
287 class bzaccess(object):
288 '''Base class for access to Bugzilla.'''
288 '''Base class for access to Bugzilla.'''
289
289
290 def __init__(self, ui):
290 def __init__(self, ui):
291 self.ui = ui
291 self.ui = ui
292 usermap = self.ui.config('bugzilla', 'usermap')
292 usermap = self.ui.config('bugzilla', 'usermap')
293 if usermap:
293 if usermap:
294 self.ui.readconfig(usermap, sections=['usermap'])
294 self.ui.readconfig(usermap, sections=['usermap'])
295
295
296 def map_committer(self, user):
296 def map_committer(self, user):
297 '''map name of committer to Bugzilla user name.'''
297 '''map name of committer to Bugzilla user name.'''
298 for committer, bzuser in self.ui.configitems('usermap'):
298 for committer, bzuser in self.ui.configitems('usermap'):
299 if committer.lower() == user.lower():
299 if committer.lower() == user.lower():
300 return bzuser
300 return bzuser
301 return user
301 return user
302
302
303 # Methods to be implemented by access classes.
303 # Methods to be implemented by access classes.
304 #
304 #
305 # 'bugs' is a dict keyed on bug id, where values are a dict holding
305 # 'bugs' is a dict keyed on bug id, where values are a dict holding
306 # updates to bug state. Recognised dict keys are:
306 # updates to bug state. Recognized dict keys are:
307 #
307 #
308 # 'hours': Value, float containing work hours to be updated.
308 # 'hours': Value, float containing work hours to be updated.
309 # 'fix': If key present, bug is to be marked fixed. Value ignored.
309 # 'fix': If key present, bug is to be marked fixed. Value ignored.
310
310
311 def filter_real_bug_ids(self, bugs):
311 def filter_real_bug_ids(self, bugs):
312 '''remove bug IDs that do not exist in Bugzilla from bugs.'''
312 '''remove bug IDs that do not exist in Bugzilla from bugs.'''
313 pass
313 pass
314
314
315 def filter_cset_known_bug_ids(self, node, bugs):
315 def filter_cset_known_bug_ids(self, node, bugs):
316 '''remove bug IDs where node occurs in comment text from bugs.'''
316 '''remove bug IDs where node occurs in comment text from bugs.'''
317 pass
317 pass
318
318
319 def updatebug(self, bugid, newstate, text, committer):
319 def updatebug(self, bugid, newstate, text, committer):
320 '''update the specified bug. Add comment text and set new states.
320 '''update the specified bug. Add comment text and set new states.
321
321
322 If possible add the comment as being from the committer of
322 If possible add the comment as being from the committer of
323 the changeset. Otherwise use the default Bugzilla user.
323 the changeset. Otherwise use the default Bugzilla user.
324 '''
324 '''
325 pass
325 pass
326
326
327 def notify(self, bugs, committer):
327 def notify(self, bugs, committer):
328 '''Force sending of Bugzilla notification emails.
328 '''Force sending of Bugzilla notification emails.
329
329
330 Only required if the access method does not trigger notification
330 Only required if the access method does not trigger notification
331 emails automatically.
331 emails automatically.
332 '''
332 '''
333 pass
333 pass
334
334
335 # Bugzilla via direct access to MySQL database.
335 # Bugzilla via direct access to MySQL database.
336 class bzmysql(bzaccess):
336 class bzmysql(bzaccess):
337 '''Support for direct MySQL access to Bugzilla.
337 '''Support for direct MySQL access to Bugzilla.
338
338
339 The earliest Bugzilla version this is tested with is version 2.16.
339 The earliest Bugzilla version this is tested with is version 2.16.
340
340
341 If your Bugzilla is version 3.4 or above, you are strongly
341 If your Bugzilla is version 3.4 or above, you are strongly
342 recommended to use the XMLRPC access method instead.
342 recommended to use the XMLRPC access method instead.
343 '''
343 '''
344
344
345 @staticmethod
345 @staticmethod
346 def sql_buglist(ids):
346 def sql_buglist(ids):
347 '''return SQL-friendly list of bug ids'''
347 '''return SQL-friendly list of bug ids'''
348 return '(' + ','.join(map(str, ids)) + ')'
348 return '(' + ','.join(map(str, ids)) + ')'
349
349
350 _MySQLdb = None
350 _MySQLdb = None
351
351
352 def __init__(self, ui):
352 def __init__(self, ui):
353 try:
353 try:
354 import MySQLdb as mysql
354 import MySQLdb as mysql
355 bzmysql._MySQLdb = mysql
355 bzmysql._MySQLdb = mysql
356 except ImportError, err:
356 except ImportError, err:
357 raise util.Abort(_('python mysql support not available: %s') % err)
357 raise util.Abort(_('python mysql support not available: %s') % err)
358
358
359 bzaccess.__init__(self, ui)
359 bzaccess.__init__(self, ui)
360
360
361 host = self.ui.config('bugzilla', 'host', 'localhost')
361 host = self.ui.config('bugzilla', 'host', 'localhost')
362 user = self.ui.config('bugzilla', 'user', 'bugs')
362 user = self.ui.config('bugzilla', 'user', 'bugs')
363 passwd = self.ui.config('bugzilla', 'password')
363 passwd = self.ui.config('bugzilla', 'password')
364 db = self.ui.config('bugzilla', 'db', 'bugs')
364 db = self.ui.config('bugzilla', 'db', 'bugs')
365 timeout = int(self.ui.config('bugzilla', 'timeout', 5))
365 timeout = int(self.ui.config('bugzilla', 'timeout', 5))
366 self.ui.note(_('connecting to %s:%s as %s, password %s\n') %
366 self.ui.note(_('connecting to %s:%s as %s, password %s\n') %
367 (host, db, user, '*' * len(passwd)))
367 (host, db, user, '*' * len(passwd)))
368 self.conn = bzmysql._MySQLdb.connect(host=host,
368 self.conn = bzmysql._MySQLdb.connect(host=host,
369 user=user, passwd=passwd,
369 user=user, passwd=passwd,
370 db=db,
370 db=db,
371 connect_timeout=timeout)
371 connect_timeout=timeout)
372 self.cursor = self.conn.cursor()
372 self.cursor = self.conn.cursor()
373 self.longdesc_id = self.get_longdesc_id()
373 self.longdesc_id = self.get_longdesc_id()
374 self.user_ids = {}
374 self.user_ids = {}
375 self.default_notify = "cd %(bzdir)s && ./processmail %(id)s %(user)s"
375 self.default_notify = "cd %(bzdir)s && ./processmail %(id)s %(user)s"
376
376
377 def run(self, *args, **kwargs):
377 def run(self, *args, **kwargs):
378 '''run a query.'''
378 '''run a query.'''
379 self.ui.note(_('query: %s %s\n') % (args, kwargs))
379 self.ui.note(_('query: %s %s\n') % (args, kwargs))
380 try:
380 try:
381 self.cursor.execute(*args, **kwargs)
381 self.cursor.execute(*args, **kwargs)
382 except bzmysql._MySQLdb.MySQLError:
382 except bzmysql._MySQLdb.MySQLError:
383 self.ui.note(_('failed query: %s %s\n') % (args, kwargs))
383 self.ui.note(_('failed query: %s %s\n') % (args, kwargs))
384 raise
384 raise
385
385
386 def get_longdesc_id(self):
386 def get_longdesc_id(self):
387 '''get identity of longdesc field'''
387 '''get identity of longdesc field'''
388 self.run('select fieldid from fielddefs where name = "longdesc"')
388 self.run('select fieldid from fielddefs where name = "longdesc"')
389 ids = self.cursor.fetchall()
389 ids = self.cursor.fetchall()
390 if len(ids) != 1:
390 if len(ids) != 1:
391 raise util.Abort(_('unknown database schema'))
391 raise util.Abort(_('unknown database schema'))
392 return ids[0][0]
392 return ids[0][0]
393
393
394 def filter_real_bug_ids(self, bugs):
394 def filter_real_bug_ids(self, bugs):
395 '''filter not-existing bugs from set.'''
395 '''filter not-existing bugs from set.'''
396 self.run('select bug_id from bugs where bug_id in %s' %
396 self.run('select bug_id from bugs where bug_id in %s' %
397 bzmysql.sql_buglist(bugs.keys()))
397 bzmysql.sql_buglist(bugs.keys()))
398 existing = [id for (id,) in self.cursor.fetchall()]
398 existing = [id for (id,) in self.cursor.fetchall()]
399 for id in bugs.keys():
399 for id in bugs.keys():
400 if id not in existing:
400 if id not in existing:
401 self.ui.status(_('bug %d does not exist\n') % id)
401 self.ui.status(_('bug %d does not exist\n') % id)
402 del bugs[id]
402 del bugs[id]
403
403
404 def filter_cset_known_bug_ids(self, node, bugs):
404 def filter_cset_known_bug_ids(self, node, bugs):
405 '''filter bug ids that already refer to this changeset from set.'''
405 '''filter bug ids that already refer to this changeset from set.'''
406 self.run('''select bug_id from longdescs where
406 self.run('''select bug_id from longdescs where
407 bug_id in %s and thetext like "%%%s%%"''' %
407 bug_id in %s and thetext like "%%%s%%"''' %
408 (bzmysql.sql_buglist(bugs.keys()), short(node)))
408 (bzmysql.sql_buglist(bugs.keys()), short(node)))
409 for (id,) in self.cursor.fetchall():
409 for (id,) in self.cursor.fetchall():
410 self.ui.status(_('bug %d already knows about changeset %s\n') %
410 self.ui.status(_('bug %d already knows about changeset %s\n') %
411 (id, short(node)))
411 (id, short(node)))
412 del bugs[id]
412 del bugs[id]
413
413
414 def notify(self, bugs, committer):
414 def notify(self, bugs, committer):
415 '''tell bugzilla to send mail.'''
415 '''tell bugzilla to send mail.'''
416 self.ui.status(_('telling bugzilla to send mail:\n'))
416 self.ui.status(_('telling bugzilla to send mail:\n'))
417 (user, userid) = self.get_bugzilla_user(committer)
417 (user, userid) = self.get_bugzilla_user(committer)
418 for id in bugs.keys():
418 for id in bugs.keys():
419 self.ui.status(_(' bug %s\n') % id)
419 self.ui.status(_(' bug %s\n') % id)
420 cmdfmt = self.ui.config('bugzilla', 'notify', self.default_notify)
420 cmdfmt = self.ui.config('bugzilla', 'notify', self.default_notify)
421 bzdir = self.ui.config('bugzilla', 'bzdir',
421 bzdir = self.ui.config('bugzilla', 'bzdir',
422 '/var/www/html/bugzilla')
422 '/var/www/html/bugzilla')
423 try:
423 try:
424 # Backwards-compatible with old notify string, which
424 # Backwards-compatible with old notify string, which
425 # took one string. This will throw with a new format
425 # took one string. This will throw with a new format
426 # string.
426 # string.
427 cmd = cmdfmt % id
427 cmd = cmdfmt % id
428 except TypeError:
428 except TypeError:
429 cmd = cmdfmt % {'bzdir': bzdir, 'id': id, 'user': user}
429 cmd = cmdfmt % {'bzdir': bzdir, 'id': id, 'user': user}
430 self.ui.note(_('running notify command %s\n') % cmd)
430 self.ui.note(_('running notify command %s\n') % cmd)
431 fp = util.popen('(%s) 2>&1' % cmd)
431 fp = util.popen('(%s) 2>&1' % cmd)
432 out = fp.read()
432 out = fp.read()
433 ret = fp.close()
433 ret = fp.close()
434 if ret:
434 if ret:
435 self.ui.warn(out)
435 self.ui.warn(out)
436 raise util.Abort(_('bugzilla notify command %s') %
436 raise util.Abort(_('bugzilla notify command %s') %
437 util.explainexit(ret)[0])
437 util.explainexit(ret)[0])
438 self.ui.status(_('done\n'))
438 self.ui.status(_('done\n'))
439
439
440 def get_user_id(self, user):
440 def get_user_id(self, user):
441 '''look up numeric bugzilla user id.'''
441 '''look up numeric bugzilla user id.'''
442 try:
442 try:
443 return self.user_ids[user]
443 return self.user_ids[user]
444 except KeyError:
444 except KeyError:
445 try:
445 try:
446 userid = int(user)
446 userid = int(user)
447 except ValueError:
447 except ValueError:
448 self.ui.note(_('looking up user %s\n') % user)
448 self.ui.note(_('looking up user %s\n') % user)
449 self.run('''select userid from profiles
449 self.run('''select userid from profiles
450 where login_name like %s''', user)
450 where login_name like %s''', user)
451 all = self.cursor.fetchall()
451 all = self.cursor.fetchall()
452 if len(all) != 1:
452 if len(all) != 1:
453 raise KeyError(user)
453 raise KeyError(user)
454 userid = int(all[0][0])
454 userid = int(all[0][0])
455 self.user_ids[user] = userid
455 self.user_ids[user] = userid
456 return userid
456 return userid
457
457
458 def get_bugzilla_user(self, committer):
458 def get_bugzilla_user(self, committer):
459 '''See if committer is a registered bugzilla user. Return
459 '''See if committer is a registered bugzilla user. Return
460 bugzilla username and userid if so. If not, return default
460 bugzilla username and userid if so. If not, return default
461 bugzilla username and userid.'''
461 bugzilla username and userid.'''
462 user = self.map_committer(committer)
462 user = self.map_committer(committer)
463 try:
463 try:
464 userid = self.get_user_id(user)
464 userid = self.get_user_id(user)
465 except KeyError:
465 except KeyError:
466 try:
466 try:
467 defaultuser = self.ui.config('bugzilla', 'bzuser')
467 defaultuser = self.ui.config('bugzilla', 'bzuser')
468 if not defaultuser:
468 if not defaultuser:
469 raise util.Abort(_('cannot find bugzilla user id for %s') %
469 raise util.Abort(_('cannot find bugzilla user id for %s') %
470 user)
470 user)
471 userid = self.get_user_id(defaultuser)
471 userid = self.get_user_id(defaultuser)
472 user = defaultuser
472 user = defaultuser
473 except KeyError:
473 except KeyError:
474 raise util.Abort(_('cannot find bugzilla user id for %s or %s')
474 raise util.Abort(_('cannot find bugzilla user id for %s or %s')
475 % (user, defaultuser))
475 % (user, defaultuser))
476 return (user, userid)
476 return (user, userid)
477
477
478 def updatebug(self, bugid, newstate, text, committer):
478 def updatebug(self, bugid, newstate, text, committer):
479 '''update bug state with comment text.
479 '''update bug state with comment text.
480
480
481 Try adding comment as committer of changeset, otherwise as
481 Try adding comment as committer of changeset, otherwise as
482 default bugzilla user.'''
482 default bugzilla user.'''
483 if len(newstate) > 0:
483 if len(newstate) > 0:
484 self.ui.warn(_("Bugzilla/MySQL cannot update bug state\n"))
484 self.ui.warn(_("Bugzilla/MySQL cannot update bug state\n"))
485
485
486 (user, userid) = self.get_bugzilla_user(committer)
486 (user, userid) = self.get_bugzilla_user(committer)
487 now = time.strftime('%Y-%m-%d %H:%M:%S')
487 now = time.strftime('%Y-%m-%d %H:%M:%S')
488 self.run('''insert into longdescs
488 self.run('''insert into longdescs
489 (bug_id, who, bug_when, thetext)
489 (bug_id, who, bug_when, thetext)
490 values (%s, %s, %s, %s)''',
490 values (%s, %s, %s, %s)''',
491 (bugid, userid, now, text))
491 (bugid, userid, now, text))
492 self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid)
492 self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid)
493 values (%s, %s, %s, %s)''',
493 values (%s, %s, %s, %s)''',
494 (bugid, userid, now, self.longdesc_id))
494 (bugid, userid, now, self.longdesc_id))
495 self.conn.commit()
495 self.conn.commit()
496
496
497 class bzmysql_2_18(bzmysql):
497 class bzmysql_2_18(bzmysql):
498 '''support for bugzilla 2.18 series.'''
498 '''support for bugzilla 2.18 series.'''
499
499
500 def __init__(self, ui):
500 def __init__(self, ui):
501 bzmysql.__init__(self, ui)
501 bzmysql.__init__(self, ui)
502 self.default_notify = \
502 self.default_notify = \
503 "cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s"
503 "cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s"
504
504
505 class bzmysql_3_0(bzmysql_2_18):
505 class bzmysql_3_0(bzmysql_2_18):
506 '''support for bugzilla 3.0 series.'''
506 '''support for bugzilla 3.0 series.'''
507
507
508 def __init__(self, ui):
508 def __init__(self, ui):
509 bzmysql_2_18.__init__(self, ui)
509 bzmysql_2_18.__init__(self, ui)
510
510
511 def get_longdesc_id(self):
511 def get_longdesc_id(self):
512 '''get identity of longdesc field'''
512 '''get identity of longdesc field'''
513 self.run('select id from fielddefs where name = "longdesc"')
513 self.run('select id from fielddefs where name = "longdesc"')
514 ids = self.cursor.fetchall()
514 ids = self.cursor.fetchall()
515 if len(ids) != 1:
515 if len(ids) != 1:
516 raise util.Abort(_('unknown database schema'))
516 raise util.Abort(_('unknown database schema'))
517 return ids[0][0]
517 return ids[0][0]
518
518
519 # Bugzilla via XMLRPC interface.
519 # Bugzilla via XMLRPC interface.
520
520
521 class cookietransportrequest(object):
521 class cookietransportrequest(object):
522 """A Transport request method that retains cookies over its lifetime.
522 """A Transport request method that retains cookies over its lifetime.
523
523
524 The regular xmlrpclib transports ignore cookies. Which causes
524 The regular xmlrpclib transports ignore cookies. Which causes
525 a bit of a problem when you need a cookie-based login, as with
525 a bit of a problem when you need a cookie-based login, as with
526 the Bugzilla XMLRPC interface.
526 the Bugzilla XMLRPC interface.
527
527
528 So this is a helper for defining a Transport which looks for
528 So this is a helper for defining a Transport which looks for
529 cookies being set in responses and saves them to add to all future
529 cookies being set in responses and saves them to add to all future
530 requests.
530 requests.
531 """
531 """
532
532
533 # Inspiration drawn from
533 # Inspiration drawn from
534 # http://blog.godson.in/2010/09/how-to-make-python-xmlrpclib-client.html
534 # http://blog.godson.in/2010/09/how-to-make-python-xmlrpclib-client.html
535 # http://www.itkovian.net/base/transport-class-for-pythons-xml-rpc-lib/
535 # http://www.itkovian.net/base/transport-class-for-pythons-xml-rpc-lib/
536
536
537 cookies = []
537 cookies = []
538 def send_cookies(self, connection):
538 def send_cookies(self, connection):
539 if self.cookies:
539 if self.cookies:
540 for cookie in self.cookies:
540 for cookie in self.cookies:
541 connection.putheader("Cookie", cookie)
541 connection.putheader("Cookie", cookie)
542
542
543 def request(self, host, handler, request_body, verbose=0):
543 def request(self, host, handler, request_body, verbose=0):
544 self.verbose = verbose
544 self.verbose = verbose
545 self.accept_gzip_encoding = False
545 self.accept_gzip_encoding = False
546
546
547 # issue XML-RPC request
547 # issue XML-RPC request
548 h = self.make_connection(host)
548 h = self.make_connection(host)
549 if verbose:
549 if verbose:
550 h.set_debuglevel(1)
550 h.set_debuglevel(1)
551
551
552 self.send_request(h, handler, request_body)
552 self.send_request(h, handler, request_body)
553 self.send_host(h, host)
553 self.send_host(h, host)
554 self.send_cookies(h)
554 self.send_cookies(h)
555 self.send_user_agent(h)
555 self.send_user_agent(h)
556 self.send_content(h, request_body)
556 self.send_content(h, request_body)
557
557
558 # Deal with differences between Python 2.4-2.6 and 2.7.
558 # Deal with differences between Python 2.4-2.6 and 2.7.
559 # In the former h is a HTTP(S). In the latter it's a
559 # In the former h is a HTTP(S). In the latter it's a
560 # HTTP(S)Connection. Luckily, the 2.4-2.6 implementation of
560 # HTTP(S)Connection. Luckily, the 2.4-2.6 implementation of
561 # HTTP(S) has an underlying HTTP(S)Connection, so extract
561 # HTTP(S) has an underlying HTTP(S)Connection, so extract
562 # that and use it.
562 # that and use it.
563 try:
563 try:
564 response = h.getresponse()
564 response = h.getresponse()
565 except AttributeError:
565 except AttributeError:
566 response = h._conn.getresponse()
566 response = h._conn.getresponse()
567
567
568 # Add any cookie definitions to our list.
568 # Add any cookie definitions to our list.
569 for header in response.msg.getallmatchingheaders("Set-Cookie"):
569 for header in response.msg.getallmatchingheaders("Set-Cookie"):
570 val = header.split(": ", 1)[1]
570 val = header.split(": ", 1)[1]
571 cookie = val.split(";", 1)[0]
571 cookie = val.split(";", 1)[0]
572 self.cookies.append(cookie)
572 self.cookies.append(cookie)
573
573
574 if response.status != 200:
574 if response.status != 200:
575 raise xmlrpclib.ProtocolError(host + handler, response.status,
575 raise xmlrpclib.ProtocolError(host + handler, response.status,
576 response.reason, response.msg.headers)
576 response.reason, response.msg.headers)
577
577
578 payload = response.read()
578 payload = response.read()
579 parser, unmarshaller = self.getparser()
579 parser, unmarshaller = self.getparser()
580 parser.feed(payload)
580 parser.feed(payload)
581 parser.close()
581 parser.close()
582
582
583 return unmarshaller.close()
583 return unmarshaller.close()
584
584
585 # The explicit calls to the underlying xmlrpclib __init__() methods are
585 # The explicit calls to the underlying xmlrpclib __init__() methods are
586 # necessary. The xmlrpclib.Transport classes are old-style classes, and
586 # necessary. The xmlrpclib.Transport classes are old-style classes, and
587 # it turns out their __init__() doesn't get called when doing multiple
587 # it turns out their __init__() doesn't get called when doing multiple
588 # inheritance with a new-style class.
588 # inheritance with a new-style class.
589 class cookietransport(cookietransportrequest, xmlrpclib.Transport):
589 class cookietransport(cookietransportrequest, xmlrpclib.Transport):
590 def __init__(self, use_datetime=0):
590 def __init__(self, use_datetime=0):
591 if util.safehasattr(xmlrpclib.Transport, "__init__"):
591 if util.safehasattr(xmlrpclib.Transport, "__init__"):
592 xmlrpclib.Transport.__init__(self, use_datetime)
592 xmlrpclib.Transport.__init__(self, use_datetime)
593
593
594 class cookiesafetransport(cookietransportrequest, xmlrpclib.SafeTransport):
594 class cookiesafetransport(cookietransportrequest, xmlrpclib.SafeTransport):
595 def __init__(self, use_datetime=0):
595 def __init__(self, use_datetime=0):
596 if util.safehasattr(xmlrpclib.Transport, "__init__"):
596 if util.safehasattr(xmlrpclib.Transport, "__init__"):
597 xmlrpclib.SafeTransport.__init__(self, use_datetime)
597 xmlrpclib.SafeTransport.__init__(self, use_datetime)
598
598
599 class bzxmlrpc(bzaccess):
599 class bzxmlrpc(bzaccess):
600 """Support for access to Bugzilla via the Bugzilla XMLRPC API.
600 """Support for access to Bugzilla via the Bugzilla XMLRPC API.
601
601
602 Requires a minimum Bugzilla version 3.4.
602 Requires a minimum Bugzilla version 3.4.
603 """
603 """
604
604
605 def __init__(self, ui):
605 def __init__(self, ui):
606 bzaccess.__init__(self, ui)
606 bzaccess.__init__(self, ui)
607
607
608 bzweb = self.ui.config('bugzilla', 'bzurl',
608 bzweb = self.ui.config('bugzilla', 'bzurl',
609 'http://localhost/bugzilla/')
609 'http://localhost/bugzilla/')
610 bzweb = bzweb.rstrip("/") + "/xmlrpc.cgi"
610 bzweb = bzweb.rstrip("/") + "/xmlrpc.cgi"
611
611
612 user = self.ui.config('bugzilla', 'user', 'bugs')
612 user = self.ui.config('bugzilla', 'user', 'bugs')
613 passwd = self.ui.config('bugzilla', 'password')
613 passwd = self.ui.config('bugzilla', 'password')
614
614
615 self.fixstatus = self.ui.config('bugzilla', 'fixstatus', 'RESOLVED')
615 self.fixstatus = self.ui.config('bugzilla', 'fixstatus', 'RESOLVED')
616 self.fixresolution = self.ui.config('bugzilla', 'fixresolution',
616 self.fixresolution = self.ui.config('bugzilla', 'fixresolution',
617 'FIXED')
617 'FIXED')
618
618
619 self.bzproxy = xmlrpclib.ServerProxy(bzweb, self.transport(bzweb))
619 self.bzproxy = xmlrpclib.ServerProxy(bzweb, self.transport(bzweb))
620 ver = self.bzproxy.Bugzilla.version()['version'].split('.')
620 ver = self.bzproxy.Bugzilla.version()['version'].split('.')
621 self.bzvermajor = int(ver[0])
621 self.bzvermajor = int(ver[0])
622 self.bzverminor = int(ver[1])
622 self.bzverminor = int(ver[1])
623 self.bzproxy.User.login(dict(login=user, password=passwd))
623 self.bzproxy.User.login(dict(login=user, password=passwd))
624
624
625 def transport(self, uri):
625 def transport(self, uri):
626 if urlparse.urlparse(uri, "http")[0] == "https":
626 if urlparse.urlparse(uri, "http")[0] == "https":
627 return cookiesafetransport()
627 return cookiesafetransport()
628 else:
628 else:
629 return cookietransport()
629 return cookietransport()
630
630
631 def get_bug_comments(self, id):
631 def get_bug_comments(self, id):
632 """Return a string with all comment text for a bug."""
632 """Return a string with all comment text for a bug."""
633 c = self.bzproxy.Bug.comments(dict(ids=[id], include_fields=['text']))
633 c = self.bzproxy.Bug.comments(dict(ids=[id], include_fields=['text']))
634 return ''.join([t['text'] for t in c['bugs'][str(id)]['comments']])
634 return ''.join([t['text'] for t in c['bugs'][str(id)]['comments']])
635
635
636 def filter_real_bug_ids(self, bugs):
636 def filter_real_bug_ids(self, bugs):
637 probe = self.bzproxy.Bug.get(dict(ids=sorted(bugs.keys()),
637 probe = self.bzproxy.Bug.get(dict(ids=sorted(bugs.keys()),
638 include_fields=[],
638 include_fields=[],
639 permissive=True))
639 permissive=True))
640 for badbug in probe['faults']:
640 for badbug in probe['faults']:
641 id = badbug['id']
641 id = badbug['id']
642 self.ui.status(_('bug %d does not exist\n') % id)
642 self.ui.status(_('bug %d does not exist\n') % id)
643 del bugs[id]
643 del bugs[id]
644
644
645 def filter_cset_known_bug_ids(self, node, bugs):
645 def filter_cset_known_bug_ids(self, node, bugs):
646 for id in sorted(bugs.keys()):
646 for id in sorted(bugs.keys()):
647 if self.get_bug_comments(id).find(short(node)) != -1:
647 if self.get_bug_comments(id).find(short(node)) != -1:
648 self.ui.status(_('bug %d already knows about changeset %s\n') %
648 self.ui.status(_('bug %d already knows about changeset %s\n') %
649 (id, short(node)))
649 (id, short(node)))
650 del bugs[id]
650 del bugs[id]
651
651
652 def updatebug(self, bugid, newstate, text, committer):
652 def updatebug(self, bugid, newstate, text, committer):
653 args = {}
653 args = {}
654 if 'hours' in newstate:
654 if 'hours' in newstate:
655 args['work_time'] = newstate['hours']
655 args['work_time'] = newstate['hours']
656
656
657 if self.bzvermajor >= 4:
657 if self.bzvermajor >= 4:
658 args['ids'] = [bugid]
658 args['ids'] = [bugid]
659 args['comment'] = {'body' : text}
659 args['comment'] = {'body' : text}
660 if 'fix' in newstate:
660 if 'fix' in newstate:
661 args['status'] = self.fixstatus
661 args['status'] = self.fixstatus
662 args['resolution'] = self.fixresolution
662 args['resolution'] = self.fixresolution
663 self.bzproxy.Bug.update(args)
663 self.bzproxy.Bug.update(args)
664 else:
664 else:
665 if 'fix' in newstate:
665 if 'fix' in newstate:
666 self.ui.warn(_("Bugzilla/XMLRPC needs Bugzilla 4.0 or later "
666 self.ui.warn(_("Bugzilla/XMLRPC needs Bugzilla 4.0 or later "
667 "to mark bugs fixed\n"))
667 "to mark bugs fixed\n"))
668 args['id'] = bugid
668 args['id'] = bugid
669 args['comment'] = text
669 args['comment'] = text
670 self.bzproxy.Bug.add_comment(args)
670 self.bzproxy.Bug.add_comment(args)
671
671
672 class bzxmlrpcemail(bzxmlrpc):
672 class bzxmlrpcemail(bzxmlrpc):
673 """Read data from Bugzilla via XMLRPC, send updates via email.
673 """Read data from Bugzilla via XMLRPC, send updates via email.
674
674
675 Advantages of sending updates via email:
675 Advantages of sending updates via email:
676 1. Comments can be added as any user, not just logged in user.
676 1. Comments can be added as any user, not just logged in user.
677 2. Bug statuses or other fields not accessible via XMLRPC can
677 2. Bug statuses or other fields not accessible via XMLRPC can
678 potentially be updated.
678 potentially be updated.
679
679
680 There is no XMLRPC function to change bug status before Bugzilla
680 There is no XMLRPC function to change bug status before Bugzilla
681 4.0, so bugs cannot be marked fixed via XMLRPC before Bugzilla 4.0.
681 4.0, so bugs cannot be marked fixed via XMLRPC before Bugzilla 4.0.
682 But bugs can be marked fixed via email from 3.4 onwards.
682 But bugs can be marked fixed via email from 3.4 onwards.
683 """
683 """
684
684
685 # The email interface changes subtly between 3.4 and 3.6. In 3.4,
685 # The email interface changes subtly between 3.4 and 3.6. In 3.4,
686 # in-email fields are specified as '@<fieldname> = <value>'. In
686 # in-email fields are specified as '@<fieldname> = <value>'. In
687 # 3.6 this becomes '@<fieldname> <value>'. And fieldname @bug_id
687 # 3.6 this becomes '@<fieldname> <value>'. And fieldname @bug_id
688 # in 3.4 becomes @id in 3.6. 3.6 and 4.0 both maintain backwards
688 # in 3.4 becomes @id in 3.6. 3.6 and 4.0 both maintain backwards
689 # compatibility, but rather than rely on this use the new format for
689 # compatibility, but rather than rely on this use the new format for
690 # 4.0 onwards.
690 # 4.0 onwards.
691
691
692 def __init__(self, ui):
692 def __init__(self, ui):
693 bzxmlrpc.__init__(self, ui)
693 bzxmlrpc.__init__(self, ui)
694
694
695 self.bzemail = self.ui.config('bugzilla', 'bzemail')
695 self.bzemail = self.ui.config('bugzilla', 'bzemail')
696 if not self.bzemail:
696 if not self.bzemail:
697 raise util.Abort(_("configuration 'bzemail' missing"))
697 raise util.Abort(_("configuration 'bzemail' missing"))
698 mail.validateconfig(self.ui)
698 mail.validateconfig(self.ui)
699
699
700 def makecommandline(self, fieldname, value):
700 def makecommandline(self, fieldname, value):
701 if self.bzvermajor >= 4:
701 if self.bzvermajor >= 4:
702 return "@%s %s" % (fieldname, str(value))
702 return "@%s %s" % (fieldname, str(value))
703 else:
703 else:
704 if fieldname == "id":
704 if fieldname == "id":
705 fieldname = "bug_id"
705 fieldname = "bug_id"
706 return "@%s = %s" % (fieldname, str(value))
706 return "@%s = %s" % (fieldname, str(value))
707
707
708 def send_bug_modify_email(self, bugid, commands, comment, committer):
708 def send_bug_modify_email(self, bugid, commands, comment, committer):
709 '''send modification message to Bugzilla bug via email.
709 '''send modification message to Bugzilla bug via email.
710
710
711 The message format is documented in the Bugzilla email_in.pl
711 The message format is documented in the Bugzilla email_in.pl
712 specification. commands is a list of command lines, comment is the
712 specification. commands is a list of command lines, comment is the
713 comment text.
713 comment text.
714
714
715 To stop users from crafting commit comments with
715 To stop users from crafting commit comments with
716 Bugzilla commands, specify the bug ID via the message body, rather
716 Bugzilla commands, specify the bug ID via the message body, rather
717 than the subject line, and leave a blank line after it.
717 than the subject line, and leave a blank line after it.
718 '''
718 '''
719 user = self.map_committer(committer)
719 user = self.map_committer(committer)
720 matches = self.bzproxy.User.get(dict(match=[user]))
720 matches = self.bzproxy.User.get(dict(match=[user]))
721 if not matches['users']:
721 if not matches['users']:
722 user = self.ui.config('bugzilla', 'user', 'bugs')
722 user = self.ui.config('bugzilla', 'user', 'bugs')
723 matches = self.bzproxy.User.get(dict(match=[user]))
723 matches = self.bzproxy.User.get(dict(match=[user]))
724 if not matches['users']:
724 if not matches['users']:
725 raise util.Abort(_("default bugzilla user %s email not found") %
725 raise util.Abort(_("default bugzilla user %s email not found") %
726 user)
726 user)
727 user = matches['users'][0]['email']
727 user = matches['users'][0]['email']
728 commands.append(self.makecommandline("id", bugid))
728 commands.append(self.makecommandline("id", bugid))
729
729
730 text = "\n".join(commands) + "\n\n" + comment
730 text = "\n".join(commands) + "\n\n" + comment
731
731
732 _charsets = mail._charsets(self.ui)
732 _charsets = mail._charsets(self.ui)
733 user = mail.addressencode(self.ui, user, _charsets)
733 user = mail.addressencode(self.ui, user, _charsets)
734 bzemail = mail.addressencode(self.ui, self.bzemail, _charsets)
734 bzemail = mail.addressencode(self.ui, self.bzemail, _charsets)
735 msg = mail.mimeencode(self.ui, text, _charsets)
735 msg = mail.mimeencode(self.ui, text, _charsets)
736 msg['From'] = user
736 msg['From'] = user
737 msg['To'] = bzemail
737 msg['To'] = bzemail
738 msg['Subject'] = mail.headencode(self.ui, "Bug modification", _charsets)
738 msg['Subject'] = mail.headencode(self.ui, "Bug modification", _charsets)
739 sendmail = mail.connect(self.ui)
739 sendmail = mail.connect(self.ui)
740 sendmail(user, bzemail, msg.as_string())
740 sendmail(user, bzemail, msg.as_string())
741
741
742 def updatebug(self, bugid, newstate, text, committer):
742 def updatebug(self, bugid, newstate, text, committer):
743 cmds = []
743 cmds = []
744 if 'hours' in newstate:
744 if 'hours' in newstate:
745 cmds.append(self.makecommandline("work_time", newstate['hours']))
745 cmds.append(self.makecommandline("work_time", newstate['hours']))
746 if 'fix' in newstate:
746 if 'fix' in newstate:
747 cmds.append(self.makecommandline("bug_status", self.fixstatus))
747 cmds.append(self.makecommandline("bug_status", self.fixstatus))
748 cmds.append(self.makecommandline("resolution", self.fixresolution))
748 cmds.append(self.makecommandline("resolution", self.fixresolution))
749 self.send_bug_modify_email(bugid, cmds, text, committer)
749 self.send_bug_modify_email(bugid, cmds, text, committer)
750
750
751 class bugzilla(object):
751 class bugzilla(object):
752 # supported versions of bugzilla. different versions have
752 # supported versions of bugzilla. different versions have
753 # different schemas.
753 # different schemas.
754 _versions = {
754 _versions = {
755 '2.16': bzmysql,
755 '2.16': bzmysql,
756 '2.18': bzmysql_2_18,
756 '2.18': bzmysql_2_18,
757 '3.0': bzmysql_3_0,
757 '3.0': bzmysql_3_0,
758 'xmlrpc': bzxmlrpc,
758 'xmlrpc': bzxmlrpc,
759 'xmlrpc+email': bzxmlrpcemail
759 'xmlrpc+email': bzxmlrpcemail
760 }
760 }
761
761
762 _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
762 _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
763 r'(?P<ids>(?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)'
763 r'(?P<ids>(?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)'
764 r'\.?\s*(?:h(?:ours?)?\s*(?P<hours>\d*(?:\.\d+)?))?')
764 r'\.?\s*(?:h(?:ours?)?\s*(?P<hours>\d*(?:\.\d+)?))?')
765
765
766 _default_fix_re = (r'fix(?:es)?\s*(?:bugs?\s*)?,?\s*'
766 _default_fix_re = (r'fix(?:es)?\s*(?:bugs?\s*)?,?\s*'
767 r'(?:nos?\.?|num(?:ber)?s?)?\s*'
767 r'(?:nos?\.?|num(?:ber)?s?)?\s*'
768 r'(?P<ids>(?:#?\d+\s*(?:,?\s*(?:and)?)?\s*)+)'
768 r'(?P<ids>(?:#?\d+\s*(?:,?\s*(?:and)?)?\s*)+)'
769 r'\.?\s*(?:h(?:ours?)?\s*(?P<hours>\d*(?:\.\d+)?))?')
769 r'\.?\s*(?:h(?:ours?)?\s*(?P<hours>\d*(?:\.\d+)?))?')
770
770
771 _bz = None
771 _bz = None
772
772
773 def __init__(self, ui, repo):
773 def __init__(self, ui, repo):
774 self.ui = ui
774 self.ui = ui
775 self.repo = repo
775 self.repo = repo
776
776
777 def bz(self):
777 def bz(self):
778 '''return object that knows how to talk to bugzilla version in
778 '''return object that knows how to talk to bugzilla version in
779 use.'''
779 use.'''
780
780
781 if bugzilla._bz is None:
781 if bugzilla._bz is None:
782 bzversion = self.ui.config('bugzilla', 'version')
782 bzversion = self.ui.config('bugzilla', 'version')
783 try:
783 try:
784 bzclass = bugzilla._versions[bzversion]
784 bzclass = bugzilla._versions[bzversion]
785 except KeyError:
785 except KeyError:
786 raise util.Abort(_('bugzilla version %s not supported') %
786 raise util.Abort(_('bugzilla version %s not supported') %
787 bzversion)
787 bzversion)
788 bugzilla._bz = bzclass(self.ui)
788 bugzilla._bz = bzclass(self.ui)
789 return bugzilla._bz
789 return bugzilla._bz
790
790
791 def __getattr__(self, key):
791 def __getattr__(self, key):
792 return getattr(self.bz(), key)
792 return getattr(self.bz(), key)
793
793
794 _bug_re = None
794 _bug_re = None
795 _fix_re = None
795 _fix_re = None
796 _split_re = None
796 _split_re = None
797
797
798 def find_bugs(self, ctx):
798 def find_bugs(self, ctx):
799 '''return bugs dictionary created from commit comment.
799 '''return bugs dictionary created from commit comment.
800
800
801 Extract bug info from changeset comments. Filter out any that are
801 Extract bug info from changeset comments. Filter out any that are
802 not known to Bugzilla, and any that already have a reference to
802 not known to Bugzilla, and any that already have a reference to
803 the given changeset in their comments.
803 the given changeset in their comments.
804 '''
804 '''
805 if bugzilla._bug_re is None:
805 if bugzilla._bug_re is None:
806 bugzilla._bug_re = re.compile(
806 bugzilla._bug_re = re.compile(
807 self.ui.config('bugzilla', 'regexp',
807 self.ui.config('bugzilla', 'regexp',
808 bugzilla._default_bug_re), re.IGNORECASE)
808 bugzilla._default_bug_re), re.IGNORECASE)
809 bugzilla._fix_re = re.compile(
809 bugzilla._fix_re = re.compile(
810 self.ui.config('bugzilla', 'fixregexp',
810 self.ui.config('bugzilla', 'fixregexp',
811 bugzilla._default_fix_re), re.IGNORECASE)
811 bugzilla._default_fix_re), re.IGNORECASE)
812 bugzilla._split_re = re.compile(r'\D+')
812 bugzilla._split_re = re.compile(r'\D+')
813 start = 0
813 start = 0
814 hours = 0.0
814 hours = 0.0
815 bugs = {}
815 bugs = {}
816 bugmatch = bugzilla._bug_re.search(ctx.description(), start)
816 bugmatch = bugzilla._bug_re.search(ctx.description(), start)
817 fixmatch = bugzilla._fix_re.search(ctx.description(), start)
817 fixmatch = bugzilla._fix_re.search(ctx.description(), start)
818 while True:
818 while True:
819 bugattribs = {}
819 bugattribs = {}
820 if not bugmatch and not fixmatch:
820 if not bugmatch and not fixmatch:
821 break
821 break
822 if not bugmatch:
822 if not bugmatch:
823 m = fixmatch
823 m = fixmatch
824 elif not fixmatch:
824 elif not fixmatch:
825 m = bugmatch
825 m = bugmatch
826 else:
826 else:
827 if bugmatch.start() < fixmatch.start():
827 if bugmatch.start() < fixmatch.start():
828 m = bugmatch
828 m = bugmatch
829 else:
829 else:
830 m = fixmatch
830 m = fixmatch
831 start = m.end()
831 start = m.end()
832 if m is bugmatch:
832 if m is bugmatch:
833 bugmatch = bugzilla._bug_re.search(ctx.description(), start)
833 bugmatch = bugzilla._bug_re.search(ctx.description(), start)
834 if 'fix' in bugattribs:
834 if 'fix' in bugattribs:
835 del bugattribs['fix']
835 del bugattribs['fix']
836 else:
836 else:
837 fixmatch = bugzilla._fix_re.search(ctx.description(), start)
837 fixmatch = bugzilla._fix_re.search(ctx.description(), start)
838 bugattribs['fix'] = None
838 bugattribs['fix'] = None
839
839
840 try:
840 try:
841 ids = m.group('ids')
841 ids = m.group('ids')
842 except IndexError:
842 except IndexError:
843 ids = m.group(1)
843 ids = m.group(1)
844 try:
844 try:
845 hours = float(m.group('hours'))
845 hours = float(m.group('hours'))
846 bugattribs['hours'] = hours
846 bugattribs['hours'] = hours
847 except IndexError:
847 except IndexError:
848 pass
848 pass
849 except TypeError:
849 except TypeError:
850 pass
850 pass
851 except ValueError:
851 except ValueError:
852 self.ui.status(_("%s: invalid hours\n") % m.group('hours'))
852 self.ui.status(_("%s: invalid hours\n") % m.group('hours'))
853
853
854 for id in bugzilla._split_re.split(ids):
854 for id in bugzilla._split_re.split(ids):
855 if not id:
855 if not id:
856 continue
856 continue
857 bugs[int(id)] = bugattribs
857 bugs[int(id)] = bugattribs
858 if bugs:
858 if bugs:
859 self.filter_real_bug_ids(bugs)
859 self.filter_real_bug_ids(bugs)
860 if bugs:
860 if bugs:
861 self.filter_cset_known_bug_ids(ctx.node(), bugs)
861 self.filter_cset_known_bug_ids(ctx.node(), bugs)
862 return bugs
862 return bugs
863
863
864 def update(self, bugid, newstate, ctx):
864 def update(self, bugid, newstate, ctx):
865 '''update bugzilla bug with reference to changeset.'''
865 '''update bugzilla bug with reference to changeset.'''
866
866
867 def webroot(root):
867 def webroot(root):
868 '''strip leading prefix of repo root and turn into
868 '''strip leading prefix of repo root and turn into
869 url-safe path.'''
869 url-safe path.'''
870 count = int(self.ui.config('bugzilla', 'strip', 0))
870 count = int(self.ui.config('bugzilla', 'strip', 0))
871 root = util.pconvert(root)
871 root = util.pconvert(root)
872 while count > 0:
872 while count > 0:
873 c = root.find('/')
873 c = root.find('/')
874 if c == -1:
874 if c == -1:
875 break
875 break
876 root = root[c + 1:]
876 root = root[c + 1:]
877 count -= 1
877 count -= 1
878 return root
878 return root
879
879
880 mapfile = self.ui.config('bugzilla', 'style')
880 mapfile = self.ui.config('bugzilla', 'style')
881 tmpl = self.ui.config('bugzilla', 'template')
881 tmpl = self.ui.config('bugzilla', 'template')
882 t = cmdutil.changeset_templater(self.ui, self.repo,
882 t = cmdutil.changeset_templater(self.ui, self.repo,
883 False, None, mapfile, False)
883 False, None, mapfile, False)
884 if not mapfile and not tmpl:
884 if not mapfile and not tmpl:
885 tmpl = _('changeset {node|short} in repo {root} refers '
885 tmpl = _('changeset {node|short} in repo {root} refers '
886 'to bug {bug}.\ndetails:\n\t{desc|tabindent}')
886 'to bug {bug}.\ndetails:\n\t{desc|tabindent}')
887 if tmpl:
887 if tmpl:
888 tmpl = templater.parsestring(tmpl, quoted=False)
888 tmpl = templater.parsestring(tmpl, quoted=False)
889 t.use_template(tmpl)
889 t.use_template(tmpl)
890 self.ui.pushbuffer()
890 self.ui.pushbuffer()
891 t.show(ctx, changes=ctx.changeset(),
891 t.show(ctx, changes=ctx.changeset(),
892 bug=str(bugid),
892 bug=str(bugid),
893 hgweb=self.ui.config('web', 'baseurl'),
893 hgweb=self.ui.config('web', 'baseurl'),
894 root=self.repo.root,
894 root=self.repo.root,
895 webroot=webroot(self.repo.root))
895 webroot=webroot(self.repo.root))
896 data = self.ui.popbuffer()
896 data = self.ui.popbuffer()
897 self.updatebug(bugid, newstate, data, util.email(ctx.user()))
897 self.updatebug(bugid, newstate, data, util.email(ctx.user()))
898
898
899 def hook(ui, repo, hooktype, node=None, **kwargs):
899 def hook(ui, repo, hooktype, node=None, **kwargs):
900 '''add comment to bugzilla for each changeset that refers to a
900 '''add comment to bugzilla for each changeset that refers to a
901 bugzilla bug id. only add a comment once per bug, so same change
901 bugzilla bug id. only add a comment once per bug, so same change
902 seen multiple times does not fill bug with duplicate data.'''
902 seen multiple times does not fill bug with duplicate data.'''
903 if node is None:
903 if node is None:
904 raise util.Abort(_('hook type %s does not pass a changeset id') %
904 raise util.Abort(_('hook type %s does not pass a changeset id') %
905 hooktype)
905 hooktype)
906 try:
906 try:
907 bz = bugzilla(ui, repo)
907 bz = bugzilla(ui, repo)
908 ctx = repo[node]
908 ctx = repo[node]
909 bugs = bz.find_bugs(ctx)
909 bugs = bz.find_bugs(ctx)
910 if bugs:
910 if bugs:
911 for bug in bugs:
911 for bug in bugs:
912 bz.update(bug, bugs[bug], ctx)
912 bz.update(bug, bugs[bug], ctx)
913 bz.notify(bugs, util.email(ctx.user()))
913 bz.notify(bugs, util.email(ctx.user()))
914 except Exception, e:
914 except Exception, e:
915 raise util.Abort(_('Bugzilla error: %s') % e)
915 raise util.Abort(_('Bugzilla error: %s') % e)
@@ -1,128 +1,128 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2007 Daniel Holth <dholth@fastmail.fm>
3 # Copyright (C) 2007 Daniel Holth <dholth@fastmail.fm>
4 # This is a stripped-down version of the original bzr-svn transport.py,
4 # This is a stripped-down version of the original bzr-svn transport.py,
5 # Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org>
5 # Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org>
6
6
7 # This program is free software; you can redistribute it and/or modify
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
10 # (at your option) any later version.
11
11
12 # This program is distributed in the hope that it will be useful,
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
15 # GNU General Public License for more details.
16
16
17 # You should have received a copy of the GNU General Public License
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, see <http://www.gnu.org/licenses/>.
18 # along with this program; if not, see <http://www.gnu.org/licenses/>.
19
19
20 from mercurial import util
20 from mercurial import util
21 from svn.core import SubversionException, Pool
21 from svn.core import SubversionException, Pool
22 import svn.ra
22 import svn.ra
23 import svn.client
23 import svn.client
24 import svn.core
24 import svn.core
25
25
26 # Some older versions of the Python bindings need to be
26 # Some older versions of the Python bindings need to be
27 # explicitly initialized. But what we want to do probably
27 # explicitly initialized. But what we want to do probably
28 # won't work worth a darn against those libraries anyway!
28 # won't work worth a darn against those libraries anyway!
29 svn.ra.initialize()
29 svn.ra.initialize()
30
30
31 svn_config = svn.core.svn_config_get_config(None)
31 svn_config = svn.core.svn_config_get_config(None)
32
32
33
33
34 def _create_auth_baton(pool):
34 def _create_auth_baton(pool):
35 """Create a Subversion authentication baton. """
35 """Create a Subversion authentication baton. """
36 import svn.client
36 import svn.client
37 # Give the client context baton a suite of authentication
37 # Give the client context baton a suite of authentication
38 # providers.h
38 # providers.h
39 providers = [
39 providers = [
40 svn.client.get_simple_provider(pool),
40 svn.client.get_simple_provider(pool),
41 svn.client.get_username_provider(pool),
41 svn.client.get_username_provider(pool),
42 svn.client.get_ssl_client_cert_file_provider(pool),
42 svn.client.get_ssl_client_cert_file_provider(pool),
43 svn.client.get_ssl_client_cert_pw_file_provider(pool),
43 svn.client.get_ssl_client_cert_pw_file_provider(pool),
44 svn.client.get_ssl_server_trust_file_provider(pool),
44 svn.client.get_ssl_server_trust_file_provider(pool),
45 ]
45 ]
46 # Platform-dependant authentication methods
46 # Platform-dependent authentication methods
47 getprovider = getattr(svn.core, 'svn_auth_get_platform_specific_provider',
47 getprovider = getattr(svn.core, 'svn_auth_get_platform_specific_provider',
48 None)
48 None)
49 if getprovider:
49 if getprovider:
50 # Available in svn >= 1.6
50 # Available in svn >= 1.6
51 for name in ('gnome_keyring', 'keychain', 'kwallet', 'windows'):
51 for name in ('gnome_keyring', 'keychain', 'kwallet', 'windows'):
52 for type in ('simple', 'ssl_client_cert_pw', 'ssl_server_trust'):
52 for type in ('simple', 'ssl_client_cert_pw', 'ssl_server_trust'):
53 p = getprovider(name, type, pool)
53 p = getprovider(name, type, pool)
54 if p:
54 if p:
55 providers.append(p)
55 providers.append(p)
56 else:
56 else:
57 if util.safehasattr(svn.client, 'get_windows_simple_provider'):
57 if util.safehasattr(svn.client, 'get_windows_simple_provider'):
58 providers.append(svn.client.get_windows_simple_provider(pool))
58 providers.append(svn.client.get_windows_simple_provider(pool))
59
59
60 return svn.core.svn_auth_open(providers, pool)
60 return svn.core.svn_auth_open(providers, pool)
61
61
62 class NotBranchError(SubversionException):
62 class NotBranchError(SubversionException):
63 pass
63 pass
64
64
65 class SvnRaTransport(object):
65 class SvnRaTransport(object):
66 """
66 """
67 Open an ra connection to a Subversion repository.
67 Open an ra connection to a Subversion repository.
68 """
68 """
69 def __init__(self, url="", ra=None):
69 def __init__(self, url="", ra=None):
70 self.pool = Pool()
70 self.pool = Pool()
71 self.svn_url = url
71 self.svn_url = url
72 self.username = ''
72 self.username = ''
73 self.password = ''
73 self.password = ''
74
74
75 # Only Subversion 1.4 has reparent()
75 # Only Subversion 1.4 has reparent()
76 if ra is None or not util.safehasattr(svn.ra, 'reparent'):
76 if ra is None or not util.safehasattr(svn.ra, 'reparent'):
77 self.client = svn.client.create_context(self.pool)
77 self.client = svn.client.create_context(self.pool)
78 ab = _create_auth_baton(self.pool)
78 ab = _create_auth_baton(self.pool)
79 if False:
79 if False:
80 svn.core.svn_auth_set_parameter(
80 svn.core.svn_auth_set_parameter(
81 ab, svn.core.SVN_AUTH_PARAM_DEFAULT_USERNAME, self.username)
81 ab, svn.core.SVN_AUTH_PARAM_DEFAULT_USERNAME, self.username)
82 svn.core.svn_auth_set_parameter(
82 svn.core.svn_auth_set_parameter(
83 ab, svn.core.SVN_AUTH_PARAM_DEFAULT_PASSWORD, self.password)
83 ab, svn.core.SVN_AUTH_PARAM_DEFAULT_PASSWORD, self.password)
84 self.client.auth_baton = ab
84 self.client.auth_baton = ab
85 self.client.config = svn_config
85 self.client.config = svn_config
86 try:
86 try:
87 self.ra = svn.client.open_ra_session(
87 self.ra = svn.client.open_ra_session(
88 self.svn_url,
88 self.svn_url,
89 self.client, self.pool)
89 self.client, self.pool)
90 except SubversionException, (inst, num):
90 except SubversionException, (inst, num):
91 if num in (svn.core.SVN_ERR_RA_ILLEGAL_URL,
91 if num in (svn.core.SVN_ERR_RA_ILLEGAL_URL,
92 svn.core.SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED,
92 svn.core.SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED,
93 svn.core.SVN_ERR_BAD_URL):
93 svn.core.SVN_ERR_BAD_URL):
94 raise NotBranchError(url)
94 raise NotBranchError(url)
95 raise
95 raise
96 else:
96 else:
97 self.ra = ra
97 self.ra = ra
98 svn.ra.reparent(self.ra, self.svn_url.encode('utf8'))
98 svn.ra.reparent(self.ra, self.svn_url.encode('utf8'))
99
99
100 class Reporter(object):
100 class Reporter(object):
101 def __init__(self, reporter_data):
101 def __init__(self, reporter_data):
102 self._reporter, self._baton = reporter_data
102 self._reporter, self._baton = reporter_data
103
103
104 def set_path(self, path, revnum, start_empty, lock_token, pool=None):
104 def set_path(self, path, revnum, start_empty, lock_token, pool=None):
105 svn.ra.reporter2_invoke_set_path(self._reporter, self._baton,
105 svn.ra.reporter2_invoke_set_path(self._reporter, self._baton,
106 path, revnum, start_empty, lock_token, pool)
106 path, revnum, start_empty, lock_token, pool)
107
107
108 def delete_path(self, path, pool=None):
108 def delete_path(self, path, pool=None):
109 svn.ra.reporter2_invoke_delete_path(self._reporter, self._baton,
109 svn.ra.reporter2_invoke_delete_path(self._reporter, self._baton,
110 path, pool)
110 path, pool)
111
111
112 def link_path(self, path, url, revision, start_empty, lock_token,
112 def link_path(self, path, url, revision, start_empty, lock_token,
113 pool=None):
113 pool=None):
114 svn.ra.reporter2_invoke_link_path(self._reporter, self._baton,
114 svn.ra.reporter2_invoke_link_path(self._reporter, self._baton,
115 path, url, revision, start_empty, lock_token,
115 path, url, revision, start_empty, lock_token,
116 pool)
116 pool)
117
117
118 def finish_report(self, pool=None):
118 def finish_report(self, pool=None):
119 svn.ra.reporter2_invoke_finish_report(self._reporter,
119 svn.ra.reporter2_invoke_finish_report(self._reporter,
120 self._baton, pool)
120 self._baton, pool)
121
121
122 def abort_report(self, pool=None):
122 def abort_report(self, pool=None):
123 svn.ra.reporter2_invoke_abort_report(self._reporter,
123 svn.ra.reporter2_invoke_abort_report(self._reporter,
124 self._baton, pool)
124 self._baton, pool)
125
125
126 def do_update(self, revnum, path, *args, **kwargs):
126 def do_update(self, revnum, path, *args, **kwargs):
127 return self.Reporter(svn.ra.do_update(self.ra, revnum, path,
127 return self.Reporter(svn.ra.do_update(self.ra, revnum, path,
128 *args, **kwargs))
128 *args, **kwargs))
@@ -1,349 +1,349 b''
1 """automatically manage newlines in repository files
1 """automatically manage newlines in repository files
2
2
3 This extension allows you to manage the type of line endings (CRLF or
3 This extension allows you to manage the type of line endings (CRLF or
4 LF) that are used in the repository and in the local working
4 LF) that are used in the repository and in the local working
5 directory. That way you can get CRLF line endings on Windows and LF on
5 directory. That way you can get CRLF line endings on Windows and LF on
6 Unix/Mac, thereby letting everybody use their OS native line endings.
6 Unix/Mac, thereby letting everybody use their OS native line endings.
7
7
8 The extension reads its configuration from a versioned ``.hgeol``
8 The extension reads its configuration from a versioned ``.hgeol``
9 configuration file found in the root of the working copy. The
9 configuration file found in the root of the working copy. The
10 ``.hgeol`` file use the same syntax as all other Mercurial
10 ``.hgeol`` file use the same syntax as all other Mercurial
11 configuration files. It uses two sections, ``[patterns]`` and
11 configuration files. It uses two sections, ``[patterns]`` and
12 ``[repository]``.
12 ``[repository]``.
13
13
14 The ``[patterns]`` section specifies how line endings should be
14 The ``[patterns]`` section specifies how line endings should be
15 converted between the working copy and the repository. The format is
15 converted between the working copy and the repository. The format is
16 specified by a file pattern. The first match is used, so put more
16 specified by a file pattern. The first match is used, so put more
17 specific patterns first. The available line endings are ``LF``,
17 specific patterns first. The available line endings are ``LF``,
18 ``CRLF``, and ``BIN``.
18 ``CRLF``, and ``BIN``.
19
19
20 Files with the declared format of ``CRLF`` or ``LF`` are always
20 Files with the declared format of ``CRLF`` or ``LF`` are always
21 checked out and stored in the repository in that format and files
21 checked out and stored in the repository in that format and files
22 declared to be binary (``BIN``) are left unchanged. Additionally,
22 declared to be binary (``BIN``) are left unchanged. Additionally,
23 ``native`` is an alias for checking out in the platform's default line
23 ``native`` is an alias for checking out in the platform's default line
24 ending: ``LF`` on Unix (including Mac OS X) and ``CRLF`` on
24 ending: ``LF`` on Unix (including Mac OS X) and ``CRLF`` on
25 Windows. Note that ``BIN`` (do nothing to line endings) is Mercurial's
25 Windows. Note that ``BIN`` (do nothing to line endings) is Mercurial's
26 default behaviour; it is only needed if you need to override a later,
26 default behaviour; it is only needed if you need to override a later,
27 more general pattern.
27 more general pattern.
28
28
29 The optional ``[repository]`` section specifies the line endings to
29 The optional ``[repository]`` section specifies the line endings to
30 use for files stored in the repository. It has a single setting,
30 use for files stored in the repository. It has a single setting,
31 ``native``, which determines the storage line endings for files
31 ``native``, which determines the storage line endings for files
32 declared as ``native`` in the ``[patterns]`` section. It can be set to
32 declared as ``native`` in the ``[patterns]`` section. It can be set to
33 ``LF`` or ``CRLF``. The default is ``LF``. For example, this means
33 ``LF`` or ``CRLF``. The default is ``LF``. For example, this means
34 that on Windows, files configured as ``native`` (``CRLF`` by default)
34 that on Windows, files configured as ``native`` (``CRLF`` by default)
35 will be converted to ``LF`` when stored in the repository. Files
35 will be converted to ``LF`` when stored in the repository. Files
36 declared as ``LF``, ``CRLF``, or ``BIN`` in the ``[patterns]`` section
36 declared as ``LF``, ``CRLF``, or ``BIN`` in the ``[patterns]`` section
37 are always stored as-is in the repository.
37 are always stored as-is in the repository.
38
38
39 Example versioned ``.hgeol`` file::
39 Example versioned ``.hgeol`` file::
40
40
41 [patterns]
41 [patterns]
42 **.py = native
42 **.py = native
43 **.vcproj = CRLF
43 **.vcproj = CRLF
44 **.txt = native
44 **.txt = native
45 Makefile = LF
45 Makefile = LF
46 **.jpg = BIN
46 **.jpg = BIN
47
47
48 [repository]
48 [repository]
49 native = LF
49 native = LF
50
50
51 .. note::
51 .. note::
52 The rules will first apply when files are touched in the working
52 The rules will first apply when files are touched in the working
53 copy, e.g. by updating to null and back to tip to touch all files.
53 copy, e.g. by updating to null and back to tip to touch all files.
54
54
55 The extension uses an optional ``[eol]`` section read from both the
55 The extension uses an optional ``[eol]`` section read from both the
56 normal Mercurial configuration files and the ``.hgeol`` file, with the
56 normal Mercurial configuration files and the ``.hgeol`` file, with the
57 latter overriding the former. You can use that section to control the
57 latter overriding the former. You can use that section to control the
58 overall behavior. There are three settings:
58 overall behavior. There are three settings:
59
59
60 - ``eol.native`` (default ``os.linesep``) can be set to ``LF`` or
60 - ``eol.native`` (default ``os.linesep``) can be set to ``LF`` or
61 ``CRLF`` to override the default interpretation of ``native`` for
61 ``CRLF`` to override the default interpretation of ``native`` for
62 checkout. This can be used with :hg:`archive` on Unix, say, to
62 checkout. This can be used with :hg:`archive` on Unix, say, to
63 generate an archive where files have line endings for Windows.
63 generate an archive where files have line endings for Windows.
64
64
65 - ``eol.only-consistent`` (default True) can be set to False to make
65 - ``eol.only-consistent`` (default True) can be set to False to make
66 the extension convert files with inconsistent EOLs. Inconsistent
66 the extension convert files with inconsistent EOLs. Inconsistent
67 means that there is both ``CRLF`` and ``LF`` present in the file.
67 means that there is both ``CRLF`` and ``LF`` present in the file.
68 Such files are normally not touched under the assumption that they
68 Such files are normally not touched under the assumption that they
69 have mixed EOLs on purpose.
69 have mixed EOLs on purpose.
70
70
71 - ``eol.fix-trailing-newline`` (default False) can be set to True to
71 - ``eol.fix-trailing-newline`` (default False) can be set to True to
72 ensure that converted files end with a EOL character (either ``\\n``
72 ensure that converted files end with a EOL character (either ``\\n``
73 or ``\\r\\n`` as per the configured patterns).
73 or ``\\r\\n`` as per the configured patterns).
74
74
75 The extension provides ``cleverencode:`` and ``cleverdecode:`` filters
75 The extension provides ``cleverencode:`` and ``cleverdecode:`` filters
76 like the deprecated win32text extension does. This means that you can
76 like the deprecated win32text extension does. This means that you can
77 disable win32text and enable eol and your filters will still work. You
77 disable win32text and enable eol and your filters will still work. You
78 only need to these filters until you have prepared a ``.hgeol`` file.
78 only need to these filters until you have prepared a ``.hgeol`` file.
79
79
80 The ``win32text.forbid*`` hooks provided by the win32text extension
80 The ``win32text.forbid*`` hooks provided by the win32text extension
81 have been unified into a single hook named ``eol.checkheadshook``. The
81 have been unified into a single hook named ``eol.checkheadshook``. The
82 hook will lookup the expected line endings from the ``.hgeol`` file,
82 hook will lookup the expected line endings from the ``.hgeol`` file,
83 which means you must migrate to a ``.hgeol`` file first before using
83 which means you must migrate to a ``.hgeol`` file first before using
84 the hook. ``eol.checkheadshook`` only checks heads, intermediate
84 the hook. ``eol.checkheadshook`` only checks heads, intermediate
85 invalid revisions will be pushed. To forbid them completely, use the
85 invalid revisions will be pushed. To forbid them completely, use the
86 ``eol.checkallhook`` hook. These hooks are best used as
86 ``eol.checkallhook`` hook. These hooks are best used as
87 ``pretxnchangegroup`` hooks.
87 ``pretxnchangegroup`` hooks.
88
88
89 See :hg:`help patterns` for more information about the glob patterns
89 See :hg:`help patterns` for more information about the glob patterns
90 used.
90 used.
91 """
91 """
92
92
93 from mercurial.i18n import _
93 from mercurial.i18n import _
94 from mercurial import util, config, extensions, match, error
94 from mercurial import util, config, extensions, match, error
95 import re, os
95 import re, os
96
96
97 testedwith = 'internal'
97 testedwith = 'internal'
98
98
99 # Matches a lone LF, i.e., one that is not part of CRLF.
99 # Matches a lone LF, i.e., one that is not part of CRLF.
100 singlelf = re.compile('(^|[^\r])\n')
100 singlelf = re.compile('(^|[^\r])\n')
101 # Matches a single EOL which can either be a CRLF where repeated CR
101 # Matches a single EOL which can either be a CRLF where repeated CR
102 # are removed or a LF. We do not care about old Machintosh files, so a
102 # are removed or a LF. We do not care about old Macintosh files, so a
103 # stray CR is an error.
103 # stray CR is an error.
104 eolre = re.compile('\r*\n')
104 eolre = re.compile('\r*\n')
105
105
106
106
107 def inconsistenteol(data):
107 def inconsistenteol(data):
108 return '\r\n' in data and singlelf.search(data)
108 return '\r\n' in data and singlelf.search(data)
109
109
110 def tolf(s, params, ui, **kwargs):
110 def tolf(s, params, ui, **kwargs):
111 """Filter to convert to LF EOLs."""
111 """Filter to convert to LF EOLs."""
112 if util.binary(s):
112 if util.binary(s):
113 return s
113 return s
114 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
114 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
115 return s
115 return s
116 if (ui.configbool('eol', 'fix-trailing-newline', False)
116 if (ui.configbool('eol', 'fix-trailing-newline', False)
117 and s and s[-1] != '\n'):
117 and s and s[-1] != '\n'):
118 s = s + '\n'
118 s = s + '\n'
119 return eolre.sub('\n', s)
119 return eolre.sub('\n', s)
120
120
121 def tocrlf(s, params, ui, **kwargs):
121 def tocrlf(s, params, ui, **kwargs):
122 """Filter to convert to CRLF EOLs."""
122 """Filter to convert to CRLF EOLs."""
123 if util.binary(s):
123 if util.binary(s):
124 return s
124 return s
125 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
125 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
126 return s
126 return s
127 if (ui.configbool('eol', 'fix-trailing-newline', False)
127 if (ui.configbool('eol', 'fix-trailing-newline', False)
128 and s and s[-1] != '\n'):
128 and s and s[-1] != '\n'):
129 s = s + '\n'
129 s = s + '\n'
130 return eolre.sub('\r\n', s)
130 return eolre.sub('\r\n', s)
131
131
132 def isbinary(s, params):
132 def isbinary(s, params):
133 """Filter to do nothing with the file."""
133 """Filter to do nothing with the file."""
134 return s
134 return s
135
135
136 filters = {
136 filters = {
137 'to-lf': tolf,
137 'to-lf': tolf,
138 'to-crlf': tocrlf,
138 'to-crlf': tocrlf,
139 'is-binary': isbinary,
139 'is-binary': isbinary,
140 # The following provide backwards compatibility with win32text
140 # The following provide backwards compatibility with win32text
141 'cleverencode:': tolf,
141 'cleverencode:': tolf,
142 'cleverdecode:': tocrlf
142 'cleverdecode:': tocrlf
143 }
143 }
144
144
145 class eolfile(object):
145 class eolfile(object):
146 def __init__(self, ui, root, data):
146 def __init__(self, ui, root, data):
147 self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
147 self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
148 self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
148 self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
149
149
150 self.cfg = config.config()
150 self.cfg = config.config()
151 # Our files should not be touched. The pattern must be
151 # Our files should not be touched. The pattern must be
152 # inserted first override a '** = native' pattern.
152 # inserted first override a '** = native' pattern.
153 self.cfg.set('patterns', '.hg*', 'BIN')
153 self.cfg.set('patterns', '.hg*', 'BIN')
154 # We can then parse the user's patterns.
154 # We can then parse the user's patterns.
155 self.cfg.parse('.hgeol', data)
155 self.cfg.parse('.hgeol', data)
156
156
157 isrepolf = self.cfg.get('repository', 'native') != 'CRLF'
157 isrepolf = self.cfg.get('repository', 'native') != 'CRLF'
158 self._encode['NATIVE'] = isrepolf and 'to-lf' or 'to-crlf'
158 self._encode['NATIVE'] = isrepolf and 'to-lf' or 'to-crlf'
159 iswdlf = ui.config('eol', 'native', os.linesep) in ('LF', '\n')
159 iswdlf = ui.config('eol', 'native', os.linesep) in ('LF', '\n')
160 self._decode['NATIVE'] = iswdlf and 'to-lf' or 'to-crlf'
160 self._decode['NATIVE'] = iswdlf and 'to-lf' or 'to-crlf'
161
161
162 include = []
162 include = []
163 exclude = []
163 exclude = []
164 for pattern, style in self.cfg.items('patterns'):
164 for pattern, style in self.cfg.items('patterns'):
165 key = style.upper()
165 key = style.upper()
166 if key == 'BIN':
166 if key == 'BIN':
167 exclude.append(pattern)
167 exclude.append(pattern)
168 else:
168 else:
169 include.append(pattern)
169 include.append(pattern)
170 # This will match the files for which we need to care
170 # This will match the files for which we need to care
171 # about inconsistent newlines.
171 # about inconsistent newlines.
172 self.match = match.match(root, '', [], include, exclude)
172 self.match = match.match(root, '', [], include, exclude)
173
173
174 def copytoui(self, ui):
174 def copytoui(self, ui):
175 for pattern, style in self.cfg.items('patterns'):
175 for pattern, style in self.cfg.items('patterns'):
176 key = style.upper()
176 key = style.upper()
177 try:
177 try:
178 ui.setconfig('decode', pattern, self._decode[key])
178 ui.setconfig('decode', pattern, self._decode[key])
179 ui.setconfig('encode', pattern, self._encode[key])
179 ui.setconfig('encode', pattern, self._encode[key])
180 except KeyError:
180 except KeyError:
181 ui.warn(_("ignoring unknown EOL style '%s' from %s\n")
181 ui.warn(_("ignoring unknown EOL style '%s' from %s\n")
182 % (style, self.cfg.source('patterns', pattern)))
182 % (style, self.cfg.source('patterns', pattern)))
183 # eol.only-consistent can be specified in ~/.hgrc or .hgeol
183 # eol.only-consistent can be specified in ~/.hgrc or .hgeol
184 for k, v in self.cfg.items('eol'):
184 for k, v in self.cfg.items('eol'):
185 ui.setconfig('eol', k, v)
185 ui.setconfig('eol', k, v)
186
186
187 def checkrev(self, repo, ctx, files):
187 def checkrev(self, repo, ctx, files):
188 failed = []
188 failed = []
189 for f in (files or ctx.files()):
189 for f in (files or ctx.files()):
190 if f not in ctx:
190 if f not in ctx:
191 continue
191 continue
192 for pattern, style in self.cfg.items('patterns'):
192 for pattern, style in self.cfg.items('patterns'):
193 if not match.match(repo.root, '', [pattern])(f):
193 if not match.match(repo.root, '', [pattern])(f):
194 continue
194 continue
195 target = self._encode[style.upper()]
195 target = self._encode[style.upper()]
196 data = ctx[f].data()
196 data = ctx[f].data()
197 if (target == "to-lf" and "\r\n" in data
197 if (target == "to-lf" and "\r\n" in data
198 or target == "to-crlf" and singlelf.search(data)):
198 or target == "to-crlf" and singlelf.search(data)):
199 failed.append((str(ctx), target, f))
199 failed.append((str(ctx), target, f))
200 break
200 break
201 return failed
201 return failed
202
202
203 def parseeol(ui, repo, nodes):
203 def parseeol(ui, repo, nodes):
204 try:
204 try:
205 for node in nodes:
205 for node in nodes:
206 try:
206 try:
207 if node is None:
207 if node is None:
208 # Cannot use workingctx.data() since it would load
208 # Cannot use workingctx.data() since it would load
209 # and cache the filters before we configure them.
209 # and cache the filters before we configure them.
210 data = repo.wfile('.hgeol').read()
210 data = repo.wfile('.hgeol').read()
211 else:
211 else:
212 data = repo[node]['.hgeol'].data()
212 data = repo[node]['.hgeol'].data()
213 return eolfile(ui, repo.root, data)
213 return eolfile(ui, repo.root, data)
214 except (IOError, LookupError):
214 except (IOError, LookupError):
215 pass
215 pass
216 except error.ParseError, inst:
216 except error.ParseError, inst:
217 ui.warn(_("warning: ignoring .hgeol file due to parse error "
217 ui.warn(_("warning: ignoring .hgeol file due to parse error "
218 "at %s: %s\n") % (inst.args[1], inst.args[0]))
218 "at %s: %s\n") % (inst.args[1], inst.args[0]))
219 return None
219 return None
220
220
221 def _checkhook(ui, repo, node, headsonly):
221 def _checkhook(ui, repo, node, headsonly):
222 # Get revisions to check and touched files at the same time
222 # Get revisions to check and touched files at the same time
223 files = set()
223 files = set()
224 revs = set()
224 revs = set()
225 for rev in xrange(repo[node].rev(), len(repo)):
225 for rev in xrange(repo[node].rev(), len(repo)):
226 revs.add(rev)
226 revs.add(rev)
227 if headsonly:
227 if headsonly:
228 ctx = repo[rev]
228 ctx = repo[rev]
229 files.update(ctx.files())
229 files.update(ctx.files())
230 for pctx in ctx.parents():
230 for pctx in ctx.parents():
231 revs.discard(pctx.rev())
231 revs.discard(pctx.rev())
232 failed = []
232 failed = []
233 for rev in revs:
233 for rev in revs:
234 ctx = repo[rev]
234 ctx = repo[rev]
235 eol = parseeol(ui, repo, [ctx.node()])
235 eol = parseeol(ui, repo, [ctx.node()])
236 if eol:
236 if eol:
237 failed.extend(eol.checkrev(repo, ctx, files))
237 failed.extend(eol.checkrev(repo, ctx, files))
238
238
239 if failed:
239 if failed:
240 eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'}
240 eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'}
241 msgs = []
241 msgs = []
242 for node, target, f in failed:
242 for node, target, f in failed:
243 msgs.append(_(" %s in %s should not have %s line endings") %
243 msgs.append(_(" %s in %s should not have %s line endings") %
244 (f, node, eols[target]))
244 (f, node, eols[target]))
245 raise util.Abort(_("end-of-line check failed:\n") + "\n".join(msgs))
245 raise util.Abort(_("end-of-line check failed:\n") + "\n".join(msgs))
246
246
247 def checkallhook(ui, repo, node, hooktype, **kwargs):
247 def checkallhook(ui, repo, node, hooktype, **kwargs):
248 """verify that files have expected EOLs"""
248 """verify that files have expected EOLs"""
249 _checkhook(ui, repo, node, False)
249 _checkhook(ui, repo, node, False)
250
250
251 def checkheadshook(ui, repo, node, hooktype, **kwargs):
251 def checkheadshook(ui, repo, node, hooktype, **kwargs):
252 """verify that files have expected EOLs"""
252 """verify that files have expected EOLs"""
253 _checkhook(ui, repo, node, True)
253 _checkhook(ui, repo, node, True)
254
254
255 # "checkheadshook" used to be called "hook"
255 # "checkheadshook" used to be called "hook"
256 hook = checkheadshook
256 hook = checkheadshook
257
257
258 def preupdate(ui, repo, hooktype, parent1, parent2):
258 def preupdate(ui, repo, hooktype, parent1, parent2):
259 repo.loadeol([parent1])
259 repo.loadeol([parent1])
260 return False
260 return False
261
261
262 def uisetup(ui):
262 def uisetup(ui):
263 ui.setconfig('hooks', 'preupdate.eol', preupdate)
263 ui.setconfig('hooks', 'preupdate.eol', preupdate)
264
264
265 def extsetup(ui):
265 def extsetup(ui):
266 try:
266 try:
267 extensions.find('win32text')
267 extensions.find('win32text')
268 ui.warn(_("the eol extension is incompatible with the "
268 ui.warn(_("the eol extension is incompatible with the "
269 "win32text extension\n"))
269 "win32text extension\n"))
270 except KeyError:
270 except KeyError:
271 pass
271 pass
272
272
273
273
274 def reposetup(ui, repo):
274 def reposetup(ui, repo):
275 uisetup(repo.ui)
275 uisetup(repo.ui)
276
276
277 if not repo.local():
277 if not repo.local():
278 return
278 return
279 for name, fn in filters.iteritems():
279 for name, fn in filters.iteritems():
280 repo.adddatafilter(name, fn)
280 repo.adddatafilter(name, fn)
281
281
282 ui.setconfig('patch', 'eol', 'auto')
282 ui.setconfig('patch', 'eol', 'auto')
283
283
284 class eolrepo(repo.__class__):
284 class eolrepo(repo.__class__):
285
285
286 def loadeol(self, nodes):
286 def loadeol(self, nodes):
287 eol = parseeol(self.ui, self, nodes)
287 eol = parseeol(self.ui, self, nodes)
288 if eol is None:
288 if eol is None:
289 return None
289 return None
290 eol.copytoui(self.ui)
290 eol.copytoui(self.ui)
291 return eol.match
291 return eol.match
292
292
293 def _hgcleardirstate(self):
293 def _hgcleardirstate(self):
294 self._eolfile = self.loadeol([None, 'tip'])
294 self._eolfile = self.loadeol([None, 'tip'])
295 if not self._eolfile:
295 if not self._eolfile:
296 self._eolfile = util.never
296 self._eolfile = util.never
297 return
297 return
298
298
299 try:
299 try:
300 cachemtime = os.path.getmtime(self.join("eol.cache"))
300 cachemtime = os.path.getmtime(self.join("eol.cache"))
301 except OSError:
301 except OSError:
302 cachemtime = 0
302 cachemtime = 0
303
303
304 try:
304 try:
305 eolmtime = os.path.getmtime(self.wjoin(".hgeol"))
305 eolmtime = os.path.getmtime(self.wjoin(".hgeol"))
306 except OSError:
306 except OSError:
307 eolmtime = 0
307 eolmtime = 0
308
308
309 if eolmtime > cachemtime:
309 if eolmtime > cachemtime:
310 ui.debug("eol: detected change in .hgeol\n")
310 ui.debug("eol: detected change in .hgeol\n")
311 wlock = None
311 wlock = None
312 try:
312 try:
313 wlock = self.wlock()
313 wlock = self.wlock()
314 for f in self.dirstate:
314 for f in self.dirstate:
315 if self.dirstate[f] == 'n':
315 if self.dirstate[f] == 'n':
316 # all normal files need to be looked at
316 # all normal files need to be looked at
317 # again since the new .hgeol file might no
317 # again since the new .hgeol file might no
318 # longer match a file it matched before
318 # longer match a file it matched before
319 self.dirstate.normallookup(f)
319 self.dirstate.normallookup(f)
320 # Create or touch the cache to update mtime
320 # Create or touch the cache to update mtime
321 self.opener("eol.cache", "w").close()
321 self.opener("eol.cache", "w").close()
322 wlock.release()
322 wlock.release()
323 except error.LockUnavailable:
323 except error.LockUnavailable:
324 # If we cannot lock the repository and clear the
324 # If we cannot lock the repository and clear the
325 # dirstate, then a commit might not see all files
325 # dirstate, then a commit might not see all files
326 # as modified. But if we cannot lock the
326 # as modified. But if we cannot lock the
327 # repository, then we can also not make a commit,
327 # repository, then we can also not make a commit,
328 # so ignore the error.
328 # so ignore the error.
329 pass
329 pass
330
330
331 def commitctx(self, ctx, error=False):
331 def commitctx(self, ctx, error=False):
332 for f in sorted(ctx.added() + ctx.modified()):
332 for f in sorted(ctx.added() + ctx.modified()):
333 if not self._eolfile(f):
333 if not self._eolfile(f):
334 continue
334 continue
335 try:
335 try:
336 data = ctx[f].data()
336 data = ctx[f].data()
337 except IOError:
337 except IOError:
338 continue
338 continue
339 if util.binary(data):
339 if util.binary(data):
340 # We should not abort here, since the user should
340 # We should not abort here, since the user should
341 # be able to say "** = native" to automatically
341 # be able to say "** = native" to automatically
342 # have all non-binary files taken care of.
342 # have all non-binary files taken care of.
343 continue
343 continue
344 if inconsistenteol(data):
344 if inconsistenteol(data):
345 raise util.Abort(_("inconsistent newline style "
345 raise util.Abort(_("inconsistent newline style "
346 "in %s\n" % f))
346 "in %s\n" % f))
347 return super(eolrepo, self).commitctx(ctx, error)
347 return super(eolrepo, self).commitctx(ctx, error)
348 repo.__class__ = eolrepo
348 repo.__class__ = eolrepo
349 repo._hgcleardirstate()
349 repo._hgcleardirstate()
@@ -1,649 +1,649 b''
1 /*
1 /*
2 * _inotify.c - Python extension interfacing to the Linux inotify subsystem
2 * _inotify.c - Python extension interfacing to the Linux inotify subsystem
3 *
3 *
4 * Copyright 2006 Bryan O'Sullivan <bos@serpentine.com>
4 * Copyright 2006 Bryan O'Sullivan <bos@serpentine.com>
5 *
5 *
6 * This library is free software; you can redistribute it and/or
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of version 2.1 of the GNU Lesser General
7 * modify it under the terms of version 2.1 of the GNU Lesser General
8 * Public License or any later version.
8 * Public License or any later version.
9 */
9 */
10
10
11 #include <Python.h>
11 #include <Python.h>
12 #include <alloca.h>
12 #include <alloca.h>
13 #include <sys/inotify.h>
13 #include <sys/inotify.h>
14 #include <stdint.h>
14 #include <stdint.h>
15 #include <sys/ioctl.h>
15 #include <sys/ioctl.h>
16 #include <unistd.h>
16 #include <unistd.h>
17
17
18 #include <util.h>
18 #include <util.h>
19
19
20 /* Variables used in the event string representation */
20 /* Variables used in the event string representation */
21 static PyObject *join;
21 static PyObject *join;
22 static PyObject *er_wm;
22 static PyObject *er_wm;
23 static PyObject *er_wmc;
23 static PyObject *er_wmc;
24 static PyObject *er_wmn;
24 static PyObject *er_wmn;
25 static PyObject *er_wmcn;
25 static PyObject *er_wmcn;
26
26
27 static PyObject *init(PyObject *self, PyObject *args)
27 static PyObject *init(PyObject *self, PyObject *args)
28 {
28 {
29 PyObject *ret = NULL;
29 PyObject *ret = NULL;
30 int fd = -1;
30 int fd = -1;
31
31
32 if (!PyArg_ParseTuple(args, ":init"))
32 if (!PyArg_ParseTuple(args, ":init"))
33 goto bail;
33 goto bail;
34
34
35 Py_BEGIN_ALLOW_THREADS;
35 Py_BEGIN_ALLOW_THREADS;
36 fd = inotify_init();
36 fd = inotify_init();
37 Py_END_ALLOW_THREADS;
37 Py_END_ALLOW_THREADS;
38
38
39 if (fd == -1) {
39 if (fd == -1) {
40 PyErr_SetFromErrno(PyExc_OSError);
40 PyErr_SetFromErrno(PyExc_OSError);
41 goto bail;
41 goto bail;
42 }
42 }
43
43
44 ret = PyInt_FromLong(fd);
44 ret = PyInt_FromLong(fd);
45 if (ret == NULL)
45 if (ret == NULL)
46 goto bail;
46 goto bail;
47
47
48 goto done;
48 goto done;
49
49
50 bail:
50 bail:
51 if (fd != -1)
51 if (fd != -1)
52 close(fd);
52 close(fd);
53
53
54 Py_CLEAR(ret);
54 Py_CLEAR(ret);
55
55
56 done:
56 done:
57 return ret;
57 return ret;
58 }
58 }
59
59
60 PyDoc_STRVAR(
60 PyDoc_STRVAR(
61 init_doc,
61 init_doc,
62 "init() -> fd\n"
62 "init() -> fd\n"
63 "\n"
63 "\n"
64 "Initialise an inotify instance.\n"
64 "Initialize an inotify instance.\n"
65 "Return a file descriptor associated with a new inotify event queue.");
65 "Return a file descriptor associated with a new inotify event queue.");
66
66
67 static PyObject *add_watch(PyObject *self, PyObject *args)
67 static PyObject *add_watch(PyObject *self, PyObject *args)
68 {
68 {
69 PyObject *ret = NULL;
69 PyObject *ret = NULL;
70 uint32_t mask;
70 uint32_t mask;
71 int wd = -1;
71 int wd = -1;
72 char *path;
72 char *path;
73 int fd;
73 int fd;
74
74
75 if (!PyArg_ParseTuple(args, "isI:add_watch", &fd, &path, &mask))
75 if (!PyArg_ParseTuple(args, "isI:add_watch", &fd, &path, &mask))
76 goto bail;
76 goto bail;
77
77
78 Py_BEGIN_ALLOW_THREADS;
78 Py_BEGIN_ALLOW_THREADS;
79 wd = inotify_add_watch(fd, path, mask);
79 wd = inotify_add_watch(fd, path, mask);
80 Py_END_ALLOW_THREADS;
80 Py_END_ALLOW_THREADS;
81
81
82 if (wd == -1) {
82 if (wd == -1) {
83 PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
83 PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
84 goto bail;
84 goto bail;
85 }
85 }
86
86
87 ret = PyInt_FromLong(wd);
87 ret = PyInt_FromLong(wd);
88 if (ret == NULL)
88 if (ret == NULL)
89 goto bail;
89 goto bail;
90
90
91 goto done;
91 goto done;
92
92
93 bail:
93 bail:
94 if (wd != -1)
94 if (wd != -1)
95 inotify_rm_watch(fd, wd);
95 inotify_rm_watch(fd, wd);
96
96
97 Py_CLEAR(ret);
97 Py_CLEAR(ret);
98
98
99 done:
99 done:
100 return ret;
100 return ret;
101 }
101 }
102
102
103 PyDoc_STRVAR(
103 PyDoc_STRVAR(
104 add_watch_doc,
104 add_watch_doc,
105 "add_watch(fd, path, mask) -> wd\n"
105 "add_watch(fd, path, mask) -> wd\n"
106 "\n"
106 "\n"
107 "Add a watch to an inotify instance, or modify an existing watch.\n"
107 "Add a watch to an inotify instance, or modify an existing watch.\n"
108 "\n"
108 "\n"
109 " fd: file descriptor returned by init()\n"
109 " fd: file descriptor returned by init()\n"
110 " path: path to watch\n"
110 " path: path to watch\n"
111 " mask: mask of events to watch for\n"
111 " mask: mask of events to watch for\n"
112 "\n"
112 "\n"
113 "Return a unique numeric watch descriptor for the inotify instance\n"
113 "Return a unique numeric watch descriptor for the inotify instance\n"
114 "mapped by the file descriptor.");
114 "mapped by the file descriptor.");
115
115
116 static PyObject *remove_watch(PyObject *self, PyObject *args)
116 static PyObject *remove_watch(PyObject *self, PyObject *args)
117 {
117 {
118 uint32_t wd;
118 uint32_t wd;
119 int fd;
119 int fd;
120 int r;
120 int r;
121
121
122 if (!PyArg_ParseTuple(args, "iI:remove_watch", &fd, &wd))
122 if (!PyArg_ParseTuple(args, "iI:remove_watch", &fd, &wd))
123 return NULL;
123 return NULL;
124
124
125 Py_BEGIN_ALLOW_THREADS;
125 Py_BEGIN_ALLOW_THREADS;
126 r = inotify_rm_watch(fd, wd);
126 r = inotify_rm_watch(fd, wd);
127 Py_END_ALLOW_THREADS;
127 Py_END_ALLOW_THREADS;
128
128
129 if (r == -1) {
129 if (r == -1) {
130 PyErr_SetFromErrno(PyExc_OSError);
130 PyErr_SetFromErrno(PyExc_OSError);
131 return NULL;
131 return NULL;
132 }
132 }
133
133
134 Py_INCREF(Py_None);
134 Py_INCREF(Py_None);
135 return Py_None;
135 return Py_None;
136 }
136 }
137
137
138 PyDoc_STRVAR(
138 PyDoc_STRVAR(
139 remove_watch_doc,
139 remove_watch_doc,
140 "remove_watch(fd, wd)\n"
140 "remove_watch(fd, wd)\n"
141 "\n"
141 "\n"
142 " fd: file descriptor returned by init()\n"
142 " fd: file descriptor returned by init()\n"
143 " wd: watch descriptor returned by add_watch()\n"
143 " wd: watch descriptor returned by add_watch()\n"
144 "\n"
144 "\n"
145 "Remove a watch associated with the watch descriptor wd from the\n"
145 "Remove a watch associated with the watch descriptor wd from the\n"
146 "inotify instance associated with the file descriptor fd.\n"
146 "inotify instance associated with the file descriptor fd.\n"
147 "\n"
147 "\n"
148 "Removing a watch causes an IN_IGNORED event to be generated for this\n"
148 "Removing a watch causes an IN_IGNORED event to be generated for this\n"
149 "watch descriptor.");
149 "watch descriptor.");
150
150
151 #define bit_name(x) {x, #x}
151 #define bit_name(x) {x, #x}
152
152
153 static struct {
153 static struct {
154 int bit;
154 int bit;
155 const char *name;
155 const char *name;
156 PyObject *pyname;
156 PyObject *pyname;
157 } bit_names[] = {
157 } bit_names[] = {
158 bit_name(IN_ACCESS),
158 bit_name(IN_ACCESS),
159 bit_name(IN_MODIFY),
159 bit_name(IN_MODIFY),
160 bit_name(IN_ATTRIB),
160 bit_name(IN_ATTRIB),
161 bit_name(IN_CLOSE_WRITE),
161 bit_name(IN_CLOSE_WRITE),
162 bit_name(IN_CLOSE_NOWRITE),
162 bit_name(IN_CLOSE_NOWRITE),
163 bit_name(IN_OPEN),
163 bit_name(IN_OPEN),
164 bit_name(IN_MOVED_FROM),
164 bit_name(IN_MOVED_FROM),
165 bit_name(IN_MOVED_TO),
165 bit_name(IN_MOVED_TO),
166 bit_name(IN_CREATE),
166 bit_name(IN_CREATE),
167 bit_name(IN_DELETE),
167 bit_name(IN_DELETE),
168 bit_name(IN_DELETE_SELF),
168 bit_name(IN_DELETE_SELF),
169 bit_name(IN_MOVE_SELF),
169 bit_name(IN_MOVE_SELF),
170 bit_name(IN_UNMOUNT),
170 bit_name(IN_UNMOUNT),
171 bit_name(IN_Q_OVERFLOW),
171 bit_name(IN_Q_OVERFLOW),
172 bit_name(IN_IGNORED),
172 bit_name(IN_IGNORED),
173 bit_name(IN_ONLYDIR),
173 bit_name(IN_ONLYDIR),
174 bit_name(IN_DONT_FOLLOW),
174 bit_name(IN_DONT_FOLLOW),
175 bit_name(IN_MASK_ADD),
175 bit_name(IN_MASK_ADD),
176 bit_name(IN_ISDIR),
176 bit_name(IN_ISDIR),
177 bit_name(IN_ONESHOT),
177 bit_name(IN_ONESHOT),
178 {0}
178 {0}
179 };
179 };
180
180
181 static PyObject *decode_mask(int mask)
181 static PyObject *decode_mask(int mask)
182 {
182 {
183 PyObject *ret = PyList_New(0);
183 PyObject *ret = PyList_New(0);
184 int i;
184 int i;
185
185
186 if (ret == NULL)
186 if (ret == NULL)
187 goto bail;
187 goto bail;
188
188
189 for (i = 0; bit_names[i].bit; i++) {
189 for (i = 0; bit_names[i].bit; i++) {
190 if (mask & bit_names[i].bit) {
190 if (mask & bit_names[i].bit) {
191 if (bit_names[i].pyname == NULL) {
191 if (bit_names[i].pyname == NULL) {
192 bit_names[i].pyname = PyString_FromString(bit_names[i].name);
192 bit_names[i].pyname = PyString_FromString(bit_names[i].name);
193 if (bit_names[i].pyname == NULL)
193 if (bit_names[i].pyname == NULL)
194 goto bail;
194 goto bail;
195 }
195 }
196 Py_INCREF(bit_names[i].pyname);
196 Py_INCREF(bit_names[i].pyname);
197 if (PyList_Append(ret, bit_names[i].pyname) == -1)
197 if (PyList_Append(ret, bit_names[i].pyname) == -1)
198 goto bail;
198 goto bail;
199 }
199 }
200 }
200 }
201
201
202 goto done;
202 goto done;
203
203
204 bail:
204 bail:
205 Py_CLEAR(ret);
205 Py_CLEAR(ret);
206
206
207 done:
207 done:
208 return ret;
208 return ret;
209 }
209 }
210
210
211 static PyObject *pydecode_mask(PyObject *self, PyObject *args)
211 static PyObject *pydecode_mask(PyObject *self, PyObject *args)
212 {
212 {
213 int mask;
213 int mask;
214
214
215 if (!PyArg_ParseTuple(args, "i:decode_mask", &mask))
215 if (!PyArg_ParseTuple(args, "i:decode_mask", &mask))
216 return NULL;
216 return NULL;
217
217
218 return decode_mask(mask);
218 return decode_mask(mask);
219 }
219 }
220
220
221 PyDoc_STRVAR(
221 PyDoc_STRVAR(
222 decode_mask_doc,
222 decode_mask_doc,
223 "decode_mask(mask) -> list_of_strings\n"
223 "decode_mask(mask) -> list_of_strings\n"
224 "\n"
224 "\n"
225 "Decode an inotify mask value into a list of strings that give the\n"
225 "Decode an inotify mask value into a list of strings that give the\n"
226 "name of each bit set in the mask.");
226 "name of each bit set in the mask.");
227
227
228 static char doc[] = "Low-level inotify interface wrappers.";
228 static char doc[] = "Low-level inotify interface wrappers.";
229
229
230 static void define_const(PyObject *dict, const char *name, uint32_t val)
230 static void define_const(PyObject *dict, const char *name, uint32_t val)
231 {
231 {
232 PyObject *pyval = PyInt_FromLong(val);
232 PyObject *pyval = PyInt_FromLong(val);
233 PyObject *pyname = PyString_FromString(name);
233 PyObject *pyname = PyString_FromString(name);
234
234
235 if (!pyname || !pyval)
235 if (!pyname || !pyval)
236 goto bail;
236 goto bail;
237
237
238 PyDict_SetItem(dict, pyname, pyval);
238 PyDict_SetItem(dict, pyname, pyval);
239
239
240 bail:
240 bail:
241 Py_XDECREF(pyname);
241 Py_XDECREF(pyname);
242 Py_XDECREF(pyval);
242 Py_XDECREF(pyval);
243 }
243 }
244
244
245 static void define_consts(PyObject *dict)
245 static void define_consts(PyObject *dict)
246 {
246 {
247 define_const(dict, "IN_ACCESS", IN_ACCESS);
247 define_const(dict, "IN_ACCESS", IN_ACCESS);
248 define_const(dict, "IN_MODIFY", IN_MODIFY);
248 define_const(dict, "IN_MODIFY", IN_MODIFY);
249 define_const(dict, "IN_ATTRIB", IN_ATTRIB);
249 define_const(dict, "IN_ATTRIB", IN_ATTRIB);
250 define_const(dict, "IN_CLOSE_WRITE", IN_CLOSE_WRITE);
250 define_const(dict, "IN_CLOSE_WRITE", IN_CLOSE_WRITE);
251 define_const(dict, "IN_CLOSE_NOWRITE", IN_CLOSE_NOWRITE);
251 define_const(dict, "IN_CLOSE_NOWRITE", IN_CLOSE_NOWRITE);
252 define_const(dict, "IN_OPEN", IN_OPEN);
252 define_const(dict, "IN_OPEN", IN_OPEN);
253 define_const(dict, "IN_MOVED_FROM", IN_MOVED_FROM);
253 define_const(dict, "IN_MOVED_FROM", IN_MOVED_FROM);
254 define_const(dict, "IN_MOVED_TO", IN_MOVED_TO);
254 define_const(dict, "IN_MOVED_TO", IN_MOVED_TO);
255
255
256 define_const(dict, "IN_CLOSE", IN_CLOSE);
256 define_const(dict, "IN_CLOSE", IN_CLOSE);
257 define_const(dict, "IN_MOVE", IN_MOVE);
257 define_const(dict, "IN_MOVE", IN_MOVE);
258
258
259 define_const(dict, "IN_CREATE", IN_CREATE);
259 define_const(dict, "IN_CREATE", IN_CREATE);
260 define_const(dict, "IN_DELETE", IN_DELETE);
260 define_const(dict, "IN_DELETE", IN_DELETE);
261 define_const(dict, "IN_DELETE_SELF", IN_DELETE_SELF);
261 define_const(dict, "IN_DELETE_SELF", IN_DELETE_SELF);
262 define_const(dict, "IN_MOVE_SELF", IN_MOVE_SELF);
262 define_const(dict, "IN_MOVE_SELF", IN_MOVE_SELF);
263 define_const(dict, "IN_UNMOUNT", IN_UNMOUNT);
263 define_const(dict, "IN_UNMOUNT", IN_UNMOUNT);
264 define_const(dict, "IN_Q_OVERFLOW", IN_Q_OVERFLOW);
264 define_const(dict, "IN_Q_OVERFLOW", IN_Q_OVERFLOW);
265 define_const(dict, "IN_IGNORED", IN_IGNORED);
265 define_const(dict, "IN_IGNORED", IN_IGNORED);
266
266
267 define_const(dict, "IN_ONLYDIR", IN_ONLYDIR);
267 define_const(dict, "IN_ONLYDIR", IN_ONLYDIR);
268 define_const(dict, "IN_DONT_FOLLOW", IN_DONT_FOLLOW);
268 define_const(dict, "IN_DONT_FOLLOW", IN_DONT_FOLLOW);
269 define_const(dict, "IN_MASK_ADD", IN_MASK_ADD);
269 define_const(dict, "IN_MASK_ADD", IN_MASK_ADD);
270 define_const(dict, "IN_ISDIR", IN_ISDIR);
270 define_const(dict, "IN_ISDIR", IN_ISDIR);
271 define_const(dict, "IN_ONESHOT", IN_ONESHOT);
271 define_const(dict, "IN_ONESHOT", IN_ONESHOT);
272 define_const(dict, "IN_ALL_EVENTS", IN_ALL_EVENTS);
272 define_const(dict, "IN_ALL_EVENTS", IN_ALL_EVENTS);
273 }
273 }
274
274
275 struct event {
275 struct event {
276 PyObject_HEAD
276 PyObject_HEAD
277 PyObject *wd;
277 PyObject *wd;
278 PyObject *mask;
278 PyObject *mask;
279 PyObject *cookie;
279 PyObject *cookie;
280 PyObject *name;
280 PyObject *name;
281 };
281 };
282
282
283 static PyObject *event_wd(PyObject *self, void *x)
283 static PyObject *event_wd(PyObject *self, void *x)
284 {
284 {
285 struct event *evt = (struct event *)self;
285 struct event *evt = (struct event *)self;
286 Py_INCREF(evt->wd);
286 Py_INCREF(evt->wd);
287 return evt->wd;
287 return evt->wd;
288 }
288 }
289
289
290 static PyObject *event_mask(PyObject *self, void *x)
290 static PyObject *event_mask(PyObject *self, void *x)
291 {
291 {
292 struct event *evt = (struct event *)self;
292 struct event *evt = (struct event *)self;
293 Py_INCREF(evt->mask);
293 Py_INCREF(evt->mask);
294 return evt->mask;
294 return evt->mask;
295 }
295 }
296
296
297 static PyObject *event_cookie(PyObject *self, void *x)
297 static PyObject *event_cookie(PyObject *self, void *x)
298 {
298 {
299 struct event *evt = (struct event *)self;
299 struct event *evt = (struct event *)self;
300 Py_INCREF(evt->cookie);
300 Py_INCREF(evt->cookie);
301 return evt->cookie;
301 return evt->cookie;
302 }
302 }
303
303
304 static PyObject *event_name(PyObject *self, void *x)
304 static PyObject *event_name(PyObject *self, void *x)
305 {
305 {
306 struct event *evt = (struct event *)self;
306 struct event *evt = (struct event *)self;
307 Py_INCREF(evt->name);
307 Py_INCREF(evt->name);
308 return evt->name;
308 return evt->name;
309 }
309 }
310
310
311 static struct PyGetSetDef event_getsets[] = {
311 static struct PyGetSetDef event_getsets[] = {
312 {"wd", event_wd, NULL,
312 {"wd", event_wd, NULL,
313 "watch descriptor"},
313 "watch descriptor"},
314 {"mask", event_mask, NULL,
314 {"mask", event_mask, NULL,
315 "event mask"},
315 "event mask"},
316 {"cookie", event_cookie, NULL,
316 {"cookie", event_cookie, NULL,
317 "rename cookie, if rename-related event"},
317 "rename cookie, if rename-related event"},
318 {"name", event_name, NULL,
318 {"name", event_name, NULL,
319 "file name"},
319 "file name"},
320 {NULL}
320 {NULL}
321 };
321 };
322
322
323 PyDoc_STRVAR(
323 PyDoc_STRVAR(
324 event_doc,
324 event_doc,
325 "event: Structure describing an inotify event.");
325 "event: Structure describing an inotify event.");
326
326
327 static PyObject *event_new(PyTypeObject *t, PyObject *a, PyObject *k)
327 static PyObject *event_new(PyTypeObject *t, PyObject *a, PyObject *k)
328 {
328 {
329 return (*t->tp_alloc)(t, 0);
329 return (*t->tp_alloc)(t, 0);
330 }
330 }
331
331
332 static void event_dealloc(struct event *evt)
332 static void event_dealloc(struct event *evt)
333 {
333 {
334 Py_XDECREF(evt->wd);
334 Py_XDECREF(evt->wd);
335 Py_XDECREF(evt->mask);
335 Py_XDECREF(evt->mask);
336 Py_XDECREF(evt->cookie);
336 Py_XDECREF(evt->cookie);
337 Py_XDECREF(evt->name);
337 Py_XDECREF(evt->name);
338
338
339 Py_TYPE(evt)->tp_free(evt);
339 Py_TYPE(evt)->tp_free(evt);
340 }
340 }
341
341
342 static PyObject *event_repr(struct event *evt)
342 static PyObject *event_repr(struct event *evt)
343 {
343 {
344 int cookie = evt->cookie == Py_None ? -1 : PyInt_AsLong(evt->cookie);
344 int cookie = evt->cookie == Py_None ? -1 : PyInt_AsLong(evt->cookie);
345 PyObject *ret = NULL, *pymasks = NULL, *pymask = NULL;
345 PyObject *ret = NULL, *pymasks = NULL, *pymask = NULL;
346 PyObject *tuple = NULL, *formatstr = NULL;
346 PyObject *tuple = NULL, *formatstr = NULL;
347
347
348 pymasks = decode_mask(PyInt_AsLong(evt->mask));
348 pymasks = decode_mask(PyInt_AsLong(evt->mask));
349 if (pymasks == NULL)
349 if (pymasks == NULL)
350 goto bail;
350 goto bail;
351
351
352 pymask = _PyString_Join(join, pymasks);
352 pymask = _PyString_Join(join, pymasks);
353 if (pymask == NULL)
353 if (pymask == NULL)
354 goto bail;
354 goto bail;
355
355
356 if (evt->name != Py_None) {
356 if (evt->name != Py_None) {
357 if (cookie == -1) {
357 if (cookie == -1) {
358 formatstr = er_wmn;
358 formatstr = er_wmn;
359 tuple = PyTuple_Pack(3, evt->wd, pymask, evt->name);
359 tuple = PyTuple_Pack(3, evt->wd, pymask, evt->name);
360 }
360 }
361 else {
361 else {
362 formatstr = er_wmcn;
362 formatstr = er_wmcn;
363 tuple = PyTuple_Pack(4, evt->wd, pymask,
363 tuple = PyTuple_Pack(4, evt->wd, pymask,
364 evt->cookie, evt->name);
364 evt->cookie, evt->name);
365 }
365 }
366 } else {
366 } else {
367 if (cookie == -1) {
367 if (cookie == -1) {
368 formatstr = er_wm;
368 formatstr = er_wm;
369 tuple = PyTuple_Pack(2, evt->wd, pymask);
369 tuple = PyTuple_Pack(2, evt->wd, pymask);
370 }
370 }
371 else {
371 else {
372 formatstr = er_wmc;
372 formatstr = er_wmc;
373 tuple = PyTuple_Pack(3, evt->wd, pymask, evt->cookie);
373 tuple = PyTuple_Pack(3, evt->wd, pymask, evt->cookie);
374 }
374 }
375 }
375 }
376
376
377 if (tuple == NULL)
377 if (tuple == NULL)
378 goto bail;
378 goto bail;
379
379
380 ret = PyNumber_Remainder(formatstr, tuple);
380 ret = PyNumber_Remainder(formatstr, tuple);
381
381
382 if (ret == NULL)
382 if (ret == NULL)
383 goto bail;
383 goto bail;
384
384
385 goto done;
385 goto done;
386 bail:
386 bail:
387 Py_CLEAR(ret);
387 Py_CLEAR(ret);
388
388
389 done:
389 done:
390 Py_XDECREF(pymask);
390 Py_XDECREF(pymask);
391 Py_XDECREF(pymasks);
391 Py_XDECREF(pymasks);
392 Py_XDECREF(tuple);
392 Py_XDECREF(tuple);
393
393
394 return ret;
394 return ret;
395 }
395 }
396
396
397 static PyTypeObject event_type = {
397 static PyTypeObject event_type = {
398 PyVarObject_HEAD_INIT(NULL, 0)
398 PyVarObject_HEAD_INIT(NULL, 0)
399 "_inotify.event", /*tp_name*/
399 "_inotify.event", /*tp_name*/
400 sizeof(struct event), /*tp_basicsize*/
400 sizeof(struct event), /*tp_basicsize*/
401 0, /*tp_itemsize*/
401 0, /*tp_itemsize*/
402 (destructor)event_dealloc, /*tp_dealloc*/
402 (destructor)event_dealloc, /*tp_dealloc*/
403 0, /*tp_print*/
403 0, /*tp_print*/
404 0, /*tp_getattr*/
404 0, /*tp_getattr*/
405 0, /*tp_setattr*/
405 0, /*tp_setattr*/
406 0, /*tp_compare*/
406 0, /*tp_compare*/
407 (reprfunc)event_repr, /*tp_repr*/
407 (reprfunc)event_repr, /*tp_repr*/
408 0, /*tp_as_number*/
408 0, /*tp_as_number*/
409 0, /*tp_as_sequence*/
409 0, /*tp_as_sequence*/
410 0, /*tp_as_mapping*/
410 0, /*tp_as_mapping*/
411 0, /*tp_hash */
411 0, /*tp_hash */
412 0, /*tp_call*/
412 0, /*tp_call*/
413 0, /*tp_str*/
413 0, /*tp_str*/
414 0, /*tp_getattro*/
414 0, /*tp_getattro*/
415 0, /*tp_setattro*/
415 0, /*tp_setattro*/
416 0, /*tp_as_buffer*/
416 0, /*tp_as_buffer*/
417 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
417 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
418 event_doc, /* tp_doc */
418 event_doc, /* tp_doc */
419 0, /* tp_traverse */
419 0, /* tp_traverse */
420 0, /* tp_clear */
420 0, /* tp_clear */
421 0, /* tp_richcompare */
421 0, /* tp_richcompare */
422 0, /* tp_weaklistoffset */
422 0, /* tp_weaklistoffset */
423 0, /* tp_iter */
423 0, /* tp_iter */
424 0, /* tp_iternext */
424 0, /* tp_iternext */
425 0, /* tp_methods */
425 0, /* tp_methods */
426 0, /* tp_members */
426 0, /* tp_members */
427 event_getsets, /* tp_getset */
427 event_getsets, /* tp_getset */
428 0, /* tp_base */
428 0, /* tp_base */
429 0, /* tp_dict */
429 0, /* tp_dict */
430 0, /* tp_descr_get */
430 0, /* tp_descr_get */
431 0, /* tp_descr_set */
431 0, /* tp_descr_set */
432 0, /* tp_dictoffset */
432 0, /* tp_dictoffset */
433 0, /* tp_init */
433 0, /* tp_init */
434 0, /* tp_alloc */
434 0, /* tp_alloc */
435 event_new, /* tp_new */
435 event_new, /* tp_new */
436 };
436 };
437
437
438 PyObject *read_events(PyObject *self, PyObject *args)
438 PyObject *read_events(PyObject *self, PyObject *args)
439 {
439 {
440 PyObject *ctor_args = NULL;
440 PyObject *ctor_args = NULL;
441 PyObject *pybufsize = NULL;
441 PyObject *pybufsize = NULL;
442 PyObject *ret = NULL;
442 PyObject *ret = NULL;
443 int bufsize = 65536;
443 int bufsize = 65536;
444 char *buf = NULL;
444 char *buf = NULL;
445 int nread, pos;
445 int nread, pos;
446 int fd;
446 int fd;
447
447
448 if (!PyArg_ParseTuple(args, "i|O:read", &fd, &pybufsize))
448 if (!PyArg_ParseTuple(args, "i|O:read", &fd, &pybufsize))
449 goto bail;
449 goto bail;
450
450
451 if (pybufsize && pybufsize != Py_None)
451 if (pybufsize && pybufsize != Py_None)
452 bufsize = PyInt_AsLong(pybufsize);
452 bufsize = PyInt_AsLong(pybufsize);
453
453
454 ret = PyList_New(0);
454 ret = PyList_New(0);
455 if (ret == NULL)
455 if (ret == NULL)
456 goto bail;
456 goto bail;
457
457
458 if (bufsize <= 0) {
458 if (bufsize <= 0) {
459 int r;
459 int r;
460
460
461 Py_BEGIN_ALLOW_THREADS;
461 Py_BEGIN_ALLOW_THREADS;
462 r = ioctl(fd, FIONREAD, &bufsize);
462 r = ioctl(fd, FIONREAD, &bufsize);
463 Py_END_ALLOW_THREADS;
463 Py_END_ALLOW_THREADS;
464
464
465 if (r == -1) {
465 if (r == -1) {
466 PyErr_SetFromErrno(PyExc_OSError);
466 PyErr_SetFromErrno(PyExc_OSError);
467 goto bail;
467 goto bail;
468 }
468 }
469 if (bufsize == 0)
469 if (bufsize == 0)
470 goto done;
470 goto done;
471 }
471 }
472 else {
472 else {
473 static long name_max;
473 static long name_max;
474 static long name_fd = -1;
474 static long name_fd = -1;
475 long min;
475 long min;
476
476
477 if (name_fd != fd) {
477 if (name_fd != fd) {
478 name_fd = fd;
478 name_fd = fd;
479 Py_BEGIN_ALLOW_THREADS;
479 Py_BEGIN_ALLOW_THREADS;
480 name_max = fpathconf(fd, _PC_NAME_MAX);
480 name_max = fpathconf(fd, _PC_NAME_MAX);
481 Py_END_ALLOW_THREADS;
481 Py_END_ALLOW_THREADS;
482 }
482 }
483
483
484 min = sizeof(struct inotify_event) + name_max + 1;
484 min = sizeof(struct inotify_event) + name_max + 1;
485
485
486 if (bufsize < min) {
486 if (bufsize < min) {
487 PyErr_Format(PyExc_ValueError,
487 PyErr_Format(PyExc_ValueError,
488 "bufsize must be at least %d", (int)min);
488 "bufsize must be at least %d", (int)min);
489 goto bail;
489 goto bail;
490 }
490 }
491 }
491 }
492
492
493 buf = alloca(bufsize);
493 buf = alloca(bufsize);
494
494
495 Py_BEGIN_ALLOW_THREADS;
495 Py_BEGIN_ALLOW_THREADS;
496 nread = read(fd, buf, bufsize);
496 nread = read(fd, buf, bufsize);
497 Py_END_ALLOW_THREADS;
497 Py_END_ALLOW_THREADS;
498
498
499 if (nread == -1) {
499 if (nread == -1) {
500 PyErr_SetFromErrno(PyExc_OSError);
500 PyErr_SetFromErrno(PyExc_OSError);
501 goto bail;
501 goto bail;
502 }
502 }
503
503
504 ctor_args = PyTuple_New(0);
504 ctor_args = PyTuple_New(0);
505
505
506 if (ctor_args == NULL)
506 if (ctor_args == NULL)
507 goto bail;
507 goto bail;
508
508
509 pos = 0;
509 pos = 0;
510
510
511 while (pos < nread) {
511 while (pos < nread) {
512 struct inotify_event *in = (struct inotify_event *)(buf + pos);
512 struct inotify_event *in = (struct inotify_event *)(buf + pos);
513 struct event *evt;
513 struct event *evt;
514 PyObject *obj;
514 PyObject *obj;
515
515
516 obj = PyObject_CallObject((PyObject *)&event_type, ctor_args);
516 obj = PyObject_CallObject((PyObject *)&event_type, ctor_args);
517
517
518 if (obj == NULL)
518 if (obj == NULL)
519 goto bail;
519 goto bail;
520
520
521 evt = (struct event *)obj;
521 evt = (struct event *)obj;
522
522
523 evt->wd = PyInt_FromLong(in->wd);
523 evt->wd = PyInt_FromLong(in->wd);
524 evt->mask = PyInt_FromLong(in->mask);
524 evt->mask = PyInt_FromLong(in->mask);
525 if (in->mask & IN_MOVE)
525 if (in->mask & IN_MOVE)
526 evt->cookie = PyInt_FromLong(in->cookie);
526 evt->cookie = PyInt_FromLong(in->cookie);
527 else {
527 else {
528 Py_INCREF(Py_None);
528 Py_INCREF(Py_None);
529 evt->cookie = Py_None;
529 evt->cookie = Py_None;
530 }
530 }
531 if (in->len)
531 if (in->len)
532 evt->name = PyString_FromString(in->name);
532 evt->name = PyString_FromString(in->name);
533 else {
533 else {
534 Py_INCREF(Py_None);
534 Py_INCREF(Py_None);
535 evt->name = Py_None;
535 evt->name = Py_None;
536 }
536 }
537
537
538 if (!evt->wd || !evt->mask || !evt->cookie || !evt->name)
538 if (!evt->wd || !evt->mask || !evt->cookie || !evt->name)
539 goto mybail;
539 goto mybail;
540
540
541 if (PyList_Append(ret, obj) == -1)
541 if (PyList_Append(ret, obj) == -1)
542 goto mybail;
542 goto mybail;
543
543
544 pos += sizeof(struct inotify_event) + in->len;
544 pos += sizeof(struct inotify_event) + in->len;
545 continue;
545 continue;
546
546
547 mybail:
547 mybail:
548 Py_CLEAR(evt->wd);
548 Py_CLEAR(evt->wd);
549 Py_CLEAR(evt->mask);
549 Py_CLEAR(evt->mask);
550 Py_CLEAR(evt->cookie);
550 Py_CLEAR(evt->cookie);
551 Py_CLEAR(evt->name);
551 Py_CLEAR(evt->name);
552 Py_DECREF(obj);
552 Py_DECREF(obj);
553
553
554 goto bail;
554 goto bail;
555 }
555 }
556
556
557 goto done;
557 goto done;
558
558
559 bail:
559 bail:
560 Py_CLEAR(ret);
560 Py_CLEAR(ret);
561
561
562 done:
562 done:
563 Py_XDECREF(ctor_args);
563 Py_XDECREF(ctor_args);
564
564
565 return ret;
565 return ret;
566 }
566 }
567
567
568 static int init_globals(void)
568 static int init_globals(void)
569 {
569 {
570 join = PyString_FromString("|");
570 join = PyString_FromString("|");
571 er_wm = PyString_FromString("event(wd=%d, mask=%s)");
571 er_wm = PyString_FromString("event(wd=%d, mask=%s)");
572 er_wmn = PyString_FromString("event(wd=%d, mask=%s, name=%s)");
572 er_wmn = PyString_FromString("event(wd=%d, mask=%s, name=%s)");
573 er_wmc = PyString_FromString("event(wd=%d, mask=%s, cookie=0x%x)");
573 er_wmc = PyString_FromString("event(wd=%d, mask=%s, cookie=0x%x)");
574 er_wmcn = PyString_FromString("event(wd=%d, mask=%s, cookie=0x%x, name=%s)");
574 er_wmcn = PyString_FromString("event(wd=%d, mask=%s, cookie=0x%x, name=%s)");
575
575
576 return join && er_wm && er_wmn && er_wmc && er_wmcn;
576 return join && er_wm && er_wmn && er_wmc && er_wmcn;
577 }
577 }
578
578
579 PyDoc_STRVAR(
579 PyDoc_STRVAR(
580 read_doc,
580 read_doc,
581 "read(fd, bufsize[=65536]) -> list_of_events\n"
581 "read(fd, bufsize[=65536]) -> list_of_events\n"
582 "\n"
582 "\n"
583 "\nRead inotify events from a file descriptor.\n"
583 "\nRead inotify events from a file descriptor.\n"
584 "\n"
584 "\n"
585 " fd: file descriptor returned by init()\n"
585 " fd: file descriptor returned by init()\n"
586 " bufsize: size of buffer to read into, in bytes\n"
586 " bufsize: size of buffer to read into, in bytes\n"
587 "\n"
587 "\n"
588 "Return a list of event objects.\n"
588 "Return a list of event objects.\n"
589 "\n"
589 "\n"
590 "If bufsize is > 0, block until events are available to be read.\n"
590 "If bufsize is > 0, block until events are available to be read.\n"
591 "Otherwise, immediately return all events that can be read without\n"
591 "Otherwise, immediately return all events that can be read without\n"
592 "blocking.");
592 "blocking.");
593
593
594 static PyMethodDef methods[] = {
594 static PyMethodDef methods[] = {
595 {"init", init, METH_VARARGS, init_doc},
595 {"init", init, METH_VARARGS, init_doc},
596 {"add_watch", add_watch, METH_VARARGS, add_watch_doc},
596 {"add_watch", add_watch, METH_VARARGS, add_watch_doc},
597 {"remove_watch", remove_watch, METH_VARARGS, remove_watch_doc},
597 {"remove_watch", remove_watch, METH_VARARGS, remove_watch_doc},
598 {"read", read_events, METH_VARARGS, read_doc},
598 {"read", read_events, METH_VARARGS, read_doc},
599 {"decode_mask", pydecode_mask, METH_VARARGS, decode_mask_doc},
599 {"decode_mask", pydecode_mask, METH_VARARGS, decode_mask_doc},
600 {NULL},
600 {NULL},
601 };
601 };
602
602
603 #ifdef IS_PY3K
603 #ifdef IS_PY3K
604 static struct PyModuleDef _inotify_module = {
604 static struct PyModuleDef _inotify_module = {
605 PyModuleDef_HEAD_INIT,
605 PyModuleDef_HEAD_INIT,
606 "_inotify",
606 "_inotify",
607 doc,
607 doc,
608 -1,
608 -1,
609 methods
609 methods
610 };
610 };
611
611
612 PyMODINIT_FUNC PyInit__inotify(void)
612 PyMODINIT_FUNC PyInit__inotify(void)
613 {
613 {
614 PyObject *mod, *dict;
614 PyObject *mod, *dict;
615
615
616 mod = PyModule_Create(&_inotify_module);
616 mod = PyModule_Create(&_inotify_module);
617
617
618 if (mod == NULL)
618 if (mod == NULL)
619 return NULL;
619 return NULL;
620
620
621 if (!init_globals())
621 if (!init_globals())
622 return;
622 return;
623
623
624 dict = PyModule_GetDict(mod);
624 dict = PyModule_GetDict(mod);
625
625
626 if (dict)
626 if (dict)
627 define_consts(dict);
627 define_consts(dict);
628
628
629 return mod;
629 return mod;
630 }
630 }
631 #else
631 #else
632 void init_inotify(void)
632 void init_inotify(void)
633 {
633 {
634 PyObject *mod, *dict;
634 PyObject *mod, *dict;
635
635
636 if (PyType_Ready(&event_type) == -1)
636 if (PyType_Ready(&event_type) == -1)
637 return;
637 return;
638
638
639 if (!init_globals())
639 if (!init_globals())
640 return;
640 return;
641
641
642 mod = Py_InitModule3("_inotify", methods, doc);
642 mod = Py_InitModule3("_inotify", methods, doc);
643
643
644 dict = PyModule_GetDict(mod);
644 dict = PyModule_GetDict(mod);
645
645
646 if (dict)
646 if (dict)
647 define_consts(dict);
647 define_consts(dict);
648 }
648 }
649 #endif
649 #endif
@@ -1,5902 +1,5902 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, bin, nullid, nullrev, short
8 from node import hex, bin, nullid, nullrev, short
9 from lock import release
9 from lock import release
10 from i18n import _, gettext
10 from i18n import _, gettext
11 import os, re, difflib, time, tempfile, errno
11 import os, re, difflib, time, tempfile, errno
12 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
12 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
13 import patch, help, url, encoding, templatekw, discovery
13 import patch, help, url, encoding, templatekw, discovery
14 import archival, changegroup, cmdutil, hbisect
14 import archival, changegroup, cmdutil, hbisect
15 import sshserver, hgweb, hgweb.server, commandserver
15 import sshserver, hgweb, hgweb.server, commandserver
16 import merge as mergemod
16 import merge as mergemod
17 import minirst, revset, fileset
17 import minirst, revset, fileset
18 import dagparser, context, simplemerge, graphmod
18 import dagparser, context, simplemerge, graphmod
19 import random, setdiscovery, treediscovery, dagutil, pvec, localrepo
19 import random, setdiscovery, treediscovery, dagutil, pvec, localrepo
20 import phases, obsolete
20 import phases, obsolete
21
21
22 table = {}
22 table = {}
23
23
24 command = cmdutil.command(table)
24 command = cmdutil.command(table)
25
25
26 # common command options
26 # common command options
27
27
28 globalopts = [
28 globalopts = [
29 ('R', 'repository', '',
29 ('R', 'repository', '',
30 _('repository root directory or name of overlay bundle file'),
30 _('repository root directory or name of overlay bundle file'),
31 _('REPO')),
31 _('REPO')),
32 ('', 'cwd', '',
32 ('', 'cwd', '',
33 _('change working directory'), _('DIR')),
33 _('change working directory'), _('DIR')),
34 ('y', 'noninteractive', None,
34 ('y', 'noninteractive', None,
35 _('do not prompt, automatically pick the first choice for all prompts')),
35 _('do not prompt, automatically pick the first choice for all prompts')),
36 ('q', 'quiet', None, _('suppress output')),
36 ('q', 'quiet', None, _('suppress output')),
37 ('v', 'verbose', None, _('enable additional output')),
37 ('v', 'verbose', None, _('enable additional output')),
38 ('', 'config', [],
38 ('', 'config', [],
39 _('set/override config option (use \'section.name=value\')'),
39 _('set/override config option (use \'section.name=value\')'),
40 _('CONFIG')),
40 _('CONFIG')),
41 ('', 'debug', None, _('enable debugging output')),
41 ('', 'debug', None, _('enable debugging output')),
42 ('', 'debugger', None, _('start debugger')),
42 ('', 'debugger', None, _('start debugger')),
43 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
43 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
44 _('ENCODE')),
44 _('ENCODE')),
45 ('', 'encodingmode', encoding.encodingmode,
45 ('', 'encodingmode', encoding.encodingmode,
46 _('set the charset encoding mode'), _('MODE')),
46 _('set the charset encoding mode'), _('MODE')),
47 ('', 'traceback', None, _('always print a traceback on exception')),
47 ('', 'traceback', None, _('always print a traceback on exception')),
48 ('', 'time', None, _('time how long the command takes')),
48 ('', 'time', None, _('time how long the command takes')),
49 ('', 'profile', None, _('print command execution profile')),
49 ('', 'profile', None, _('print command execution profile')),
50 ('', 'version', None, _('output version information and exit')),
50 ('', 'version', None, _('output version information and exit')),
51 ('h', 'help', None, _('display help and exit')),
51 ('h', 'help', None, _('display help and exit')),
52 ]
52 ]
53
53
54 dryrunopts = [('n', 'dry-run', None,
54 dryrunopts = [('n', 'dry-run', None,
55 _('do not perform actions, just print output'))]
55 _('do not perform actions, just print output'))]
56
56
57 remoteopts = [
57 remoteopts = [
58 ('e', 'ssh', '',
58 ('e', 'ssh', '',
59 _('specify ssh command to use'), _('CMD')),
59 _('specify ssh command to use'), _('CMD')),
60 ('', 'remotecmd', '',
60 ('', 'remotecmd', '',
61 _('specify hg command to run on the remote side'), _('CMD')),
61 _('specify hg command to run on the remote side'), _('CMD')),
62 ('', 'insecure', None,
62 ('', 'insecure', None,
63 _('do not verify server certificate (ignoring web.cacerts config)')),
63 _('do not verify server certificate (ignoring web.cacerts config)')),
64 ]
64 ]
65
65
66 walkopts = [
66 walkopts = [
67 ('I', 'include', [],
67 ('I', 'include', [],
68 _('include names matching the given patterns'), _('PATTERN')),
68 _('include names matching the given patterns'), _('PATTERN')),
69 ('X', 'exclude', [],
69 ('X', 'exclude', [],
70 _('exclude names matching the given patterns'), _('PATTERN')),
70 _('exclude names matching the given patterns'), _('PATTERN')),
71 ]
71 ]
72
72
73 commitopts = [
73 commitopts = [
74 ('m', 'message', '',
74 ('m', 'message', '',
75 _('use text as commit message'), _('TEXT')),
75 _('use text as commit message'), _('TEXT')),
76 ('l', 'logfile', '',
76 ('l', 'logfile', '',
77 _('read commit message from file'), _('FILE')),
77 _('read commit message from file'), _('FILE')),
78 ]
78 ]
79
79
80 commitopts2 = [
80 commitopts2 = [
81 ('d', 'date', '',
81 ('d', 'date', '',
82 _('record the specified date as commit date'), _('DATE')),
82 _('record the specified date as commit date'), _('DATE')),
83 ('u', 'user', '',
83 ('u', 'user', '',
84 _('record the specified user as committer'), _('USER')),
84 _('record the specified user as committer'), _('USER')),
85 ]
85 ]
86
86
87 templateopts = [
87 templateopts = [
88 ('', 'style', '',
88 ('', 'style', '',
89 _('display using template map file'), _('STYLE')),
89 _('display using template map file'), _('STYLE')),
90 ('', 'template', '',
90 ('', 'template', '',
91 _('display with template'), _('TEMPLATE')),
91 _('display with template'), _('TEMPLATE')),
92 ]
92 ]
93
93
94 logopts = [
94 logopts = [
95 ('p', 'patch', None, _('show patch')),
95 ('p', 'patch', None, _('show patch')),
96 ('g', 'git', None, _('use git extended diff format')),
96 ('g', 'git', None, _('use git extended diff format')),
97 ('l', 'limit', '',
97 ('l', 'limit', '',
98 _('limit number of changes displayed'), _('NUM')),
98 _('limit number of changes displayed'), _('NUM')),
99 ('M', 'no-merges', None, _('do not show merges')),
99 ('M', 'no-merges', None, _('do not show merges')),
100 ('', 'stat', None, _('output diffstat-style summary of changes')),
100 ('', 'stat', None, _('output diffstat-style summary of changes')),
101 ('G', 'graph', None, _("show the revision DAG")),
101 ('G', 'graph', None, _("show the revision DAG")),
102 ] + templateopts
102 ] + templateopts
103
103
104 diffopts = [
104 diffopts = [
105 ('a', 'text', None, _('treat all files as text')),
105 ('a', 'text', None, _('treat all files as text')),
106 ('g', 'git', None, _('use git extended diff format')),
106 ('g', 'git', None, _('use git extended diff format')),
107 ('', 'nodates', None, _('omit dates from diff headers'))
107 ('', 'nodates', None, _('omit dates from diff headers'))
108 ]
108 ]
109
109
110 diffwsopts = [
110 diffwsopts = [
111 ('w', 'ignore-all-space', None,
111 ('w', 'ignore-all-space', None,
112 _('ignore white space when comparing lines')),
112 _('ignore white space when comparing lines')),
113 ('b', 'ignore-space-change', None,
113 ('b', 'ignore-space-change', None,
114 _('ignore changes in the amount of white space')),
114 _('ignore changes in the amount of white space')),
115 ('B', 'ignore-blank-lines', None,
115 ('B', 'ignore-blank-lines', None,
116 _('ignore changes whose lines are all blank')),
116 _('ignore changes whose lines are all blank')),
117 ]
117 ]
118
118
119 diffopts2 = [
119 diffopts2 = [
120 ('p', 'show-function', None, _('show which function each change is in')),
120 ('p', 'show-function', None, _('show which function each change is in')),
121 ('', 'reverse', None, _('produce a diff that undoes the changes')),
121 ('', 'reverse', None, _('produce a diff that undoes the changes')),
122 ] + diffwsopts + [
122 ] + diffwsopts + [
123 ('U', 'unified', '',
123 ('U', 'unified', '',
124 _('number of lines of context to show'), _('NUM')),
124 _('number of lines of context to show'), _('NUM')),
125 ('', 'stat', None, _('output diffstat-style summary of changes')),
125 ('', 'stat', None, _('output diffstat-style summary of changes')),
126 ]
126 ]
127
127
128 mergetoolopts = [
128 mergetoolopts = [
129 ('t', 'tool', '', _('specify merge tool')),
129 ('t', 'tool', '', _('specify merge tool')),
130 ]
130 ]
131
131
132 similarityopts = [
132 similarityopts = [
133 ('s', 'similarity', '',
133 ('s', 'similarity', '',
134 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
134 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
135 ]
135 ]
136
136
137 subrepoopts = [
137 subrepoopts = [
138 ('S', 'subrepos', None,
138 ('S', 'subrepos', None,
139 _('recurse into subrepositories'))
139 _('recurse into subrepositories'))
140 ]
140 ]
141
141
142 # Commands start here, listed alphabetically
142 # Commands start here, listed alphabetically
143
143
144 @command('^add',
144 @command('^add',
145 walkopts + subrepoopts + dryrunopts,
145 walkopts + subrepoopts + dryrunopts,
146 _('[OPTION]... [FILE]...'))
146 _('[OPTION]... [FILE]...'))
147 def add(ui, repo, *pats, **opts):
147 def add(ui, repo, *pats, **opts):
148 """add the specified files on the next commit
148 """add the specified files on the next commit
149
149
150 Schedule files to be version controlled and added to the
150 Schedule files to be version controlled and added to the
151 repository.
151 repository.
152
152
153 The files will be added to the repository at the next commit. To
153 The files will be added to the repository at the next commit. To
154 undo an add before that, see :hg:`forget`.
154 undo an add before that, see :hg:`forget`.
155
155
156 If no names are given, add all files to the repository.
156 If no names are given, add all files to the repository.
157
157
158 .. container:: verbose
158 .. container:: verbose
159
159
160 An example showing how new (unknown) files are added
160 An example showing how new (unknown) files are added
161 automatically by :hg:`add`::
161 automatically by :hg:`add`::
162
162
163 $ ls
163 $ ls
164 foo.c
164 foo.c
165 $ hg status
165 $ hg status
166 ? foo.c
166 ? foo.c
167 $ hg add
167 $ hg add
168 adding foo.c
168 adding foo.c
169 $ hg status
169 $ hg status
170 A foo.c
170 A foo.c
171
171
172 Returns 0 if all files are successfully added.
172 Returns 0 if all files are successfully added.
173 """
173 """
174
174
175 m = scmutil.match(repo[None], pats, opts)
175 m = scmutil.match(repo[None], pats, opts)
176 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
176 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
177 opts.get('subrepos'), prefix="", explicitonly=False)
177 opts.get('subrepos'), prefix="", explicitonly=False)
178 return rejected and 1 or 0
178 return rejected and 1 or 0
179
179
180 @command('addremove',
180 @command('addremove',
181 similarityopts + walkopts + dryrunopts,
181 similarityopts + walkopts + dryrunopts,
182 _('[OPTION]... [FILE]...'))
182 _('[OPTION]... [FILE]...'))
183 def addremove(ui, repo, *pats, **opts):
183 def addremove(ui, repo, *pats, **opts):
184 """add all new files, delete all missing files
184 """add all new files, delete all missing files
185
185
186 Add all new files and remove all missing files from the
186 Add all new files and remove all missing files from the
187 repository.
187 repository.
188
188
189 New files are ignored if they match any of the patterns in
189 New files are ignored if they match any of the patterns in
190 ``.hgignore``. As with add, these changes take effect at the next
190 ``.hgignore``. As with add, these changes take effect at the next
191 commit.
191 commit.
192
192
193 Use the -s/--similarity option to detect renamed files. This
193 Use the -s/--similarity option to detect renamed files. This
194 option takes a percentage between 0 (disabled) and 100 (files must
194 option takes a percentage between 0 (disabled) and 100 (files must
195 be identical) as its parameter. With a parameter greater than 0,
195 be identical) as its parameter. With a parameter greater than 0,
196 this compares every removed file with every added file and records
196 this compares every removed file with every added file and records
197 those similar enough as renames. Detecting renamed files this way
197 those similar enough as renames. Detecting renamed files this way
198 can be expensive. After using this option, :hg:`status -C` can be
198 can be expensive. After using this option, :hg:`status -C` can be
199 used to check which files were identified as moved or renamed. If
199 used to check which files were identified as moved or renamed. If
200 not specified, -s/--similarity defaults to 100 and only renames of
200 not specified, -s/--similarity defaults to 100 and only renames of
201 identical files are detected.
201 identical files are detected.
202
202
203 Returns 0 if all files are successfully added.
203 Returns 0 if all files are successfully added.
204 """
204 """
205 try:
205 try:
206 sim = float(opts.get('similarity') or 100)
206 sim = float(opts.get('similarity') or 100)
207 except ValueError:
207 except ValueError:
208 raise util.Abort(_('similarity must be a number'))
208 raise util.Abort(_('similarity must be a number'))
209 if sim < 0 or sim > 100:
209 if sim < 0 or sim > 100:
210 raise util.Abort(_('similarity must be between 0 and 100'))
210 raise util.Abort(_('similarity must be between 0 and 100'))
211 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
211 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
212
212
213 @command('^annotate|blame',
213 @command('^annotate|blame',
214 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
214 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
215 ('', 'follow', None,
215 ('', 'follow', None,
216 _('follow copies/renames and list the filename (DEPRECATED)')),
216 _('follow copies/renames and list the filename (DEPRECATED)')),
217 ('', 'no-follow', None, _("don't follow copies and renames")),
217 ('', 'no-follow', None, _("don't follow copies and renames")),
218 ('a', 'text', None, _('treat all files as text')),
218 ('a', 'text', None, _('treat all files as text')),
219 ('u', 'user', None, _('list the author (long with -v)')),
219 ('u', 'user', None, _('list the author (long with -v)')),
220 ('f', 'file', None, _('list the filename')),
220 ('f', 'file', None, _('list the filename')),
221 ('d', 'date', None, _('list the date (short with -q)')),
221 ('d', 'date', None, _('list the date (short with -q)')),
222 ('n', 'number', None, _('list the revision number (default)')),
222 ('n', 'number', None, _('list the revision number (default)')),
223 ('c', 'changeset', None, _('list the changeset')),
223 ('c', 'changeset', None, _('list the changeset')),
224 ('l', 'line-number', None, _('show line number at the first appearance'))
224 ('l', 'line-number', None, _('show line number at the first appearance'))
225 ] + diffwsopts + walkopts,
225 ] + diffwsopts + walkopts,
226 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
226 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
227 def annotate(ui, repo, *pats, **opts):
227 def annotate(ui, repo, *pats, **opts):
228 """show changeset information by line for each file
228 """show changeset information by line for each file
229
229
230 List changes in files, showing the revision id responsible for
230 List changes in files, showing the revision id responsible for
231 each line
231 each line
232
232
233 This command is useful for discovering when a change was made and
233 This command is useful for discovering when a change was made and
234 by whom.
234 by whom.
235
235
236 Without the -a/--text option, annotate will avoid processing files
236 Without the -a/--text option, annotate will avoid processing files
237 it detects as binary. With -a, annotate will annotate the file
237 it detects as binary. With -a, annotate will annotate the file
238 anyway, although the results will probably be neither useful
238 anyway, although the results will probably be neither useful
239 nor desirable.
239 nor desirable.
240
240
241 Returns 0 on success.
241 Returns 0 on success.
242 """
242 """
243 if opts.get('follow'):
243 if opts.get('follow'):
244 # --follow is deprecated and now just an alias for -f/--file
244 # --follow is deprecated and now just an alias for -f/--file
245 # to mimic the behavior of Mercurial before version 1.5
245 # to mimic the behavior of Mercurial before version 1.5
246 opts['file'] = True
246 opts['file'] = True
247
247
248 datefunc = ui.quiet and util.shortdate or util.datestr
248 datefunc = ui.quiet and util.shortdate or util.datestr
249 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
249 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
250
250
251 if not pats:
251 if not pats:
252 raise util.Abort(_('at least one filename or pattern is required'))
252 raise util.Abort(_('at least one filename or pattern is required'))
253
253
254 hexfn = ui.debugflag and hex or short
254 hexfn = ui.debugflag and hex or short
255
255
256 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
256 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
257 ('number', ' ', lambda x: str(x[0].rev())),
257 ('number', ' ', lambda x: str(x[0].rev())),
258 ('changeset', ' ', lambda x: hexfn(x[0].node())),
258 ('changeset', ' ', lambda x: hexfn(x[0].node())),
259 ('date', ' ', getdate),
259 ('date', ' ', getdate),
260 ('file', ' ', lambda x: x[0].path()),
260 ('file', ' ', lambda x: x[0].path()),
261 ('line_number', ':', lambda x: str(x[1])),
261 ('line_number', ':', lambda x: str(x[1])),
262 ]
262 ]
263
263
264 if (not opts.get('user') and not opts.get('changeset')
264 if (not opts.get('user') and not opts.get('changeset')
265 and not opts.get('date') and not opts.get('file')):
265 and not opts.get('date') and not opts.get('file')):
266 opts['number'] = True
266 opts['number'] = True
267
267
268 linenumber = opts.get('line_number') is not None
268 linenumber = opts.get('line_number') is not None
269 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
269 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
270 raise util.Abort(_('at least one of -n/-c is required for -l'))
270 raise util.Abort(_('at least one of -n/-c is required for -l'))
271
271
272 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
272 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
273 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
273 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
274
274
275 def bad(x, y):
275 def bad(x, y):
276 raise util.Abort("%s: %s" % (x, y))
276 raise util.Abort("%s: %s" % (x, y))
277
277
278 ctx = scmutil.revsingle(repo, opts.get('rev'))
278 ctx = scmutil.revsingle(repo, opts.get('rev'))
279 m = scmutil.match(ctx, pats, opts)
279 m = scmutil.match(ctx, pats, opts)
280 m.bad = bad
280 m.bad = bad
281 follow = not opts.get('no_follow')
281 follow = not opts.get('no_follow')
282 diffopts = patch.diffopts(ui, opts, section='annotate')
282 diffopts = patch.diffopts(ui, opts, section='annotate')
283 for abs in ctx.walk(m):
283 for abs in ctx.walk(m):
284 fctx = ctx[abs]
284 fctx = ctx[abs]
285 if not opts.get('text') and util.binary(fctx.data()):
285 if not opts.get('text') and util.binary(fctx.data()):
286 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
286 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
287 continue
287 continue
288
288
289 lines = fctx.annotate(follow=follow, linenumber=linenumber,
289 lines = fctx.annotate(follow=follow, linenumber=linenumber,
290 diffopts=diffopts)
290 diffopts=diffopts)
291 pieces = []
291 pieces = []
292
292
293 for f, sep in funcmap:
293 for f, sep in funcmap:
294 l = [f(n) for n, dummy in lines]
294 l = [f(n) for n, dummy in lines]
295 if l:
295 if l:
296 sized = [(x, encoding.colwidth(x)) for x in l]
296 sized = [(x, encoding.colwidth(x)) for x in l]
297 ml = max([w for x, w in sized])
297 ml = max([w for x, w in sized])
298 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
298 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
299 for x, w in sized])
299 for x, w in sized])
300
300
301 if pieces:
301 if pieces:
302 for p, l in zip(zip(*pieces), lines):
302 for p, l in zip(zip(*pieces), lines):
303 ui.write("%s: %s" % ("".join(p), l[1]))
303 ui.write("%s: %s" % ("".join(p), l[1]))
304
304
305 if lines and not lines[-1][1].endswith('\n'):
305 if lines and not lines[-1][1].endswith('\n'):
306 ui.write('\n')
306 ui.write('\n')
307
307
308 @command('archive',
308 @command('archive',
309 [('', 'no-decode', None, _('do not pass files through decoders')),
309 [('', 'no-decode', None, _('do not pass files through decoders')),
310 ('p', 'prefix', '', _('directory prefix for files in archive'),
310 ('p', 'prefix', '', _('directory prefix for files in archive'),
311 _('PREFIX')),
311 _('PREFIX')),
312 ('r', 'rev', '', _('revision to distribute'), _('REV')),
312 ('r', 'rev', '', _('revision to distribute'), _('REV')),
313 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
313 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
314 ] + subrepoopts + walkopts,
314 ] + subrepoopts + walkopts,
315 _('[OPTION]... DEST'))
315 _('[OPTION]... DEST'))
316 def archive(ui, repo, dest, **opts):
316 def archive(ui, repo, dest, **opts):
317 '''create an unversioned archive of a repository revision
317 '''create an unversioned archive of a repository revision
318
318
319 By default, the revision used is the parent of the working
319 By default, the revision used is the parent of the working
320 directory; use -r/--rev to specify a different revision.
320 directory; use -r/--rev to specify a different revision.
321
321
322 The archive type is automatically detected based on file
322 The archive type is automatically detected based on file
323 extension (or override using -t/--type).
323 extension (or override using -t/--type).
324
324
325 .. container:: verbose
325 .. container:: verbose
326
326
327 Examples:
327 Examples:
328
328
329 - create a zip file containing the 1.0 release::
329 - create a zip file containing the 1.0 release::
330
330
331 hg archive -r 1.0 project-1.0.zip
331 hg archive -r 1.0 project-1.0.zip
332
332
333 - create a tarball excluding .hg files::
333 - create a tarball excluding .hg files::
334
334
335 hg archive project.tar.gz -X ".hg*"
335 hg archive project.tar.gz -X ".hg*"
336
336
337 Valid types are:
337 Valid types are:
338
338
339 :``files``: a directory full of files (default)
339 :``files``: a directory full of files (default)
340 :``tar``: tar archive, uncompressed
340 :``tar``: tar archive, uncompressed
341 :``tbz2``: tar archive, compressed using bzip2
341 :``tbz2``: tar archive, compressed using bzip2
342 :``tgz``: tar archive, compressed using gzip
342 :``tgz``: tar archive, compressed using gzip
343 :``uzip``: zip archive, uncompressed
343 :``uzip``: zip archive, uncompressed
344 :``zip``: zip archive, compressed using deflate
344 :``zip``: zip archive, compressed using deflate
345
345
346 The exact name of the destination archive or directory is given
346 The exact name of the destination archive or directory is given
347 using a format string; see :hg:`help export` for details.
347 using a format string; see :hg:`help export` for details.
348
348
349 Each member added to an archive file has a directory prefix
349 Each member added to an archive file has a directory prefix
350 prepended. Use -p/--prefix to specify a format string for the
350 prepended. Use -p/--prefix to specify a format string for the
351 prefix. The default is the basename of the archive, with suffixes
351 prefix. The default is the basename of the archive, with suffixes
352 removed.
352 removed.
353
353
354 Returns 0 on success.
354 Returns 0 on success.
355 '''
355 '''
356
356
357 ctx = scmutil.revsingle(repo, opts.get('rev'))
357 ctx = scmutil.revsingle(repo, opts.get('rev'))
358 if not ctx:
358 if not ctx:
359 raise util.Abort(_('no working directory: please specify a revision'))
359 raise util.Abort(_('no working directory: please specify a revision'))
360 node = ctx.node()
360 node = ctx.node()
361 dest = cmdutil.makefilename(repo, dest, node)
361 dest = cmdutil.makefilename(repo, dest, node)
362 if os.path.realpath(dest) == repo.root:
362 if os.path.realpath(dest) == repo.root:
363 raise util.Abort(_('repository root cannot be destination'))
363 raise util.Abort(_('repository root cannot be destination'))
364
364
365 kind = opts.get('type') or archival.guesskind(dest) or 'files'
365 kind = opts.get('type') or archival.guesskind(dest) or 'files'
366 prefix = opts.get('prefix')
366 prefix = opts.get('prefix')
367
367
368 if dest == '-':
368 if dest == '-':
369 if kind == 'files':
369 if kind == 'files':
370 raise util.Abort(_('cannot archive plain files to stdout'))
370 raise util.Abort(_('cannot archive plain files to stdout'))
371 dest = cmdutil.makefileobj(repo, dest)
371 dest = cmdutil.makefileobj(repo, dest)
372 if not prefix:
372 if not prefix:
373 prefix = os.path.basename(repo.root) + '-%h'
373 prefix = os.path.basename(repo.root) + '-%h'
374
374
375 prefix = cmdutil.makefilename(repo, prefix, node)
375 prefix = cmdutil.makefilename(repo, prefix, node)
376 matchfn = scmutil.match(ctx, [], opts)
376 matchfn = scmutil.match(ctx, [], opts)
377 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
377 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
378 matchfn, prefix, subrepos=opts.get('subrepos'))
378 matchfn, prefix, subrepos=opts.get('subrepos'))
379
379
380 @command('backout',
380 @command('backout',
381 [('', 'merge', None, _('merge with old dirstate parent after backout')),
381 [('', 'merge', None, _('merge with old dirstate parent after backout')),
382 ('', 'parent', '',
382 ('', 'parent', '',
383 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
383 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
384 ('r', 'rev', '', _('revision to backout'), _('REV')),
384 ('r', 'rev', '', _('revision to backout'), _('REV')),
385 ] + mergetoolopts + walkopts + commitopts + commitopts2,
385 ] + mergetoolopts + walkopts + commitopts + commitopts2,
386 _('[OPTION]... [-r] REV'))
386 _('[OPTION]... [-r] REV'))
387 def backout(ui, repo, node=None, rev=None, **opts):
387 def backout(ui, repo, node=None, rev=None, **opts):
388 '''reverse effect of earlier changeset
388 '''reverse effect of earlier changeset
389
389
390 Prepare a new changeset with the effect of REV undone in the
390 Prepare a new changeset with the effect of REV undone in the
391 current working directory.
391 current working directory.
392
392
393 If REV is the parent of the working directory, then this new changeset
393 If REV is the parent of the working directory, then this new changeset
394 is committed automatically. Otherwise, hg needs to merge the
394 is committed automatically. Otherwise, hg needs to merge the
395 changes and the merged result is left uncommitted.
395 changes and the merged result is left uncommitted.
396
396
397 .. note::
397 .. note::
398 backout cannot be used to fix either an unwanted or
398 backout cannot be used to fix either an unwanted or
399 incorrect merge.
399 incorrect merge.
400
400
401 .. container:: verbose
401 .. container:: verbose
402
402
403 By default, the pending changeset will have one parent,
403 By default, the pending changeset will have one parent,
404 maintaining a linear history. With --merge, the pending
404 maintaining a linear history. With --merge, the pending
405 changeset will instead have two parents: the old parent of the
405 changeset will instead have two parents: the old parent of the
406 working directory and a new child of REV that simply undoes REV.
406 working directory and a new child of REV that simply undoes REV.
407
407
408 Before version 1.7, the behavior without --merge was equivalent
408 Before version 1.7, the behavior without --merge was equivalent
409 to specifying --merge followed by :hg:`update --clean .` to
409 to specifying --merge followed by :hg:`update --clean .` to
410 cancel the merge and leave the child of REV as a head to be
410 cancel the merge and leave the child of REV as a head to be
411 merged separately.
411 merged separately.
412
412
413 See :hg:`help dates` for a list of formats valid for -d/--date.
413 See :hg:`help dates` for a list of formats valid for -d/--date.
414
414
415 Returns 0 on success.
415 Returns 0 on success.
416 '''
416 '''
417 if rev and node:
417 if rev and node:
418 raise util.Abort(_("please specify just one revision"))
418 raise util.Abort(_("please specify just one revision"))
419
419
420 if not rev:
420 if not rev:
421 rev = node
421 rev = node
422
422
423 if not rev:
423 if not rev:
424 raise util.Abort(_("please specify a revision to backout"))
424 raise util.Abort(_("please specify a revision to backout"))
425
425
426 date = opts.get('date')
426 date = opts.get('date')
427 if date:
427 if date:
428 opts['date'] = util.parsedate(date)
428 opts['date'] = util.parsedate(date)
429
429
430 cmdutil.bailifchanged(repo)
430 cmdutil.bailifchanged(repo)
431 node = scmutil.revsingle(repo, rev).node()
431 node = scmutil.revsingle(repo, rev).node()
432
432
433 op1, op2 = repo.dirstate.parents()
433 op1, op2 = repo.dirstate.parents()
434 a = repo.changelog.ancestor(op1, node)
434 a = repo.changelog.ancestor(op1, node)
435 if a != node:
435 if a != node:
436 raise util.Abort(_('cannot backout change on a different branch'))
436 raise util.Abort(_('cannot backout change on a different branch'))
437
437
438 p1, p2 = repo.changelog.parents(node)
438 p1, p2 = repo.changelog.parents(node)
439 if p1 == nullid:
439 if p1 == nullid:
440 raise util.Abort(_('cannot backout a change with no parents'))
440 raise util.Abort(_('cannot backout a change with no parents'))
441 if p2 != nullid:
441 if p2 != nullid:
442 if not opts.get('parent'):
442 if not opts.get('parent'):
443 raise util.Abort(_('cannot backout a merge changeset'))
443 raise util.Abort(_('cannot backout a merge changeset'))
444 p = repo.lookup(opts['parent'])
444 p = repo.lookup(opts['parent'])
445 if p not in (p1, p2):
445 if p not in (p1, p2):
446 raise util.Abort(_('%s is not a parent of %s') %
446 raise util.Abort(_('%s is not a parent of %s') %
447 (short(p), short(node)))
447 (short(p), short(node)))
448 parent = p
448 parent = p
449 else:
449 else:
450 if opts.get('parent'):
450 if opts.get('parent'):
451 raise util.Abort(_('cannot use --parent on non-merge changeset'))
451 raise util.Abort(_('cannot use --parent on non-merge changeset'))
452 parent = p1
452 parent = p1
453
453
454 # the backout should appear on the same branch
454 # the backout should appear on the same branch
455 wlock = repo.wlock()
455 wlock = repo.wlock()
456 try:
456 try:
457 branch = repo.dirstate.branch()
457 branch = repo.dirstate.branch()
458 hg.clean(repo, node, show_stats=False)
458 hg.clean(repo, node, show_stats=False)
459 repo.dirstate.setbranch(branch)
459 repo.dirstate.setbranch(branch)
460 revert_opts = opts.copy()
460 revert_opts = opts.copy()
461 revert_opts['date'] = None
461 revert_opts['date'] = None
462 revert_opts['all'] = True
462 revert_opts['all'] = True
463 revert_opts['rev'] = hex(parent)
463 revert_opts['rev'] = hex(parent)
464 revert_opts['no_backup'] = None
464 revert_opts['no_backup'] = None
465 revert(ui, repo, **revert_opts)
465 revert(ui, repo, **revert_opts)
466 if not opts.get('merge') and op1 != node:
466 if not opts.get('merge') and op1 != node:
467 try:
467 try:
468 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
468 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
469 return hg.update(repo, op1)
469 return hg.update(repo, op1)
470 finally:
470 finally:
471 ui.setconfig('ui', 'forcemerge', '')
471 ui.setconfig('ui', 'forcemerge', '')
472
472
473 commit_opts = opts.copy()
473 commit_opts = opts.copy()
474 commit_opts['addremove'] = False
474 commit_opts['addremove'] = False
475 if not commit_opts['message'] and not commit_opts['logfile']:
475 if not commit_opts['message'] and not commit_opts['logfile']:
476 # we don't translate commit messages
476 # we don't translate commit messages
477 commit_opts['message'] = "Backed out changeset %s" % short(node)
477 commit_opts['message'] = "Backed out changeset %s" % short(node)
478 commit_opts['force_editor'] = True
478 commit_opts['force_editor'] = True
479 commit(ui, repo, **commit_opts)
479 commit(ui, repo, **commit_opts)
480 def nice(node):
480 def nice(node):
481 return '%d:%s' % (repo.changelog.rev(node), short(node))
481 return '%d:%s' % (repo.changelog.rev(node), short(node))
482 ui.status(_('changeset %s backs out changeset %s\n') %
482 ui.status(_('changeset %s backs out changeset %s\n') %
483 (nice(repo.changelog.tip()), nice(node)))
483 (nice(repo.changelog.tip()), nice(node)))
484 if opts.get('merge') and op1 != node:
484 if opts.get('merge') and op1 != node:
485 hg.clean(repo, op1, show_stats=False)
485 hg.clean(repo, op1, show_stats=False)
486 ui.status(_('merging with changeset %s\n')
486 ui.status(_('merging with changeset %s\n')
487 % nice(repo.changelog.tip()))
487 % nice(repo.changelog.tip()))
488 try:
488 try:
489 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
489 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
490 return hg.merge(repo, hex(repo.changelog.tip()))
490 return hg.merge(repo, hex(repo.changelog.tip()))
491 finally:
491 finally:
492 ui.setconfig('ui', 'forcemerge', '')
492 ui.setconfig('ui', 'forcemerge', '')
493 finally:
493 finally:
494 wlock.release()
494 wlock.release()
495 return 0
495 return 0
496
496
497 @command('bisect',
497 @command('bisect',
498 [('r', 'reset', False, _('reset bisect state')),
498 [('r', 'reset', False, _('reset bisect state')),
499 ('g', 'good', False, _('mark changeset good')),
499 ('g', 'good', False, _('mark changeset good')),
500 ('b', 'bad', False, _('mark changeset bad')),
500 ('b', 'bad', False, _('mark changeset bad')),
501 ('s', 'skip', False, _('skip testing changeset')),
501 ('s', 'skip', False, _('skip testing changeset')),
502 ('e', 'extend', False, _('extend the bisect range')),
502 ('e', 'extend', False, _('extend the bisect range')),
503 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
503 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
504 ('U', 'noupdate', False, _('do not update to target'))],
504 ('U', 'noupdate', False, _('do not update to target'))],
505 _("[-gbsr] [-U] [-c CMD] [REV]"))
505 _("[-gbsr] [-U] [-c CMD] [REV]"))
506 def bisect(ui, repo, rev=None, extra=None, command=None,
506 def bisect(ui, repo, rev=None, extra=None, command=None,
507 reset=None, good=None, bad=None, skip=None, extend=None,
507 reset=None, good=None, bad=None, skip=None, extend=None,
508 noupdate=None):
508 noupdate=None):
509 """subdivision search of changesets
509 """subdivision search of changesets
510
510
511 This command helps to find changesets which introduce problems. To
511 This command helps to find changesets which introduce problems. To
512 use, mark the earliest changeset you know exhibits the problem as
512 use, mark the earliest changeset you know exhibits the problem as
513 bad, then mark the latest changeset which is free from the problem
513 bad, then mark the latest changeset which is free from the problem
514 as good. Bisect will update your working directory to a revision
514 as good. Bisect will update your working directory to a revision
515 for testing (unless the -U/--noupdate option is specified). Once
515 for testing (unless the -U/--noupdate option is specified). Once
516 you have performed tests, mark the working directory as good or
516 you have performed tests, mark the working directory as good or
517 bad, and bisect will either update to another candidate changeset
517 bad, and bisect will either update to another candidate changeset
518 or announce that it has found the bad revision.
518 or announce that it has found the bad revision.
519
519
520 As a shortcut, you can also use the revision argument to mark a
520 As a shortcut, you can also use the revision argument to mark a
521 revision as good or bad without checking it out first.
521 revision as good or bad without checking it out first.
522
522
523 If you supply a command, it will be used for automatic bisection.
523 If you supply a command, it will be used for automatic bisection.
524 The environment variable HG_NODE will contain the ID of the
524 The environment variable HG_NODE will contain the ID of the
525 changeset being tested. The exit status of the command will be
525 changeset being tested. The exit status of the command will be
526 used to mark revisions as good or bad: status 0 means good, 125
526 used to mark revisions as good or bad: status 0 means good, 125
527 means to skip the revision, 127 (command not found) will abort the
527 means to skip the revision, 127 (command not found) will abort the
528 bisection, and any other non-zero exit status means the revision
528 bisection, and any other non-zero exit status means the revision
529 is bad.
529 is bad.
530
530
531 .. container:: verbose
531 .. container:: verbose
532
532
533 Some examples:
533 Some examples:
534
534
535 - start a bisection with known bad revision 12, and good revision 34::
535 - start a bisection with known bad revision 12, and good revision 34::
536
536
537 hg bisect --bad 34
537 hg bisect --bad 34
538 hg bisect --good 12
538 hg bisect --good 12
539
539
540 - advance the current bisection by marking current revision as good or
540 - advance the current bisection by marking current revision as good or
541 bad::
541 bad::
542
542
543 hg bisect --good
543 hg bisect --good
544 hg bisect --bad
544 hg bisect --bad
545
545
546 - mark the current revision, or a known revision, to be skipped (e.g. if
546 - mark the current revision, or a known revision, to be skipped (e.g. if
547 that revision is not usable because of another issue)::
547 that revision is not usable because of another issue)::
548
548
549 hg bisect --skip
549 hg bisect --skip
550 hg bisect --skip 23
550 hg bisect --skip 23
551
551
552 - forget the current bisection::
552 - forget the current bisection::
553
553
554 hg bisect --reset
554 hg bisect --reset
555
555
556 - use 'make && make tests' to automatically find the first broken
556 - use 'make && make tests' to automatically find the first broken
557 revision::
557 revision::
558
558
559 hg bisect --reset
559 hg bisect --reset
560 hg bisect --bad 34
560 hg bisect --bad 34
561 hg bisect --good 12
561 hg bisect --good 12
562 hg bisect --command 'make && make tests'
562 hg bisect --command 'make && make tests'
563
563
564 - see all changesets whose states are already known in the current
564 - see all changesets whose states are already known in the current
565 bisection::
565 bisection::
566
566
567 hg log -r "bisect(pruned)"
567 hg log -r "bisect(pruned)"
568
568
569 - see the changeset currently being bisected (especially useful
569 - see the changeset currently being bisected (especially useful
570 if running with -U/--noupdate)::
570 if running with -U/--noupdate)::
571
571
572 hg log -r "bisect(current)"
572 hg log -r "bisect(current)"
573
573
574 - see all changesets that took part in the current bisection::
574 - see all changesets that took part in the current bisection::
575
575
576 hg log -r "bisect(range)"
576 hg log -r "bisect(range)"
577
577
578 - with the graphlog extension, you can even get a nice graph::
578 - with the graphlog extension, you can even get a nice graph::
579
579
580 hg log --graph -r "bisect(range)"
580 hg log --graph -r "bisect(range)"
581
581
582 See :hg:`help revsets` for more about the `bisect()` keyword.
582 See :hg:`help revsets` for more about the `bisect()` keyword.
583
583
584 Returns 0 on success.
584 Returns 0 on success.
585 """
585 """
586 def extendbisectrange(nodes, good):
586 def extendbisectrange(nodes, good):
587 # bisect is incomplete when it ends on a merge node and
587 # bisect is incomplete when it ends on a merge node and
588 # one of the parent was not checked.
588 # one of the parent was not checked.
589 parents = repo[nodes[0]].parents()
589 parents = repo[nodes[0]].parents()
590 if len(parents) > 1:
590 if len(parents) > 1:
591 side = good and state['bad'] or state['good']
591 side = good and state['bad'] or state['good']
592 num = len(set(i.node() for i in parents) & set(side))
592 num = len(set(i.node() for i in parents) & set(side))
593 if num == 1:
593 if num == 1:
594 return parents[0].ancestor(parents[1])
594 return parents[0].ancestor(parents[1])
595 return None
595 return None
596
596
597 def print_result(nodes, good):
597 def print_result(nodes, good):
598 displayer = cmdutil.show_changeset(ui, repo, {})
598 displayer = cmdutil.show_changeset(ui, repo, {})
599 if len(nodes) == 1:
599 if len(nodes) == 1:
600 # narrowed it down to a single revision
600 # narrowed it down to a single revision
601 if good:
601 if good:
602 ui.write(_("The first good revision is:\n"))
602 ui.write(_("The first good revision is:\n"))
603 else:
603 else:
604 ui.write(_("The first bad revision is:\n"))
604 ui.write(_("The first bad revision is:\n"))
605 displayer.show(repo[nodes[0]])
605 displayer.show(repo[nodes[0]])
606 extendnode = extendbisectrange(nodes, good)
606 extendnode = extendbisectrange(nodes, good)
607 if extendnode is not None:
607 if extendnode is not None:
608 ui.write(_('Not all ancestors of this changeset have been'
608 ui.write(_('Not all ancestors of this changeset have been'
609 ' checked.\nUse bisect --extend to continue the '
609 ' checked.\nUse bisect --extend to continue the '
610 'bisection from\nthe common ancestor, %s.\n')
610 'bisection from\nthe common ancestor, %s.\n')
611 % extendnode)
611 % extendnode)
612 else:
612 else:
613 # multiple possible revisions
613 # multiple possible revisions
614 if good:
614 if good:
615 ui.write(_("Due to skipped revisions, the first "
615 ui.write(_("Due to skipped revisions, the first "
616 "good revision could be any of:\n"))
616 "good revision could be any of:\n"))
617 else:
617 else:
618 ui.write(_("Due to skipped revisions, the first "
618 ui.write(_("Due to skipped revisions, the first "
619 "bad revision could be any of:\n"))
619 "bad revision could be any of:\n"))
620 for n in nodes:
620 for n in nodes:
621 displayer.show(repo[n])
621 displayer.show(repo[n])
622 displayer.close()
622 displayer.close()
623
623
624 def check_state(state, interactive=True):
624 def check_state(state, interactive=True):
625 if not state['good'] or not state['bad']:
625 if not state['good'] or not state['bad']:
626 if (good or bad or skip or reset) and interactive:
626 if (good or bad or skip or reset) and interactive:
627 return
627 return
628 if not state['good']:
628 if not state['good']:
629 raise util.Abort(_('cannot bisect (no known good revisions)'))
629 raise util.Abort(_('cannot bisect (no known good revisions)'))
630 else:
630 else:
631 raise util.Abort(_('cannot bisect (no known bad revisions)'))
631 raise util.Abort(_('cannot bisect (no known bad revisions)'))
632 return True
632 return True
633
633
634 # backward compatibility
634 # backward compatibility
635 if rev in "good bad reset init".split():
635 if rev in "good bad reset init".split():
636 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
636 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
637 cmd, rev, extra = rev, extra, None
637 cmd, rev, extra = rev, extra, None
638 if cmd == "good":
638 if cmd == "good":
639 good = True
639 good = True
640 elif cmd == "bad":
640 elif cmd == "bad":
641 bad = True
641 bad = True
642 else:
642 else:
643 reset = True
643 reset = True
644 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
644 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
645 raise util.Abort(_('incompatible arguments'))
645 raise util.Abort(_('incompatible arguments'))
646
646
647 if reset:
647 if reset:
648 p = repo.join("bisect.state")
648 p = repo.join("bisect.state")
649 if os.path.exists(p):
649 if os.path.exists(p):
650 os.unlink(p)
650 os.unlink(p)
651 return
651 return
652
652
653 state = hbisect.load_state(repo)
653 state = hbisect.load_state(repo)
654
654
655 if command:
655 if command:
656 changesets = 1
656 changesets = 1
657 try:
657 try:
658 node = state['current'][0]
658 node = state['current'][0]
659 except LookupError:
659 except LookupError:
660 if noupdate:
660 if noupdate:
661 raise util.Abort(_('current bisect revision is unknown - '
661 raise util.Abort(_('current bisect revision is unknown - '
662 'start a new bisect to fix'))
662 'start a new bisect to fix'))
663 node, p2 = repo.dirstate.parents()
663 node, p2 = repo.dirstate.parents()
664 if p2 != nullid:
664 if p2 != nullid:
665 raise util.Abort(_('current bisect revision is a merge'))
665 raise util.Abort(_('current bisect revision is a merge'))
666 try:
666 try:
667 while changesets:
667 while changesets:
668 # update state
668 # update state
669 state['current'] = [node]
669 state['current'] = [node]
670 hbisect.save_state(repo, state)
670 hbisect.save_state(repo, state)
671 status = util.system(command,
671 status = util.system(command,
672 environ={'HG_NODE': hex(node)},
672 environ={'HG_NODE': hex(node)},
673 out=ui.fout)
673 out=ui.fout)
674 if status == 125:
674 if status == 125:
675 transition = "skip"
675 transition = "skip"
676 elif status == 0:
676 elif status == 0:
677 transition = "good"
677 transition = "good"
678 # status < 0 means process was killed
678 # status < 0 means process was killed
679 elif status == 127:
679 elif status == 127:
680 raise util.Abort(_("failed to execute %s") % command)
680 raise util.Abort(_("failed to execute %s") % command)
681 elif status < 0:
681 elif status < 0:
682 raise util.Abort(_("%s killed") % command)
682 raise util.Abort(_("%s killed") % command)
683 else:
683 else:
684 transition = "bad"
684 transition = "bad"
685 ctx = scmutil.revsingle(repo, rev, node)
685 ctx = scmutil.revsingle(repo, rev, node)
686 rev = None # clear for future iterations
686 rev = None # clear for future iterations
687 state[transition].append(ctx.node())
687 state[transition].append(ctx.node())
688 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
688 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
689 check_state(state, interactive=False)
689 check_state(state, interactive=False)
690 # bisect
690 # bisect
691 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
691 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
692 # update to next check
692 # update to next check
693 node = nodes[0]
693 node = nodes[0]
694 if not noupdate:
694 if not noupdate:
695 cmdutil.bailifchanged(repo)
695 cmdutil.bailifchanged(repo)
696 hg.clean(repo, node, show_stats=False)
696 hg.clean(repo, node, show_stats=False)
697 finally:
697 finally:
698 state['current'] = [node]
698 state['current'] = [node]
699 hbisect.save_state(repo, state)
699 hbisect.save_state(repo, state)
700 print_result(nodes, good)
700 print_result(nodes, good)
701 return
701 return
702
702
703 # update state
703 # update state
704
704
705 if rev:
705 if rev:
706 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
706 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
707 else:
707 else:
708 nodes = [repo.lookup('.')]
708 nodes = [repo.lookup('.')]
709
709
710 if good or bad or skip:
710 if good or bad or skip:
711 if good:
711 if good:
712 state['good'] += nodes
712 state['good'] += nodes
713 elif bad:
713 elif bad:
714 state['bad'] += nodes
714 state['bad'] += nodes
715 elif skip:
715 elif skip:
716 state['skip'] += nodes
716 state['skip'] += nodes
717 hbisect.save_state(repo, state)
717 hbisect.save_state(repo, state)
718
718
719 if not check_state(state):
719 if not check_state(state):
720 return
720 return
721
721
722 # actually bisect
722 # actually bisect
723 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
723 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
724 if extend:
724 if extend:
725 if not changesets:
725 if not changesets:
726 extendnode = extendbisectrange(nodes, good)
726 extendnode = extendbisectrange(nodes, good)
727 if extendnode is not None:
727 if extendnode is not None:
728 ui.write(_("Extending search to changeset %d:%s\n"
728 ui.write(_("Extending search to changeset %d:%s\n"
729 % (extendnode.rev(), extendnode)))
729 % (extendnode.rev(), extendnode)))
730 state['current'] = [extendnode.node()]
730 state['current'] = [extendnode.node()]
731 hbisect.save_state(repo, state)
731 hbisect.save_state(repo, state)
732 if noupdate:
732 if noupdate:
733 return
733 return
734 cmdutil.bailifchanged(repo)
734 cmdutil.bailifchanged(repo)
735 return hg.clean(repo, extendnode.node())
735 return hg.clean(repo, extendnode.node())
736 raise util.Abort(_("nothing to extend"))
736 raise util.Abort(_("nothing to extend"))
737
737
738 if changesets == 0:
738 if changesets == 0:
739 print_result(nodes, good)
739 print_result(nodes, good)
740 else:
740 else:
741 assert len(nodes) == 1 # only a single node can be tested next
741 assert len(nodes) == 1 # only a single node can be tested next
742 node = nodes[0]
742 node = nodes[0]
743 # compute the approximate number of remaining tests
743 # compute the approximate number of remaining tests
744 tests, size = 0, 2
744 tests, size = 0, 2
745 while size <= changesets:
745 while size <= changesets:
746 tests, size = tests + 1, size * 2
746 tests, size = tests + 1, size * 2
747 rev = repo.changelog.rev(node)
747 rev = repo.changelog.rev(node)
748 ui.write(_("Testing changeset %d:%s "
748 ui.write(_("Testing changeset %d:%s "
749 "(%d changesets remaining, ~%d tests)\n")
749 "(%d changesets remaining, ~%d tests)\n")
750 % (rev, short(node), changesets, tests))
750 % (rev, short(node), changesets, tests))
751 state['current'] = [node]
751 state['current'] = [node]
752 hbisect.save_state(repo, state)
752 hbisect.save_state(repo, state)
753 if not noupdate:
753 if not noupdate:
754 cmdutil.bailifchanged(repo)
754 cmdutil.bailifchanged(repo)
755 return hg.clean(repo, node)
755 return hg.clean(repo, node)
756
756
757 @command('bookmarks',
757 @command('bookmarks',
758 [('f', 'force', False, _('force')),
758 [('f', 'force', False, _('force')),
759 ('r', 'rev', '', _('revision'), _('REV')),
759 ('r', 'rev', '', _('revision'), _('REV')),
760 ('d', 'delete', False, _('delete a given bookmark')),
760 ('d', 'delete', False, _('delete a given bookmark')),
761 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
761 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
762 ('i', 'inactive', False, _('mark a bookmark inactive'))],
762 ('i', 'inactive', False, _('mark a bookmark inactive'))],
763 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
763 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
764 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
764 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
765 rename=None, inactive=False):
765 rename=None, inactive=False):
766 '''track a line of development with movable markers
766 '''track a line of development with movable markers
767
767
768 Bookmarks are pointers to certain commits that move when committing.
768 Bookmarks are pointers to certain commits that move when committing.
769 Bookmarks are local. They can be renamed, copied and deleted. It is
769 Bookmarks are local. They can be renamed, copied and deleted. It is
770 possible to use :hg:`merge NAME` to merge from a given bookmark, and
770 possible to use :hg:`merge NAME` to merge from a given bookmark, and
771 :hg:`update NAME` to update to a given bookmark.
771 :hg:`update NAME` to update to a given bookmark.
772
772
773 You can use :hg:`bookmark NAME` to set a bookmark on the working
773 You can use :hg:`bookmark NAME` to set a bookmark on the working
774 directory's parent revision with the given name. If you specify
774 directory's parent revision with the given name. If you specify
775 a revision using -r REV (where REV may be an existing bookmark),
775 a revision using -r REV (where REV may be an existing bookmark),
776 the bookmark is assigned to that revision.
776 the bookmark is assigned to that revision.
777
777
778 Bookmarks can be pushed and pulled between repositories (see :hg:`help
778 Bookmarks can be pushed and pulled between repositories (see :hg:`help
779 push` and :hg:`help pull`). This requires both the local and remote
779 push` and :hg:`help pull`). This requires both the local and remote
780 repositories to support bookmarks. For versions prior to 1.8, this means
780 repositories to support bookmarks. For versions prior to 1.8, this means
781 the bookmarks extension must be enabled.
781 the bookmarks extension must be enabled.
782
782
783 With -i/--inactive, the new bookmark will not be made the active
783 With -i/--inactive, the new bookmark will not be made the active
784 bookmark. If -r/--rev is given, the new bookmark will not be made
784 bookmark. If -r/--rev is given, the new bookmark will not be made
785 active even if -i/--inactive is not given. If no NAME is given, the
785 active even if -i/--inactive is not given. If no NAME is given, the
786 current active bookmark will be marked inactive.
786 current active bookmark will be marked inactive.
787 '''
787 '''
788 hexfn = ui.debugflag and hex or short
788 hexfn = ui.debugflag and hex or short
789 marks = repo._bookmarks
789 marks = repo._bookmarks
790 cur = repo.changectx('.').node()
790 cur = repo.changectx('.').node()
791
791
792 if delete:
792 if delete:
793 if mark is None:
793 if mark is None:
794 raise util.Abort(_("bookmark name required"))
794 raise util.Abort(_("bookmark name required"))
795 if mark not in marks:
795 if mark not in marks:
796 raise util.Abort(_("bookmark '%s' does not exist") % mark)
796 raise util.Abort(_("bookmark '%s' does not exist") % mark)
797 if mark == repo._bookmarkcurrent:
797 if mark == repo._bookmarkcurrent:
798 bookmarks.setcurrent(repo, None)
798 bookmarks.setcurrent(repo, None)
799 del marks[mark]
799 del marks[mark]
800 bookmarks.write(repo)
800 bookmarks.write(repo)
801 return
801 return
802
802
803 if rename:
803 if rename:
804 if rename not in marks:
804 if rename not in marks:
805 raise util.Abort(_("bookmark '%s' does not exist") % rename)
805 raise util.Abort(_("bookmark '%s' does not exist") % rename)
806 if mark in marks and not force:
806 if mark in marks and not force:
807 raise util.Abort(_("bookmark '%s' already exists "
807 raise util.Abort(_("bookmark '%s' already exists "
808 "(use -f to force)") % mark)
808 "(use -f to force)") % mark)
809 if mark is None:
809 if mark is None:
810 raise util.Abort(_("new bookmark name required"))
810 raise util.Abort(_("new bookmark name required"))
811 marks[mark] = marks[rename]
811 marks[mark] = marks[rename]
812 if repo._bookmarkcurrent == rename and not inactive:
812 if repo._bookmarkcurrent == rename and not inactive:
813 bookmarks.setcurrent(repo, mark)
813 bookmarks.setcurrent(repo, mark)
814 del marks[rename]
814 del marks[rename]
815 bookmarks.write(repo)
815 bookmarks.write(repo)
816 return
816 return
817
817
818 if mark is not None:
818 if mark is not None:
819 if "\n" in mark:
819 if "\n" in mark:
820 raise util.Abort(_("bookmark name cannot contain newlines"))
820 raise util.Abort(_("bookmark name cannot contain newlines"))
821 mark = mark.strip()
821 mark = mark.strip()
822 if not mark:
822 if not mark:
823 raise util.Abort(_("bookmark names cannot consist entirely of "
823 raise util.Abort(_("bookmark names cannot consist entirely of "
824 "whitespace"))
824 "whitespace"))
825 if inactive and mark == repo._bookmarkcurrent:
825 if inactive and mark == repo._bookmarkcurrent:
826 bookmarks.setcurrent(repo, None)
826 bookmarks.setcurrent(repo, None)
827 return
827 return
828 if mark in marks and not force:
828 if mark in marks and not force:
829 raise util.Abort(_("bookmark '%s' already exists "
829 raise util.Abort(_("bookmark '%s' already exists "
830 "(use -f to force)") % mark)
830 "(use -f to force)") % mark)
831 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
831 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
832 and not force):
832 and not force):
833 raise util.Abort(
833 raise util.Abort(
834 _("a bookmark cannot have the name of an existing branch"))
834 _("a bookmark cannot have the name of an existing branch"))
835 if rev:
835 if rev:
836 marks[mark] = repo.lookup(rev)
836 marks[mark] = repo.lookup(rev)
837 else:
837 else:
838 marks[mark] = cur
838 marks[mark] = cur
839 if not inactive and cur == marks[mark]:
839 if not inactive and cur == marks[mark]:
840 bookmarks.setcurrent(repo, mark)
840 bookmarks.setcurrent(repo, mark)
841 bookmarks.write(repo)
841 bookmarks.write(repo)
842 return
842 return
843
843
844 if mark is None:
844 if mark is None:
845 if rev:
845 if rev:
846 raise util.Abort(_("bookmark name required"))
846 raise util.Abort(_("bookmark name required"))
847 if len(marks) == 0:
847 if len(marks) == 0:
848 ui.status(_("no bookmarks set\n"))
848 ui.status(_("no bookmarks set\n"))
849 else:
849 else:
850 for bmark, n in sorted(marks.iteritems()):
850 for bmark, n in sorted(marks.iteritems()):
851 current = repo._bookmarkcurrent
851 current = repo._bookmarkcurrent
852 if bmark == current and n == cur:
852 if bmark == current and n == cur:
853 prefix, label = '*', 'bookmarks.current'
853 prefix, label = '*', 'bookmarks.current'
854 else:
854 else:
855 prefix, label = ' ', ''
855 prefix, label = ' ', ''
856
856
857 if ui.quiet:
857 if ui.quiet:
858 ui.write("%s\n" % bmark, label=label)
858 ui.write("%s\n" % bmark, label=label)
859 else:
859 else:
860 ui.write(" %s %-25s %d:%s\n" % (
860 ui.write(" %s %-25s %d:%s\n" % (
861 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
861 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
862 label=label)
862 label=label)
863 return
863 return
864
864
865 @command('branch',
865 @command('branch',
866 [('f', 'force', None,
866 [('f', 'force', None,
867 _('set branch name even if it shadows an existing branch')),
867 _('set branch name even if it shadows an existing branch')),
868 ('C', 'clean', None, _('reset branch name to parent branch name'))],
868 ('C', 'clean', None, _('reset branch name to parent branch name'))],
869 _('[-fC] [NAME]'))
869 _('[-fC] [NAME]'))
870 def branch(ui, repo, label=None, **opts):
870 def branch(ui, repo, label=None, **opts):
871 """set or show the current branch name
871 """set or show the current branch name
872
872
873 .. note::
873 .. note::
874 Branch names are permanent and global. Use :hg:`bookmark` to create a
874 Branch names are permanent and global. Use :hg:`bookmark` to create a
875 light-weight bookmark instead. See :hg:`help glossary` for more
875 light-weight bookmark instead. See :hg:`help glossary` for more
876 information about named branches and bookmarks.
876 information about named branches and bookmarks.
877
877
878 With no argument, show the current branch name. With one argument,
878 With no argument, show the current branch name. With one argument,
879 set the working directory branch name (the branch will not exist
879 set the working directory branch name (the branch will not exist
880 in the repository until the next commit). Standard practice
880 in the repository until the next commit). Standard practice
881 recommends that primary development take place on the 'default'
881 recommends that primary development take place on the 'default'
882 branch.
882 branch.
883
883
884 Unless -f/--force is specified, branch will not let you set a
884 Unless -f/--force is specified, branch will not let you set a
885 branch name that already exists, even if it's inactive.
885 branch name that already exists, even if it's inactive.
886
886
887 Use -C/--clean to reset the working directory branch to that of
887 Use -C/--clean to reset the working directory branch to that of
888 the parent of the working directory, negating a previous branch
888 the parent of the working directory, negating a previous branch
889 change.
889 change.
890
890
891 Use the command :hg:`update` to switch to an existing branch. Use
891 Use the command :hg:`update` to switch to an existing branch. Use
892 :hg:`commit --close-branch` to mark this branch as closed.
892 :hg:`commit --close-branch` to mark this branch as closed.
893
893
894 Returns 0 on success.
894 Returns 0 on success.
895 """
895 """
896 if not opts.get('clean') and not label:
896 if not opts.get('clean') and not label:
897 ui.write("%s\n" % repo.dirstate.branch())
897 ui.write("%s\n" % repo.dirstate.branch())
898 return
898 return
899
899
900 wlock = repo.wlock()
900 wlock = repo.wlock()
901 try:
901 try:
902 if opts.get('clean'):
902 if opts.get('clean'):
903 label = repo[None].p1().branch()
903 label = repo[None].p1().branch()
904 repo.dirstate.setbranch(label)
904 repo.dirstate.setbranch(label)
905 ui.status(_('reset working directory to branch %s\n') % label)
905 ui.status(_('reset working directory to branch %s\n') % label)
906 elif label:
906 elif label:
907 if not opts.get('force') and label in repo.branchmap():
907 if not opts.get('force') and label in repo.branchmap():
908 if label not in [p.branch() for p in repo.parents()]:
908 if label not in [p.branch() for p in repo.parents()]:
909 raise util.Abort(_('a branch of the same name already'
909 raise util.Abort(_('a branch of the same name already'
910 ' exists'),
910 ' exists'),
911 # i18n: "it" refers to an existing branch
911 # i18n: "it" refers to an existing branch
912 hint=_("use 'hg update' to switch to it"))
912 hint=_("use 'hg update' to switch to it"))
913 repo.dirstate.setbranch(label)
913 repo.dirstate.setbranch(label)
914 ui.status(_('marked working directory as branch %s\n') % label)
914 ui.status(_('marked working directory as branch %s\n') % label)
915 ui.status(_('(branches are permanent and global, '
915 ui.status(_('(branches are permanent and global, '
916 'did you want a bookmark?)\n'))
916 'did you want a bookmark?)\n'))
917 finally:
917 finally:
918 wlock.release()
918 wlock.release()
919
919
920 @command('branches',
920 @command('branches',
921 [('a', 'active', False, _('show only branches that have unmerged heads')),
921 [('a', 'active', False, _('show only branches that have unmerged heads')),
922 ('c', 'closed', False, _('show normal and closed branches'))],
922 ('c', 'closed', False, _('show normal and closed branches'))],
923 _('[-ac]'))
923 _('[-ac]'))
924 def branches(ui, repo, active=False, closed=False):
924 def branches(ui, repo, active=False, closed=False):
925 """list repository named branches
925 """list repository named branches
926
926
927 List the repository's named branches, indicating which ones are
927 List the repository's named branches, indicating which ones are
928 inactive. If -c/--closed is specified, also list branches which have
928 inactive. If -c/--closed is specified, also list branches which have
929 been marked closed (see :hg:`commit --close-branch`).
929 been marked closed (see :hg:`commit --close-branch`).
930
930
931 If -a/--active is specified, only show active branches. A branch
931 If -a/--active is specified, only show active branches. A branch
932 is considered active if it contains repository heads.
932 is considered active if it contains repository heads.
933
933
934 Use the command :hg:`update` to switch to an existing branch.
934 Use the command :hg:`update` to switch to an existing branch.
935
935
936 Returns 0.
936 Returns 0.
937 """
937 """
938
938
939 hexfunc = ui.debugflag and hex or short
939 hexfunc = ui.debugflag and hex or short
940
940
941 activebranches = set([repo[n].branch() for n in repo.heads()])
941 activebranches = set([repo[n].branch() for n in repo.heads()])
942 branches = []
942 branches = []
943 for tag, heads in repo.branchmap().iteritems():
943 for tag, heads in repo.branchmap().iteritems():
944 for h in reversed(heads):
944 for h in reversed(heads):
945 ctx = repo[h]
945 ctx = repo[h]
946 isopen = not ctx.closesbranch()
946 isopen = not ctx.closesbranch()
947 if isopen:
947 if isopen:
948 tip = ctx
948 tip = ctx
949 break
949 break
950 else:
950 else:
951 tip = repo[heads[-1]]
951 tip = repo[heads[-1]]
952 isactive = tag in activebranches and isopen
952 isactive = tag in activebranches and isopen
953 branches.append((tip, isactive, isopen))
953 branches.append((tip, isactive, isopen))
954 branches.sort(key=lambda i: (i[1], i[0].rev(), i[0].branch(), i[2]),
954 branches.sort(key=lambda i: (i[1], i[0].rev(), i[0].branch(), i[2]),
955 reverse=True)
955 reverse=True)
956
956
957 for ctx, isactive, isopen in branches:
957 for ctx, isactive, isopen in branches:
958 if (not active) or isactive:
958 if (not active) or isactive:
959 if isactive:
959 if isactive:
960 label = 'branches.active'
960 label = 'branches.active'
961 notice = ''
961 notice = ''
962 elif not isopen:
962 elif not isopen:
963 if not closed:
963 if not closed:
964 continue
964 continue
965 label = 'branches.closed'
965 label = 'branches.closed'
966 notice = _(' (closed)')
966 notice = _(' (closed)')
967 else:
967 else:
968 label = 'branches.inactive'
968 label = 'branches.inactive'
969 notice = _(' (inactive)')
969 notice = _(' (inactive)')
970 if ctx.branch() == repo.dirstate.branch():
970 if ctx.branch() == repo.dirstate.branch():
971 label = 'branches.current'
971 label = 'branches.current'
972 rev = str(ctx.rev()).rjust(31 - encoding.colwidth(ctx.branch()))
972 rev = str(ctx.rev()).rjust(31 - encoding.colwidth(ctx.branch()))
973 rev = ui.label('%s:%s' % (rev, hexfunc(ctx.node())),
973 rev = ui.label('%s:%s' % (rev, hexfunc(ctx.node())),
974 'log.changeset')
974 'log.changeset')
975 tag = ui.label(ctx.branch(), label)
975 tag = ui.label(ctx.branch(), label)
976 if ui.quiet:
976 if ui.quiet:
977 ui.write("%s\n" % tag)
977 ui.write("%s\n" % tag)
978 else:
978 else:
979 ui.write("%s %s%s\n" % (tag, rev, notice))
979 ui.write("%s %s%s\n" % (tag, rev, notice))
980
980
981 @command('bundle',
981 @command('bundle',
982 [('f', 'force', None, _('run even when the destination is unrelated')),
982 [('f', 'force', None, _('run even when the destination is unrelated')),
983 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
983 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
984 _('REV')),
984 _('REV')),
985 ('b', 'branch', [], _('a specific branch you would like to bundle'),
985 ('b', 'branch', [], _('a specific branch you would like to bundle'),
986 _('BRANCH')),
986 _('BRANCH')),
987 ('', 'base', [],
987 ('', 'base', [],
988 _('a base changeset assumed to be available at the destination'),
988 _('a base changeset assumed to be available at the destination'),
989 _('REV')),
989 _('REV')),
990 ('a', 'all', None, _('bundle all changesets in the repository')),
990 ('a', 'all', None, _('bundle all changesets in the repository')),
991 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
991 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
992 ] + remoteopts,
992 ] + remoteopts,
993 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
993 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
994 def bundle(ui, repo, fname, dest=None, **opts):
994 def bundle(ui, repo, fname, dest=None, **opts):
995 """create a changegroup file
995 """create a changegroup file
996
996
997 Generate a compressed changegroup file collecting changesets not
997 Generate a compressed changegroup file collecting changesets not
998 known to be in another repository.
998 known to be in another repository.
999
999
1000 If you omit the destination repository, then hg assumes the
1000 If you omit the destination repository, then hg assumes the
1001 destination will have all the nodes you specify with --base
1001 destination will have all the nodes you specify with --base
1002 parameters. To create a bundle containing all changesets, use
1002 parameters. To create a bundle containing all changesets, use
1003 -a/--all (or --base null).
1003 -a/--all (or --base null).
1004
1004
1005 You can change compression method with the -t/--type option.
1005 You can change compression method with the -t/--type option.
1006 The available compression methods are: none, bzip2, and
1006 The available compression methods are: none, bzip2, and
1007 gzip (by default, bundles are compressed using bzip2).
1007 gzip (by default, bundles are compressed using bzip2).
1008
1008
1009 The bundle file can then be transferred using conventional means
1009 The bundle file can then be transferred using conventional means
1010 and applied to another repository with the unbundle or pull
1010 and applied to another repository with the unbundle or pull
1011 command. This is useful when direct push and pull are not
1011 command. This is useful when direct push and pull are not
1012 available or when exporting an entire repository is undesirable.
1012 available or when exporting an entire repository is undesirable.
1013
1013
1014 Applying bundles preserves all changeset contents including
1014 Applying bundles preserves all changeset contents including
1015 permissions, copy/rename information, and revision history.
1015 permissions, copy/rename information, and revision history.
1016
1016
1017 Returns 0 on success, 1 if no changes found.
1017 Returns 0 on success, 1 if no changes found.
1018 """
1018 """
1019 revs = None
1019 revs = None
1020 if 'rev' in opts:
1020 if 'rev' in opts:
1021 revs = scmutil.revrange(repo, opts['rev'])
1021 revs = scmutil.revrange(repo, opts['rev'])
1022
1022
1023 bundletype = opts.get('type', 'bzip2').lower()
1023 bundletype = opts.get('type', 'bzip2').lower()
1024 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1024 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1025 bundletype = btypes.get(bundletype)
1025 bundletype = btypes.get(bundletype)
1026 if bundletype not in changegroup.bundletypes:
1026 if bundletype not in changegroup.bundletypes:
1027 raise util.Abort(_('unknown bundle type specified with --type'))
1027 raise util.Abort(_('unknown bundle type specified with --type'))
1028
1028
1029 if opts.get('all'):
1029 if opts.get('all'):
1030 base = ['null']
1030 base = ['null']
1031 else:
1031 else:
1032 base = scmutil.revrange(repo, opts.get('base'))
1032 base = scmutil.revrange(repo, opts.get('base'))
1033 if base:
1033 if base:
1034 if dest:
1034 if dest:
1035 raise util.Abort(_("--base is incompatible with specifying "
1035 raise util.Abort(_("--base is incompatible with specifying "
1036 "a destination"))
1036 "a destination"))
1037 common = [repo.lookup(rev) for rev in base]
1037 common = [repo.lookup(rev) for rev in base]
1038 heads = revs and map(repo.lookup, revs) or revs
1038 heads = revs and map(repo.lookup, revs) or revs
1039 cg = repo.getbundle('bundle', heads=heads, common=common)
1039 cg = repo.getbundle('bundle', heads=heads, common=common)
1040 outgoing = None
1040 outgoing = None
1041 else:
1041 else:
1042 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1042 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1043 dest, branches = hg.parseurl(dest, opts.get('branch'))
1043 dest, branches = hg.parseurl(dest, opts.get('branch'))
1044 other = hg.peer(repo, opts, dest)
1044 other = hg.peer(repo, opts, dest)
1045 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
1045 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
1046 heads = revs and map(repo.lookup, revs) or revs
1046 heads = revs and map(repo.lookup, revs) or revs
1047 outgoing = discovery.findcommonoutgoing(repo, other,
1047 outgoing = discovery.findcommonoutgoing(repo, other,
1048 onlyheads=heads,
1048 onlyheads=heads,
1049 force=opts.get('force'),
1049 force=opts.get('force'),
1050 portable=True)
1050 portable=True)
1051 cg = repo.getlocalbundle('bundle', outgoing)
1051 cg = repo.getlocalbundle('bundle', outgoing)
1052 if not cg:
1052 if not cg:
1053 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1053 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1054 return 1
1054 return 1
1055
1055
1056 changegroup.writebundle(cg, fname, bundletype)
1056 changegroup.writebundle(cg, fname, bundletype)
1057
1057
1058 @command('cat',
1058 @command('cat',
1059 [('o', 'output', '',
1059 [('o', 'output', '',
1060 _('print output to file with formatted name'), _('FORMAT')),
1060 _('print output to file with formatted name'), _('FORMAT')),
1061 ('r', 'rev', '', _('print the given revision'), _('REV')),
1061 ('r', 'rev', '', _('print the given revision'), _('REV')),
1062 ('', 'decode', None, _('apply any matching decode filter')),
1062 ('', 'decode', None, _('apply any matching decode filter')),
1063 ] + walkopts,
1063 ] + walkopts,
1064 _('[OPTION]... FILE...'))
1064 _('[OPTION]... FILE...'))
1065 def cat(ui, repo, file1, *pats, **opts):
1065 def cat(ui, repo, file1, *pats, **opts):
1066 """output the current or given revision of files
1066 """output the current or given revision of files
1067
1067
1068 Print the specified files as they were at the given revision. If
1068 Print the specified files as they were at the given revision. If
1069 no revision is given, the parent of the working directory is used,
1069 no revision is given, the parent of the working directory is used,
1070 or tip if no revision is checked out.
1070 or tip if no revision is checked out.
1071
1071
1072 Output may be to a file, in which case the name of the file is
1072 Output may be to a file, in which case the name of the file is
1073 given using a format string. The formatting rules are the same as
1073 given using a format string. The formatting rules are the same as
1074 for the export command, with the following additions:
1074 for the export command, with the following additions:
1075
1075
1076 :``%s``: basename of file being printed
1076 :``%s``: basename of file being printed
1077 :``%d``: dirname of file being printed, or '.' if in repository root
1077 :``%d``: dirname of file being printed, or '.' if in repository root
1078 :``%p``: root-relative path name of file being printed
1078 :``%p``: root-relative path name of file being printed
1079
1079
1080 Returns 0 on success.
1080 Returns 0 on success.
1081 """
1081 """
1082 ctx = scmutil.revsingle(repo, opts.get('rev'))
1082 ctx = scmutil.revsingle(repo, opts.get('rev'))
1083 err = 1
1083 err = 1
1084 m = scmutil.match(ctx, (file1,) + pats, opts)
1084 m = scmutil.match(ctx, (file1,) + pats, opts)
1085 for abs in ctx.walk(m):
1085 for abs in ctx.walk(m):
1086 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1086 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1087 pathname=abs)
1087 pathname=abs)
1088 data = ctx[abs].data()
1088 data = ctx[abs].data()
1089 if opts.get('decode'):
1089 if opts.get('decode'):
1090 data = repo.wwritedata(abs, data)
1090 data = repo.wwritedata(abs, data)
1091 fp.write(data)
1091 fp.write(data)
1092 fp.close()
1092 fp.close()
1093 err = 0
1093 err = 0
1094 return err
1094 return err
1095
1095
1096 @command('^clone',
1096 @command('^clone',
1097 [('U', 'noupdate', None,
1097 [('U', 'noupdate', None,
1098 _('the clone will include an empty working copy (only a repository)')),
1098 _('the clone will include an empty working copy (only a repository)')),
1099 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1099 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1100 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1100 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1101 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1101 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1102 ('', 'pull', None, _('use pull protocol to copy metadata')),
1102 ('', 'pull', None, _('use pull protocol to copy metadata')),
1103 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1103 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1104 ] + remoteopts,
1104 ] + remoteopts,
1105 _('[OPTION]... SOURCE [DEST]'))
1105 _('[OPTION]... SOURCE [DEST]'))
1106 def clone(ui, source, dest=None, **opts):
1106 def clone(ui, source, dest=None, **opts):
1107 """make a copy of an existing repository
1107 """make a copy of an existing repository
1108
1108
1109 Create a copy of an existing repository in a new directory.
1109 Create a copy of an existing repository in a new directory.
1110
1110
1111 If no destination directory name is specified, it defaults to the
1111 If no destination directory name is specified, it defaults to the
1112 basename of the source.
1112 basename of the source.
1113
1113
1114 The location of the source is added to the new repository's
1114 The location of the source is added to the new repository's
1115 ``.hg/hgrc`` file, as the default to be used for future pulls.
1115 ``.hg/hgrc`` file, as the default to be used for future pulls.
1116
1116
1117 Only local paths and ``ssh://`` URLs are supported as
1117 Only local paths and ``ssh://`` URLs are supported as
1118 destinations. For ``ssh://`` destinations, no working directory or
1118 destinations. For ``ssh://`` destinations, no working directory or
1119 ``.hg/hgrc`` will be created on the remote side.
1119 ``.hg/hgrc`` will be created on the remote side.
1120
1120
1121 To pull only a subset of changesets, specify one or more revisions
1121 To pull only a subset of changesets, specify one or more revisions
1122 identifiers with -r/--rev or branches with -b/--branch. The
1122 identifiers with -r/--rev or branches with -b/--branch. The
1123 resulting clone will contain only the specified changesets and
1123 resulting clone will contain only the specified changesets and
1124 their ancestors. These options (or 'clone src#rev dest') imply
1124 their ancestors. These options (or 'clone src#rev dest') imply
1125 --pull, even for local source repositories. Note that specifying a
1125 --pull, even for local source repositories. Note that specifying a
1126 tag will include the tagged changeset but not the changeset
1126 tag will include the tagged changeset but not the changeset
1127 containing the tag.
1127 containing the tag.
1128
1128
1129 To check out a particular version, use -u/--update, or
1129 To check out a particular version, use -u/--update, or
1130 -U/--noupdate to create a clone with no working directory.
1130 -U/--noupdate to create a clone with no working directory.
1131
1131
1132 .. container:: verbose
1132 .. container:: verbose
1133
1133
1134 For efficiency, hardlinks are used for cloning whenever the
1134 For efficiency, hardlinks are used for cloning whenever the
1135 source and destination are on the same filesystem (note this
1135 source and destination are on the same filesystem (note this
1136 applies only to the repository data, not to the working
1136 applies only to the repository data, not to the working
1137 directory). Some filesystems, such as AFS, implement hardlinking
1137 directory). Some filesystems, such as AFS, implement hardlinking
1138 incorrectly, but do not report errors. In these cases, use the
1138 incorrectly, but do not report errors. In these cases, use the
1139 --pull option to avoid hardlinking.
1139 --pull option to avoid hardlinking.
1140
1140
1141 In some cases, you can clone repositories and the working
1141 In some cases, you can clone repositories and the working
1142 directory using full hardlinks with ::
1142 directory using full hardlinks with ::
1143
1143
1144 $ cp -al REPO REPOCLONE
1144 $ cp -al REPO REPOCLONE
1145
1145
1146 This is the fastest way to clone, but it is not always safe. The
1146 This is the fastest way to clone, but it is not always safe. The
1147 operation is not atomic (making sure REPO is not modified during
1147 operation is not atomic (making sure REPO is not modified during
1148 the operation is up to you) and you have to make sure your
1148 the operation is up to you) and you have to make sure your
1149 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1149 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1150 so). Also, this is not compatible with certain extensions that
1150 so). Also, this is not compatible with certain extensions that
1151 place their metadata under the .hg directory, such as mq.
1151 place their metadata under the .hg directory, such as mq.
1152
1152
1153 Mercurial will update the working directory to the first applicable
1153 Mercurial will update the working directory to the first applicable
1154 revision from this list:
1154 revision from this list:
1155
1155
1156 a) null if -U or the source repository has no changesets
1156 a) null if -U or the source repository has no changesets
1157 b) if -u . and the source repository is local, the first parent of
1157 b) if -u . and the source repository is local, the first parent of
1158 the source repository's working directory
1158 the source repository's working directory
1159 c) the changeset specified with -u (if a branch name, this means the
1159 c) the changeset specified with -u (if a branch name, this means the
1160 latest head of that branch)
1160 latest head of that branch)
1161 d) the changeset specified with -r
1161 d) the changeset specified with -r
1162 e) the tipmost head specified with -b
1162 e) the tipmost head specified with -b
1163 f) the tipmost head specified with the url#branch source syntax
1163 f) the tipmost head specified with the url#branch source syntax
1164 g) the tipmost head of the default branch
1164 g) the tipmost head of the default branch
1165 h) tip
1165 h) tip
1166
1166
1167 Examples:
1167 Examples:
1168
1168
1169 - clone a remote repository to a new directory named hg/::
1169 - clone a remote repository to a new directory named hg/::
1170
1170
1171 hg clone http://selenic.com/hg
1171 hg clone http://selenic.com/hg
1172
1172
1173 - create a lightweight local clone::
1173 - create a lightweight local clone::
1174
1174
1175 hg clone project/ project-feature/
1175 hg clone project/ project-feature/
1176
1176
1177 - clone from an absolute path on an ssh server (note double-slash)::
1177 - clone from an absolute path on an ssh server (note double-slash)::
1178
1178
1179 hg clone ssh://user@server//home/projects/alpha/
1179 hg clone ssh://user@server//home/projects/alpha/
1180
1180
1181 - do a high-speed clone over a LAN while checking out a
1181 - do a high-speed clone over a LAN while checking out a
1182 specified version::
1182 specified version::
1183
1183
1184 hg clone --uncompressed http://server/repo -u 1.5
1184 hg clone --uncompressed http://server/repo -u 1.5
1185
1185
1186 - create a repository without changesets after a particular revision::
1186 - create a repository without changesets after a particular revision::
1187
1187
1188 hg clone -r 04e544 experimental/ good/
1188 hg clone -r 04e544 experimental/ good/
1189
1189
1190 - clone (and track) a particular named branch::
1190 - clone (and track) a particular named branch::
1191
1191
1192 hg clone http://selenic.com/hg#stable
1192 hg clone http://selenic.com/hg#stable
1193
1193
1194 See :hg:`help urls` for details on specifying URLs.
1194 See :hg:`help urls` for details on specifying URLs.
1195
1195
1196 Returns 0 on success.
1196 Returns 0 on success.
1197 """
1197 """
1198 if opts.get('noupdate') and opts.get('updaterev'):
1198 if opts.get('noupdate') and opts.get('updaterev'):
1199 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1199 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1200
1200
1201 r = hg.clone(ui, opts, source, dest,
1201 r = hg.clone(ui, opts, source, dest,
1202 pull=opts.get('pull'),
1202 pull=opts.get('pull'),
1203 stream=opts.get('uncompressed'),
1203 stream=opts.get('uncompressed'),
1204 rev=opts.get('rev'),
1204 rev=opts.get('rev'),
1205 update=opts.get('updaterev') or not opts.get('noupdate'),
1205 update=opts.get('updaterev') or not opts.get('noupdate'),
1206 branch=opts.get('branch'))
1206 branch=opts.get('branch'))
1207
1207
1208 return r is None
1208 return r is None
1209
1209
1210 @command('^commit|ci',
1210 @command('^commit|ci',
1211 [('A', 'addremove', None,
1211 [('A', 'addremove', None,
1212 _('mark new/missing files as added/removed before committing')),
1212 _('mark new/missing files as added/removed before committing')),
1213 ('', 'close-branch', None,
1213 ('', 'close-branch', None,
1214 _('mark a branch as closed, hiding it from the branch list')),
1214 _('mark a branch as closed, hiding it from the branch list')),
1215 ('', 'amend', None, _('amend the parent of the working dir')),
1215 ('', 'amend', None, _('amend the parent of the working dir')),
1216 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1216 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1217 _('[OPTION]... [FILE]...'))
1217 _('[OPTION]... [FILE]...'))
1218 def commit(ui, repo, *pats, **opts):
1218 def commit(ui, repo, *pats, **opts):
1219 """commit the specified files or all outstanding changes
1219 """commit the specified files or all outstanding changes
1220
1220
1221 Commit changes to the given files into the repository. Unlike a
1221 Commit changes to the given files into the repository. Unlike a
1222 centralized SCM, this operation is a local operation. See
1222 centralized SCM, this operation is a local operation. See
1223 :hg:`push` for a way to actively distribute your changes.
1223 :hg:`push` for a way to actively distribute your changes.
1224
1224
1225 If a list of files is omitted, all changes reported by :hg:`status`
1225 If a list of files is omitted, all changes reported by :hg:`status`
1226 will be committed.
1226 will be committed.
1227
1227
1228 If you are committing the result of a merge, do not provide any
1228 If you are committing the result of a merge, do not provide any
1229 filenames or -I/-X filters.
1229 filenames or -I/-X filters.
1230
1230
1231 If no commit message is specified, Mercurial starts your
1231 If no commit message is specified, Mercurial starts your
1232 configured editor where you can enter a message. In case your
1232 configured editor where you can enter a message. In case your
1233 commit fails, you will find a backup of your message in
1233 commit fails, you will find a backup of your message in
1234 ``.hg/last-message.txt``.
1234 ``.hg/last-message.txt``.
1235
1235
1236 The --amend flag can be used to amend the parent of the
1236 The --amend flag can be used to amend the parent of the
1237 working directory with a new commit that contains the changes
1237 working directory with a new commit that contains the changes
1238 in the parent in addition to those currently reported by :hg:`status`,
1238 in the parent in addition to those currently reported by :hg:`status`,
1239 if there are any. The old commit is stored in a backup bundle in
1239 if there are any. The old commit is stored in a backup bundle in
1240 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1240 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1241 on how to restore it).
1241 on how to restore it).
1242
1242
1243 Message, user and date are taken from the amended commit unless
1243 Message, user and date are taken from the amended commit unless
1244 specified. When a message isn't specified on the command line,
1244 specified. When a message isn't specified on the command line,
1245 the editor will open with the message of the amended commit.
1245 the editor will open with the message of the amended commit.
1246
1246
1247 It is not possible to amend public changesets (see :hg:`help phases`)
1247 It is not possible to amend public changesets (see :hg:`help phases`)
1248 or changesets that have children.
1248 or changesets that have children.
1249
1249
1250 See :hg:`help dates` for a list of formats valid for -d/--date.
1250 See :hg:`help dates` for a list of formats valid for -d/--date.
1251
1251
1252 Returns 0 on success, 1 if nothing changed.
1252 Returns 0 on success, 1 if nothing changed.
1253 """
1253 """
1254 if opts.get('subrepos'):
1254 if opts.get('subrepos'):
1255 # Let --subrepos on the command line override config setting.
1255 # Let --subrepos on the command line override config setting.
1256 ui.setconfig('ui', 'commitsubrepos', True)
1256 ui.setconfig('ui', 'commitsubrepos', True)
1257
1257
1258 extra = {}
1258 extra = {}
1259 if opts.get('close_branch'):
1259 if opts.get('close_branch'):
1260 if repo['.'].node() not in repo.branchheads():
1260 if repo['.'].node() not in repo.branchheads():
1261 # The topo heads set is included in the branch heads set of the
1261 # The topo heads set is included in the branch heads set of the
1262 # current branch, so it's sufficient to test branchheads
1262 # current branch, so it's sufficient to test branchheads
1263 raise util.Abort(_('can only close branch heads'))
1263 raise util.Abort(_('can only close branch heads'))
1264 extra['close'] = 1
1264 extra['close'] = 1
1265
1265
1266 branch = repo[None].branch()
1266 branch = repo[None].branch()
1267 bheads = repo.branchheads(branch)
1267 bheads = repo.branchheads(branch)
1268
1268
1269 if opts.get('amend'):
1269 if opts.get('amend'):
1270 if ui.configbool('ui', 'commitsubrepos'):
1270 if ui.configbool('ui', 'commitsubrepos'):
1271 raise util.Abort(_('cannot amend recursively'))
1271 raise util.Abort(_('cannot amend recursively'))
1272
1272
1273 old = repo['.']
1273 old = repo['.']
1274 if old.phase() == phases.public:
1274 if old.phase() == phases.public:
1275 raise util.Abort(_('cannot amend public changesets'))
1275 raise util.Abort(_('cannot amend public changesets'))
1276 if len(old.parents()) > 1:
1276 if len(old.parents()) > 1:
1277 raise util.Abort(_('cannot amend merge changesets'))
1277 raise util.Abort(_('cannot amend merge changesets'))
1278 if len(repo[None].parents()) > 1:
1278 if len(repo[None].parents()) > 1:
1279 raise util.Abort(_('cannot amend while merging'))
1279 raise util.Abort(_('cannot amend while merging'))
1280 if old.children():
1280 if old.children():
1281 raise util.Abort(_('cannot amend changeset with children'))
1281 raise util.Abort(_('cannot amend changeset with children'))
1282
1282
1283 e = cmdutil.commiteditor
1283 e = cmdutil.commiteditor
1284 if opts.get('force_editor'):
1284 if opts.get('force_editor'):
1285 e = cmdutil.commitforceeditor
1285 e = cmdutil.commitforceeditor
1286
1286
1287 def commitfunc(ui, repo, message, match, opts):
1287 def commitfunc(ui, repo, message, match, opts):
1288 editor = e
1288 editor = e
1289 # message contains text from -m or -l, if it's empty,
1289 # message contains text from -m or -l, if it's empty,
1290 # open the editor with the old message
1290 # open the editor with the old message
1291 if not message:
1291 if not message:
1292 message = old.description()
1292 message = old.description()
1293 editor = cmdutil.commitforceeditor
1293 editor = cmdutil.commitforceeditor
1294 return repo.commit(message,
1294 return repo.commit(message,
1295 opts.get('user') or old.user(),
1295 opts.get('user') or old.user(),
1296 opts.get('date') or old.date(),
1296 opts.get('date') or old.date(),
1297 match,
1297 match,
1298 editor=editor,
1298 editor=editor,
1299 extra=extra)
1299 extra=extra)
1300
1300
1301 current = repo._bookmarkcurrent
1301 current = repo._bookmarkcurrent
1302 marks = old.bookmarks()
1302 marks = old.bookmarks()
1303 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1303 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1304 if node == old.node():
1304 if node == old.node():
1305 ui.status(_("nothing changed\n"))
1305 ui.status(_("nothing changed\n"))
1306 return 1
1306 return 1
1307 elif marks:
1307 elif marks:
1308 ui.debug('moving bookmarks %r from %s to %s\n' %
1308 ui.debug('moving bookmarks %r from %s to %s\n' %
1309 (marks, old.hex(), hex(node)))
1309 (marks, old.hex(), hex(node)))
1310 for bm in marks:
1310 for bm in marks:
1311 repo._bookmarks[bm] = node
1311 repo._bookmarks[bm] = node
1312 if bm == current:
1312 if bm == current:
1313 bookmarks.setcurrent(repo, bm)
1313 bookmarks.setcurrent(repo, bm)
1314 bookmarks.write(repo)
1314 bookmarks.write(repo)
1315 else:
1315 else:
1316 e = cmdutil.commiteditor
1316 e = cmdutil.commiteditor
1317 if opts.get('force_editor'):
1317 if opts.get('force_editor'):
1318 e = cmdutil.commitforceeditor
1318 e = cmdutil.commitforceeditor
1319
1319
1320 def commitfunc(ui, repo, message, match, opts):
1320 def commitfunc(ui, repo, message, match, opts):
1321 return repo.commit(message, opts.get('user'), opts.get('date'),
1321 return repo.commit(message, opts.get('user'), opts.get('date'),
1322 match, editor=e, extra=extra)
1322 match, editor=e, extra=extra)
1323
1323
1324 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1324 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1325
1325
1326 if not node:
1326 if not node:
1327 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1327 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1328 if stat[3]:
1328 if stat[3]:
1329 ui.status(_("nothing changed (%d missing files, see "
1329 ui.status(_("nothing changed (%d missing files, see "
1330 "'hg status')\n") % len(stat[3]))
1330 "'hg status')\n") % len(stat[3]))
1331 else:
1331 else:
1332 ui.status(_("nothing changed\n"))
1332 ui.status(_("nothing changed\n"))
1333 return 1
1333 return 1
1334
1334
1335 ctx = repo[node]
1335 ctx = repo[node]
1336 parents = ctx.parents()
1336 parents = ctx.parents()
1337
1337
1338 if (not opts.get('amend') and bheads and node not in bheads and not
1338 if (not opts.get('amend') and bheads and node not in bheads and not
1339 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1339 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1340 ui.status(_('created new head\n'))
1340 ui.status(_('created new head\n'))
1341 # The message is not printed for initial roots. For the other
1341 # The message is not printed for initial roots. For the other
1342 # changesets, it is printed in the following situations:
1342 # changesets, it is printed in the following situations:
1343 #
1343 #
1344 # Par column: for the 2 parents with ...
1344 # Par column: for the 2 parents with ...
1345 # N: null or no parent
1345 # N: null or no parent
1346 # B: parent is on another named branch
1346 # B: parent is on another named branch
1347 # C: parent is a regular non head changeset
1347 # C: parent is a regular non head changeset
1348 # H: parent was a branch head of the current branch
1348 # H: parent was a branch head of the current branch
1349 # Msg column: whether we print "created new head" message
1349 # Msg column: whether we print "created new head" message
1350 # In the following, it is assumed that there already exists some
1350 # In the following, it is assumed that there already exists some
1351 # initial branch heads of the current branch, otherwise nothing is
1351 # initial branch heads of the current branch, otherwise nothing is
1352 # printed anyway.
1352 # printed anyway.
1353 #
1353 #
1354 # Par Msg Comment
1354 # Par Msg Comment
1355 # N N y additional topo root
1355 # N N y additional topo root
1356 #
1356 #
1357 # B N y additional branch root
1357 # B N y additional branch root
1358 # C N y additional topo head
1358 # C N y additional topo head
1359 # H N n usual case
1359 # H N n usual case
1360 #
1360 #
1361 # B B y weird additional branch root
1361 # B B y weird additional branch root
1362 # C B y branch merge
1362 # C B y branch merge
1363 # H B n merge with named branch
1363 # H B n merge with named branch
1364 #
1364 #
1365 # C C y additional head from merge
1365 # C C y additional head from merge
1366 # C H n merge with a head
1366 # C H n merge with a head
1367 #
1367 #
1368 # H H n head merge: head count decreases
1368 # H H n head merge: head count decreases
1369
1369
1370 if not opts.get('close_branch'):
1370 if not opts.get('close_branch'):
1371 for r in parents:
1371 for r in parents:
1372 if r.closesbranch() and r.branch() == branch:
1372 if r.closesbranch() and r.branch() == branch:
1373 ui.status(_('reopening closed branch head %d\n') % r)
1373 ui.status(_('reopening closed branch head %d\n') % r)
1374
1374
1375 if ui.debugflag:
1375 if ui.debugflag:
1376 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1376 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1377 elif ui.verbose:
1377 elif ui.verbose:
1378 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1378 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1379
1379
1380 @command('copy|cp',
1380 @command('copy|cp',
1381 [('A', 'after', None, _('record a copy that has already occurred')),
1381 [('A', 'after', None, _('record a copy that has already occurred')),
1382 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1382 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1383 ] + walkopts + dryrunopts,
1383 ] + walkopts + dryrunopts,
1384 _('[OPTION]... [SOURCE]... DEST'))
1384 _('[OPTION]... [SOURCE]... DEST'))
1385 def copy(ui, repo, *pats, **opts):
1385 def copy(ui, repo, *pats, **opts):
1386 """mark files as copied for the next commit
1386 """mark files as copied for the next commit
1387
1387
1388 Mark dest as having copies of source files. If dest is a
1388 Mark dest as having copies of source files. If dest is a
1389 directory, copies are put in that directory. If dest is a file,
1389 directory, copies are put in that directory. If dest is a file,
1390 the source must be a single file.
1390 the source must be a single file.
1391
1391
1392 By default, this command copies the contents of files as they
1392 By default, this command copies the contents of files as they
1393 exist in the working directory. If invoked with -A/--after, the
1393 exist in the working directory. If invoked with -A/--after, the
1394 operation is recorded, but no copying is performed.
1394 operation is recorded, but no copying is performed.
1395
1395
1396 This command takes effect with the next commit. To undo a copy
1396 This command takes effect with the next commit. To undo a copy
1397 before that, see :hg:`revert`.
1397 before that, see :hg:`revert`.
1398
1398
1399 Returns 0 on success, 1 if errors are encountered.
1399 Returns 0 on success, 1 if errors are encountered.
1400 """
1400 """
1401 wlock = repo.wlock(False)
1401 wlock = repo.wlock(False)
1402 try:
1402 try:
1403 return cmdutil.copy(ui, repo, pats, opts)
1403 return cmdutil.copy(ui, repo, pats, opts)
1404 finally:
1404 finally:
1405 wlock.release()
1405 wlock.release()
1406
1406
1407 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1407 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1408 def debugancestor(ui, repo, *args):
1408 def debugancestor(ui, repo, *args):
1409 """find the ancestor revision of two revisions in a given index"""
1409 """find the ancestor revision of two revisions in a given index"""
1410 if len(args) == 3:
1410 if len(args) == 3:
1411 index, rev1, rev2 = args
1411 index, rev1, rev2 = args
1412 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1412 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1413 lookup = r.lookup
1413 lookup = r.lookup
1414 elif len(args) == 2:
1414 elif len(args) == 2:
1415 if not repo:
1415 if not repo:
1416 raise util.Abort(_("there is no Mercurial repository here "
1416 raise util.Abort(_("there is no Mercurial repository here "
1417 "(.hg not found)"))
1417 "(.hg not found)"))
1418 rev1, rev2 = args
1418 rev1, rev2 = args
1419 r = repo.changelog
1419 r = repo.changelog
1420 lookup = repo.lookup
1420 lookup = repo.lookup
1421 else:
1421 else:
1422 raise util.Abort(_('either two or three arguments required'))
1422 raise util.Abort(_('either two or three arguments required'))
1423 a = r.ancestor(lookup(rev1), lookup(rev2))
1423 a = r.ancestor(lookup(rev1), lookup(rev2))
1424 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1424 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1425
1425
1426 @command('debugbuilddag',
1426 @command('debugbuilddag',
1427 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1427 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1428 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1428 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1429 ('n', 'new-file', None, _('add new file at each rev'))],
1429 ('n', 'new-file', None, _('add new file at each rev'))],
1430 _('[OPTION]... [TEXT]'))
1430 _('[OPTION]... [TEXT]'))
1431 def debugbuilddag(ui, repo, text=None,
1431 def debugbuilddag(ui, repo, text=None,
1432 mergeable_file=False,
1432 mergeable_file=False,
1433 overwritten_file=False,
1433 overwritten_file=False,
1434 new_file=False):
1434 new_file=False):
1435 """builds a repo with a given DAG from scratch in the current empty repo
1435 """builds a repo with a given DAG from scratch in the current empty repo
1436
1436
1437 The description of the DAG is read from stdin if not given on the
1437 The description of the DAG is read from stdin if not given on the
1438 command line.
1438 command line.
1439
1439
1440 Elements:
1440 Elements:
1441
1441
1442 - "+n" is a linear run of n nodes based on the current default parent
1442 - "+n" is a linear run of n nodes based on the current default parent
1443 - "." is a single node based on the current default parent
1443 - "." is a single node based on the current default parent
1444 - "$" resets the default parent to null (implied at the start);
1444 - "$" resets the default parent to null (implied at the start);
1445 otherwise the default parent is always the last node created
1445 otherwise the default parent is always the last node created
1446 - "<p" sets the default parent to the backref p
1446 - "<p" sets the default parent to the backref p
1447 - "*p" is a fork at parent p, which is a backref
1447 - "*p" is a fork at parent p, which is a backref
1448 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1448 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1449 - "/p2" is a merge of the preceding node and p2
1449 - "/p2" is a merge of the preceding node and p2
1450 - ":tag" defines a local tag for the preceding node
1450 - ":tag" defines a local tag for the preceding node
1451 - "@branch" sets the named branch for subsequent nodes
1451 - "@branch" sets the named branch for subsequent nodes
1452 - "#...\\n" is a comment up to the end of the line
1452 - "#...\\n" is a comment up to the end of the line
1453
1453
1454 Whitespace between the above elements is ignored.
1454 Whitespace between the above elements is ignored.
1455
1455
1456 A backref is either
1456 A backref is either
1457
1457
1458 - a number n, which references the node curr-n, where curr is the current
1458 - a number n, which references the node curr-n, where curr is the current
1459 node, or
1459 node, or
1460 - the name of a local tag you placed earlier using ":tag", or
1460 - the name of a local tag you placed earlier using ":tag", or
1461 - empty to denote the default parent.
1461 - empty to denote the default parent.
1462
1462
1463 All string valued-elements are either strictly alphanumeric, or must
1463 All string valued-elements are either strictly alphanumeric, or must
1464 be enclosed in double quotes ("..."), with "\\" as escape character.
1464 be enclosed in double quotes ("..."), with "\\" as escape character.
1465 """
1465 """
1466
1466
1467 if text is None:
1467 if text is None:
1468 ui.status(_("reading DAG from stdin\n"))
1468 ui.status(_("reading DAG from stdin\n"))
1469 text = ui.fin.read()
1469 text = ui.fin.read()
1470
1470
1471 cl = repo.changelog
1471 cl = repo.changelog
1472 if len(cl) > 0:
1472 if len(cl) > 0:
1473 raise util.Abort(_('repository is not empty'))
1473 raise util.Abort(_('repository is not empty'))
1474
1474
1475 # determine number of revs in DAG
1475 # determine number of revs in DAG
1476 total = 0
1476 total = 0
1477 for type, data in dagparser.parsedag(text):
1477 for type, data in dagparser.parsedag(text):
1478 if type == 'n':
1478 if type == 'n':
1479 total += 1
1479 total += 1
1480
1480
1481 if mergeable_file:
1481 if mergeable_file:
1482 linesperrev = 2
1482 linesperrev = 2
1483 # make a file with k lines per rev
1483 # make a file with k lines per rev
1484 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1484 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1485 initialmergedlines.append("")
1485 initialmergedlines.append("")
1486
1486
1487 tags = []
1487 tags = []
1488
1488
1489 lock = tr = None
1489 lock = tr = None
1490 try:
1490 try:
1491 lock = repo.lock()
1491 lock = repo.lock()
1492 tr = repo.transaction("builddag")
1492 tr = repo.transaction("builddag")
1493
1493
1494 at = -1
1494 at = -1
1495 atbranch = 'default'
1495 atbranch = 'default'
1496 nodeids = []
1496 nodeids = []
1497 id = 0
1497 id = 0
1498 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1498 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1499 for type, data in dagparser.parsedag(text):
1499 for type, data in dagparser.parsedag(text):
1500 if type == 'n':
1500 if type == 'n':
1501 ui.note('node %s\n' % str(data))
1501 ui.note('node %s\n' % str(data))
1502 id, ps = data
1502 id, ps = data
1503
1503
1504 files = []
1504 files = []
1505 fctxs = {}
1505 fctxs = {}
1506
1506
1507 p2 = None
1507 p2 = None
1508 if mergeable_file:
1508 if mergeable_file:
1509 fn = "mf"
1509 fn = "mf"
1510 p1 = repo[ps[0]]
1510 p1 = repo[ps[0]]
1511 if len(ps) > 1:
1511 if len(ps) > 1:
1512 p2 = repo[ps[1]]
1512 p2 = repo[ps[1]]
1513 pa = p1.ancestor(p2)
1513 pa = p1.ancestor(p2)
1514 base, local, other = [x[fn].data() for x in pa, p1, p2]
1514 base, local, other = [x[fn].data() for x in pa, p1, p2]
1515 m3 = simplemerge.Merge3Text(base, local, other)
1515 m3 = simplemerge.Merge3Text(base, local, other)
1516 ml = [l.strip() for l in m3.merge_lines()]
1516 ml = [l.strip() for l in m3.merge_lines()]
1517 ml.append("")
1517 ml.append("")
1518 elif at > 0:
1518 elif at > 0:
1519 ml = p1[fn].data().split("\n")
1519 ml = p1[fn].data().split("\n")
1520 else:
1520 else:
1521 ml = initialmergedlines
1521 ml = initialmergedlines
1522 ml[id * linesperrev] += " r%i" % id
1522 ml[id * linesperrev] += " r%i" % id
1523 mergedtext = "\n".join(ml)
1523 mergedtext = "\n".join(ml)
1524 files.append(fn)
1524 files.append(fn)
1525 fctxs[fn] = context.memfilectx(fn, mergedtext)
1525 fctxs[fn] = context.memfilectx(fn, mergedtext)
1526
1526
1527 if overwritten_file:
1527 if overwritten_file:
1528 fn = "of"
1528 fn = "of"
1529 files.append(fn)
1529 files.append(fn)
1530 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1530 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1531
1531
1532 if new_file:
1532 if new_file:
1533 fn = "nf%i" % id
1533 fn = "nf%i" % id
1534 files.append(fn)
1534 files.append(fn)
1535 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1535 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1536 if len(ps) > 1:
1536 if len(ps) > 1:
1537 if not p2:
1537 if not p2:
1538 p2 = repo[ps[1]]
1538 p2 = repo[ps[1]]
1539 for fn in p2:
1539 for fn in p2:
1540 if fn.startswith("nf"):
1540 if fn.startswith("nf"):
1541 files.append(fn)
1541 files.append(fn)
1542 fctxs[fn] = p2[fn]
1542 fctxs[fn] = p2[fn]
1543
1543
1544 def fctxfn(repo, cx, path):
1544 def fctxfn(repo, cx, path):
1545 return fctxs.get(path)
1545 return fctxs.get(path)
1546
1546
1547 if len(ps) == 0 or ps[0] < 0:
1547 if len(ps) == 0 or ps[0] < 0:
1548 pars = [None, None]
1548 pars = [None, None]
1549 elif len(ps) == 1:
1549 elif len(ps) == 1:
1550 pars = [nodeids[ps[0]], None]
1550 pars = [nodeids[ps[0]], None]
1551 else:
1551 else:
1552 pars = [nodeids[p] for p in ps]
1552 pars = [nodeids[p] for p in ps]
1553 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1553 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1554 date=(id, 0),
1554 date=(id, 0),
1555 user="debugbuilddag",
1555 user="debugbuilddag",
1556 extra={'branch': atbranch})
1556 extra={'branch': atbranch})
1557 nodeid = repo.commitctx(cx)
1557 nodeid = repo.commitctx(cx)
1558 nodeids.append(nodeid)
1558 nodeids.append(nodeid)
1559 at = id
1559 at = id
1560 elif type == 'l':
1560 elif type == 'l':
1561 id, name = data
1561 id, name = data
1562 ui.note('tag %s\n' % name)
1562 ui.note('tag %s\n' % name)
1563 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1563 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1564 elif type == 'a':
1564 elif type == 'a':
1565 ui.note('branch %s\n' % data)
1565 ui.note('branch %s\n' % data)
1566 atbranch = data
1566 atbranch = data
1567 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1567 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1568 tr.close()
1568 tr.close()
1569
1569
1570 if tags:
1570 if tags:
1571 repo.opener.write("localtags", "".join(tags))
1571 repo.opener.write("localtags", "".join(tags))
1572 finally:
1572 finally:
1573 ui.progress(_('building'), None)
1573 ui.progress(_('building'), None)
1574 release(tr, lock)
1574 release(tr, lock)
1575
1575
1576 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1576 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1577 def debugbundle(ui, bundlepath, all=None, **opts):
1577 def debugbundle(ui, bundlepath, all=None, **opts):
1578 """lists the contents of a bundle"""
1578 """lists the contents of a bundle"""
1579 f = url.open(ui, bundlepath)
1579 f = url.open(ui, bundlepath)
1580 try:
1580 try:
1581 gen = changegroup.readbundle(f, bundlepath)
1581 gen = changegroup.readbundle(f, bundlepath)
1582 if all:
1582 if all:
1583 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1583 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1584
1584
1585 def showchunks(named):
1585 def showchunks(named):
1586 ui.write("\n%s\n" % named)
1586 ui.write("\n%s\n" % named)
1587 chain = None
1587 chain = None
1588 while True:
1588 while True:
1589 chunkdata = gen.deltachunk(chain)
1589 chunkdata = gen.deltachunk(chain)
1590 if not chunkdata:
1590 if not chunkdata:
1591 break
1591 break
1592 node = chunkdata['node']
1592 node = chunkdata['node']
1593 p1 = chunkdata['p1']
1593 p1 = chunkdata['p1']
1594 p2 = chunkdata['p2']
1594 p2 = chunkdata['p2']
1595 cs = chunkdata['cs']
1595 cs = chunkdata['cs']
1596 deltabase = chunkdata['deltabase']
1596 deltabase = chunkdata['deltabase']
1597 delta = chunkdata['delta']
1597 delta = chunkdata['delta']
1598 ui.write("%s %s %s %s %s %s\n" %
1598 ui.write("%s %s %s %s %s %s\n" %
1599 (hex(node), hex(p1), hex(p2),
1599 (hex(node), hex(p1), hex(p2),
1600 hex(cs), hex(deltabase), len(delta)))
1600 hex(cs), hex(deltabase), len(delta)))
1601 chain = node
1601 chain = node
1602
1602
1603 chunkdata = gen.changelogheader()
1603 chunkdata = gen.changelogheader()
1604 showchunks("changelog")
1604 showchunks("changelog")
1605 chunkdata = gen.manifestheader()
1605 chunkdata = gen.manifestheader()
1606 showchunks("manifest")
1606 showchunks("manifest")
1607 while True:
1607 while True:
1608 chunkdata = gen.filelogheader()
1608 chunkdata = gen.filelogheader()
1609 if not chunkdata:
1609 if not chunkdata:
1610 break
1610 break
1611 fname = chunkdata['filename']
1611 fname = chunkdata['filename']
1612 showchunks(fname)
1612 showchunks(fname)
1613 else:
1613 else:
1614 chunkdata = gen.changelogheader()
1614 chunkdata = gen.changelogheader()
1615 chain = None
1615 chain = None
1616 while True:
1616 while True:
1617 chunkdata = gen.deltachunk(chain)
1617 chunkdata = gen.deltachunk(chain)
1618 if not chunkdata:
1618 if not chunkdata:
1619 break
1619 break
1620 node = chunkdata['node']
1620 node = chunkdata['node']
1621 ui.write("%s\n" % hex(node))
1621 ui.write("%s\n" % hex(node))
1622 chain = node
1622 chain = node
1623 finally:
1623 finally:
1624 f.close()
1624 f.close()
1625
1625
1626 @command('debugcheckstate', [], '')
1626 @command('debugcheckstate', [], '')
1627 def debugcheckstate(ui, repo):
1627 def debugcheckstate(ui, repo):
1628 """validate the correctness of the current dirstate"""
1628 """validate the correctness of the current dirstate"""
1629 parent1, parent2 = repo.dirstate.parents()
1629 parent1, parent2 = repo.dirstate.parents()
1630 m1 = repo[parent1].manifest()
1630 m1 = repo[parent1].manifest()
1631 m2 = repo[parent2].manifest()
1631 m2 = repo[parent2].manifest()
1632 errors = 0
1632 errors = 0
1633 for f in repo.dirstate:
1633 for f in repo.dirstate:
1634 state = repo.dirstate[f]
1634 state = repo.dirstate[f]
1635 if state in "nr" and f not in m1:
1635 if state in "nr" and f not in m1:
1636 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1636 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1637 errors += 1
1637 errors += 1
1638 if state in "a" and f in m1:
1638 if state in "a" and f in m1:
1639 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1639 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1640 errors += 1
1640 errors += 1
1641 if state in "m" and f not in m1 and f not in m2:
1641 if state in "m" and f not in m1 and f not in m2:
1642 ui.warn(_("%s in state %s, but not in either manifest\n") %
1642 ui.warn(_("%s in state %s, but not in either manifest\n") %
1643 (f, state))
1643 (f, state))
1644 errors += 1
1644 errors += 1
1645 for f in m1:
1645 for f in m1:
1646 state = repo.dirstate[f]
1646 state = repo.dirstate[f]
1647 if state not in "nrm":
1647 if state not in "nrm":
1648 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1648 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1649 errors += 1
1649 errors += 1
1650 if errors:
1650 if errors:
1651 error = _(".hg/dirstate inconsistent with current parent's manifest")
1651 error = _(".hg/dirstate inconsistent with current parent's manifest")
1652 raise util.Abort(error)
1652 raise util.Abort(error)
1653
1653
1654 @command('debugcommands', [], _('[COMMAND]'))
1654 @command('debugcommands', [], _('[COMMAND]'))
1655 def debugcommands(ui, cmd='', *args):
1655 def debugcommands(ui, cmd='', *args):
1656 """list all available commands and options"""
1656 """list all available commands and options"""
1657 for cmd, vals in sorted(table.iteritems()):
1657 for cmd, vals in sorted(table.iteritems()):
1658 cmd = cmd.split('|')[0].strip('^')
1658 cmd = cmd.split('|')[0].strip('^')
1659 opts = ', '.join([i[1] for i in vals[1]])
1659 opts = ', '.join([i[1] for i in vals[1]])
1660 ui.write('%s: %s\n' % (cmd, opts))
1660 ui.write('%s: %s\n' % (cmd, opts))
1661
1661
1662 @command('debugcomplete',
1662 @command('debugcomplete',
1663 [('o', 'options', None, _('show the command options'))],
1663 [('o', 'options', None, _('show the command options'))],
1664 _('[-o] CMD'))
1664 _('[-o] CMD'))
1665 def debugcomplete(ui, cmd='', **opts):
1665 def debugcomplete(ui, cmd='', **opts):
1666 """returns the completion list associated with the given command"""
1666 """returns the completion list associated with the given command"""
1667
1667
1668 if opts.get('options'):
1668 if opts.get('options'):
1669 options = []
1669 options = []
1670 otables = [globalopts]
1670 otables = [globalopts]
1671 if cmd:
1671 if cmd:
1672 aliases, entry = cmdutil.findcmd(cmd, table, False)
1672 aliases, entry = cmdutil.findcmd(cmd, table, False)
1673 otables.append(entry[1])
1673 otables.append(entry[1])
1674 for t in otables:
1674 for t in otables:
1675 for o in t:
1675 for o in t:
1676 if "(DEPRECATED)" in o[3]:
1676 if "(DEPRECATED)" in o[3]:
1677 continue
1677 continue
1678 if o[0]:
1678 if o[0]:
1679 options.append('-%s' % o[0])
1679 options.append('-%s' % o[0])
1680 options.append('--%s' % o[1])
1680 options.append('--%s' % o[1])
1681 ui.write("%s\n" % "\n".join(options))
1681 ui.write("%s\n" % "\n".join(options))
1682 return
1682 return
1683
1683
1684 cmdlist = cmdutil.findpossible(cmd, table)
1684 cmdlist = cmdutil.findpossible(cmd, table)
1685 if ui.verbose:
1685 if ui.verbose:
1686 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1686 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1687 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1687 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1688
1688
1689 @command('debugdag',
1689 @command('debugdag',
1690 [('t', 'tags', None, _('use tags as labels')),
1690 [('t', 'tags', None, _('use tags as labels')),
1691 ('b', 'branches', None, _('annotate with branch names')),
1691 ('b', 'branches', None, _('annotate with branch names')),
1692 ('', 'dots', None, _('use dots for runs')),
1692 ('', 'dots', None, _('use dots for runs')),
1693 ('s', 'spaces', None, _('separate elements by spaces'))],
1693 ('s', 'spaces', None, _('separate elements by spaces'))],
1694 _('[OPTION]... [FILE [REV]...]'))
1694 _('[OPTION]... [FILE [REV]...]'))
1695 def debugdag(ui, repo, file_=None, *revs, **opts):
1695 def debugdag(ui, repo, file_=None, *revs, **opts):
1696 """format the changelog or an index DAG as a concise textual description
1696 """format the changelog or an index DAG as a concise textual description
1697
1697
1698 If you pass a revlog index, the revlog's DAG is emitted. If you list
1698 If you pass a revlog index, the revlog's DAG is emitted. If you list
1699 revision numbers, they get labelled in the output as rN.
1699 revision numbers, they get labeled in the output as rN.
1700
1700
1701 Otherwise, the changelog DAG of the current repo is emitted.
1701 Otherwise, the changelog DAG of the current repo is emitted.
1702 """
1702 """
1703 spaces = opts.get('spaces')
1703 spaces = opts.get('spaces')
1704 dots = opts.get('dots')
1704 dots = opts.get('dots')
1705 if file_:
1705 if file_:
1706 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1706 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1707 revs = set((int(r) for r in revs))
1707 revs = set((int(r) for r in revs))
1708 def events():
1708 def events():
1709 for r in rlog:
1709 for r in rlog:
1710 yield 'n', (r, list(set(p for p in rlog.parentrevs(r)
1710 yield 'n', (r, list(set(p for p in rlog.parentrevs(r)
1711 if p != -1)))
1711 if p != -1)))
1712 if r in revs:
1712 if r in revs:
1713 yield 'l', (r, "r%i" % r)
1713 yield 'l', (r, "r%i" % r)
1714 elif repo:
1714 elif repo:
1715 cl = repo.changelog
1715 cl = repo.changelog
1716 tags = opts.get('tags')
1716 tags = opts.get('tags')
1717 branches = opts.get('branches')
1717 branches = opts.get('branches')
1718 if tags:
1718 if tags:
1719 labels = {}
1719 labels = {}
1720 for l, n in repo.tags().items():
1720 for l, n in repo.tags().items():
1721 labels.setdefault(cl.rev(n), []).append(l)
1721 labels.setdefault(cl.rev(n), []).append(l)
1722 def events():
1722 def events():
1723 b = "default"
1723 b = "default"
1724 for r in cl:
1724 for r in cl:
1725 if branches:
1725 if branches:
1726 newb = cl.read(cl.node(r))[5]['branch']
1726 newb = cl.read(cl.node(r))[5]['branch']
1727 if newb != b:
1727 if newb != b:
1728 yield 'a', newb
1728 yield 'a', newb
1729 b = newb
1729 b = newb
1730 yield 'n', (r, list(set(p for p in cl.parentrevs(r)
1730 yield 'n', (r, list(set(p for p in cl.parentrevs(r)
1731 if p != -1)))
1731 if p != -1)))
1732 if tags:
1732 if tags:
1733 ls = labels.get(r)
1733 ls = labels.get(r)
1734 if ls:
1734 if ls:
1735 for l in ls:
1735 for l in ls:
1736 yield 'l', (r, l)
1736 yield 'l', (r, l)
1737 else:
1737 else:
1738 raise util.Abort(_('need repo for changelog dag'))
1738 raise util.Abort(_('need repo for changelog dag'))
1739
1739
1740 for line in dagparser.dagtextlines(events(),
1740 for line in dagparser.dagtextlines(events(),
1741 addspaces=spaces,
1741 addspaces=spaces,
1742 wraplabels=True,
1742 wraplabels=True,
1743 wrapannotations=True,
1743 wrapannotations=True,
1744 wrapnonlinear=dots,
1744 wrapnonlinear=dots,
1745 usedots=dots,
1745 usedots=dots,
1746 maxlinewidth=70):
1746 maxlinewidth=70):
1747 ui.write(line)
1747 ui.write(line)
1748 ui.write("\n")
1748 ui.write("\n")
1749
1749
1750 @command('debugdata',
1750 @command('debugdata',
1751 [('c', 'changelog', False, _('open changelog')),
1751 [('c', 'changelog', False, _('open changelog')),
1752 ('m', 'manifest', False, _('open manifest'))],
1752 ('m', 'manifest', False, _('open manifest'))],
1753 _('-c|-m|FILE REV'))
1753 _('-c|-m|FILE REV'))
1754 def debugdata(ui, repo, file_, rev = None, **opts):
1754 def debugdata(ui, repo, file_, rev = None, **opts):
1755 """dump the contents of a data file revision"""
1755 """dump the contents of a data file revision"""
1756 if opts.get('changelog') or opts.get('manifest'):
1756 if opts.get('changelog') or opts.get('manifest'):
1757 file_, rev = None, file_
1757 file_, rev = None, file_
1758 elif rev is None:
1758 elif rev is None:
1759 raise error.CommandError('debugdata', _('invalid arguments'))
1759 raise error.CommandError('debugdata', _('invalid arguments'))
1760 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1760 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1761 try:
1761 try:
1762 ui.write(r.revision(r.lookup(rev)))
1762 ui.write(r.revision(r.lookup(rev)))
1763 except KeyError:
1763 except KeyError:
1764 raise util.Abort(_('invalid revision identifier %s') % rev)
1764 raise util.Abort(_('invalid revision identifier %s') % rev)
1765
1765
1766 @command('debugdate',
1766 @command('debugdate',
1767 [('e', 'extended', None, _('try extended date formats'))],
1767 [('e', 'extended', None, _('try extended date formats'))],
1768 _('[-e] DATE [RANGE]'))
1768 _('[-e] DATE [RANGE]'))
1769 def debugdate(ui, date, range=None, **opts):
1769 def debugdate(ui, date, range=None, **opts):
1770 """parse and display a date"""
1770 """parse and display a date"""
1771 if opts["extended"]:
1771 if opts["extended"]:
1772 d = util.parsedate(date, util.extendeddateformats)
1772 d = util.parsedate(date, util.extendeddateformats)
1773 else:
1773 else:
1774 d = util.parsedate(date)
1774 d = util.parsedate(date)
1775 ui.write("internal: %s %s\n" % d)
1775 ui.write("internal: %s %s\n" % d)
1776 ui.write("standard: %s\n" % util.datestr(d))
1776 ui.write("standard: %s\n" % util.datestr(d))
1777 if range:
1777 if range:
1778 m = util.matchdate(range)
1778 m = util.matchdate(range)
1779 ui.write("match: %s\n" % m(d[0]))
1779 ui.write("match: %s\n" % m(d[0]))
1780
1780
1781 @command('debugdiscovery',
1781 @command('debugdiscovery',
1782 [('', 'old', None, _('use old-style discovery')),
1782 [('', 'old', None, _('use old-style discovery')),
1783 ('', 'nonheads', None,
1783 ('', 'nonheads', None,
1784 _('use old-style discovery with non-heads included')),
1784 _('use old-style discovery with non-heads included')),
1785 ] + remoteopts,
1785 ] + remoteopts,
1786 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1786 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1787 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1787 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1788 """runs the changeset discovery protocol in isolation"""
1788 """runs the changeset discovery protocol in isolation"""
1789 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
1789 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
1790 opts.get('branch'))
1790 opts.get('branch'))
1791 remote = hg.peer(repo, opts, remoteurl)
1791 remote = hg.peer(repo, opts, remoteurl)
1792 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1792 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1793
1793
1794 # make sure tests are repeatable
1794 # make sure tests are repeatable
1795 random.seed(12323)
1795 random.seed(12323)
1796
1796
1797 def doit(localheads, remoteheads, remote=remote):
1797 def doit(localheads, remoteheads, remote=remote):
1798 if opts.get('old'):
1798 if opts.get('old'):
1799 if localheads:
1799 if localheads:
1800 raise util.Abort('cannot use localheads with old style '
1800 raise util.Abort('cannot use localheads with old style '
1801 'discovery')
1801 'discovery')
1802 if not util.safehasattr(remote, 'branches'):
1802 if not util.safehasattr(remote, 'branches'):
1803 # enable in-client legacy support
1803 # enable in-client legacy support
1804 remote = localrepo.locallegacypeer(remote.local())
1804 remote = localrepo.locallegacypeer(remote.local())
1805 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1805 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1806 force=True)
1806 force=True)
1807 common = set(common)
1807 common = set(common)
1808 if not opts.get('nonheads'):
1808 if not opts.get('nonheads'):
1809 ui.write("unpruned common: %s\n" % " ".join([short(n)
1809 ui.write("unpruned common: %s\n" % " ".join([short(n)
1810 for n in common]))
1810 for n in common]))
1811 dag = dagutil.revlogdag(repo.changelog)
1811 dag = dagutil.revlogdag(repo.changelog)
1812 all = dag.ancestorset(dag.internalizeall(common))
1812 all = dag.ancestorset(dag.internalizeall(common))
1813 common = dag.externalizeall(dag.headsetofconnecteds(all))
1813 common = dag.externalizeall(dag.headsetofconnecteds(all))
1814 else:
1814 else:
1815 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1815 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1816 common = set(common)
1816 common = set(common)
1817 rheads = set(hds)
1817 rheads = set(hds)
1818 lheads = set(repo.heads())
1818 lheads = set(repo.heads())
1819 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1819 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1820 if lheads <= common:
1820 if lheads <= common:
1821 ui.write("local is subset\n")
1821 ui.write("local is subset\n")
1822 elif rheads <= common:
1822 elif rheads <= common:
1823 ui.write("remote is subset\n")
1823 ui.write("remote is subset\n")
1824
1824
1825 serverlogs = opts.get('serverlog')
1825 serverlogs = opts.get('serverlog')
1826 if serverlogs:
1826 if serverlogs:
1827 for filename in serverlogs:
1827 for filename in serverlogs:
1828 logfile = open(filename, 'r')
1828 logfile = open(filename, 'r')
1829 try:
1829 try:
1830 line = logfile.readline()
1830 line = logfile.readline()
1831 while line:
1831 while line:
1832 parts = line.strip().split(';')
1832 parts = line.strip().split(';')
1833 op = parts[1]
1833 op = parts[1]
1834 if op == 'cg':
1834 if op == 'cg':
1835 pass
1835 pass
1836 elif op == 'cgss':
1836 elif op == 'cgss':
1837 doit(parts[2].split(' '), parts[3].split(' '))
1837 doit(parts[2].split(' '), parts[3].split(' '))
1838 elif op == 'unb':
1838 elif op == 'unb':
1839 doit(parts[3].split(' '), parts[2].split(' '))
1839 doit(parts[3].split(' '), parts[2].split(' '))
1840 line = logfile.readline()
1840 line = logfile.readline()
1841 finally:
1841 finally:
1842 logfile.close()
1842 logfile.close()
1843
1843
1844 else:
1844 else:
1845 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1845 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1846 opts.get('remote_head'))
1846 opts.get('remote_head'))
1847 localrevs = opts.get('local_head')
1847 localrevs = opts.get('local_head')
1848 doit(localrevs, remoterevs)
1848 doit(localrevs, remoterevs)
1849
1849
1850 @command('debugfileset',
1850 @command('debugfileset',
1851 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
1851 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
1852 _('[-r REV] FILESPEC'))
1852 _('[-r REV] FILESPEC'))
1853 def debugfileset(ui, repo, expr, **opts):
1853 def debugfileset(ui, repo, expr, **opts):
1854 '''parse and apply a fileset specification'''
1854 '''parse and apply a fileset specification'''
1855 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
1855 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
1856 if ui.verbose:
1856 if ui.verbose:
1857 tree = fileset.parse(expr)[0]
1857 tree = fileset.parse(expr)[0]
1858 ui.note(tree, "\n")
1858 ui.note(tree, "\n")
1859
1859
1860 for f in fileset.getfileset(ctx, expr):
1860 for f in fileset.getfileset(ctx, expr):
1861 ui.write("%s\n" % f)
1861 ui.write("%s\n" % f)
1862
1862
1863 @command('debugfsinfo', [], _('[PATH]'))
1863 @command('debugfsinfo', [], _('[PATH]'))
1864 def debugfsinfo(ui, path = "."):
1864 def debugfsinfo(ui, path = "."):
1865 """show information detected about current filesystem"""
1865 """show information detected about current filesystem"""
1866 util.writefile('.debugfsinfo', '')
1866 util.writefile('.debugfsinfo', '')
1867 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1867 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1868 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1868 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1869 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1869 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1870 and 'yes' or 'no'))
1870 and 'yes' or 'no'))
1871 os.unlink('.debugfsinfo')
1871 os.unlink('.debugfsinfo')
1872
1872
1873 @command('debuggetbundle',
1873 @command('debuggetbundle',
1874 [('H', 'head', [], _('id of head node'), _('ID')),
1874 [('H', 'head', [], _('id of head node'), _('ID')),
1875 ('C', 'common', [], _('id of common node'), _('ID')),
1875 ('C', 'common', [], _('id of common node'), _('ID')),
1876 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1876 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1877 _('REPO FILE [-H|-C ID]...'))
1877 _('REPO FILE [-H|-C ID]...'))
1878 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1878 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1879 """retrieves a bundle from a repo
1879 """retrieves a bundle from a repo
1880
1880
1881 Every ID must be a full-length hex node id string. Saves the bundle to the
1881 Every ID must be a full-length hex node id string. Saves the bundle to the
1882 given file.
1882 given file.
1883 """
1883 """
1884 repo = hg.peer(ui, opts, repopath)
1884 repo = hg.peer(ui, opts, repopath)
1885 if not repo.capable('getbundle'):
1885 if not repo.capable('getbundle'):
1886 raise util.Abort("getbundle() not supported by target repository")
1886 raise util.Abort("getbundle() not supported by target repository")
1887 args = {}
1887 args = {}
1888 if common:
1888 if common:
1889 args['common'] = [bin(s) for s in common]
1889 args['common'] = [bin(s) for s in common]
1890 if head:
1890 if head:
1891 args['heads'] = [bin(s) for s in head]
1891 args['heads'] = [bin(s) for s in head]
1892 bundle = repo.getbundle('debug', **args)
1892 bundle = repo.getbundle('debug', **args)
1893
1893
1894 bundletype = opts.get('type', 'bzip2').lower()
1894 bundletype = opts.get('type', 'bzip2').lower()
1895 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1895 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1896 bundletype = btypes.get(bundletype)
1896 bundletype = btypes.get(bundletype)
1897 if bundletype not in changegroup.bundletypes:
1897 if bundletype not in changegroup.bundletypes:
1898 raise util.Abort(_('unknown bundle type specified with --type'))
1898 raise util.Abort(_('unknown bundle type specified with --type'))
1899 changegroup.writebundle(bundle, bundlepath, bundletype)
1899 changegroup.writebundle(bundle, bundlepath, bundletype)
1900
1900
1901 @command('debugignore', [], '')
1901 @command('debugignore', [], '')
1902 def debugignore(ui, repo, *values, **opts):
1902 def debugignore(ui, repo, *values, **opts):
1903 """display the combined ignore pattern"""
1903 """display the combined ignore pattern"""
1904 ignore = repo.dirstate._ignore
1904 ignore = repo.dirstate._ignore
1905 includepat = getattr(ignore, 'includepat', None)
1905 includepat = getattr(ignore, 'includepat', None)
1906 if includepat is not None:
1906 if includepat is not None:
1907 ui.write("%s\n" % includepat)
1907 ui.write("%s\n" % includepat)
1908 else:
1908 else:
1909 raise util.Abort(_("no ignore patterns found"))
1909 raise util.Abort(_("no ignore patterns found"))
1910
1910
1911 @command('debugindex',
1911 @command('debugindex',
1912 [('c', 'changelog', False, _('open changelog')),
1912 [('c', 'changelog', False, _('open changelog')),
1913 ('m', 'manifest', False, _('open manifest')),
1913 ('m', 'manifest', False, _('open manifest')),
1914 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1914 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1915 _('[-f FORMAT] -c|-m|FILE'))
1915 _('[-f FORMAT] -c|-m|FILE'))
1916 def debugindex(ui, repo, file_ = None, **opts):
1916 def debugindex(ui, repo, file_ = None, **opts):
1917 """dump the contents of an index file"""
1917 """dump the contents of an index file"""
1918 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1918 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1919 format = opts.get('format', 0)
1919 format = opts.get('format', 0)
1920 if format not in (0, 1):
1920 if format not in (0, 1):
1921 raise util.Abort(_("unknown format %d") % format)
1921 raise util.Abort(_("unknown format %d") % format)
1922
1922
1923 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1923 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1924 if generaldelta:
1924 if generaldelta:
1925 basehdr = ' delta'
1925 basehdr = ' delta'
1926 else:
1926 else:
1927 basehdr = ' base'
1927 basehdr = ' base'
1928
1928
1929 if format == 0:
1929 if format == 0:
1930 ui.write(" rev offset length " + basehdr + " linkrev"
1930 ui.write(" rev offset length " + basehdr + " linkrev"
1931 " nodeid p1 p2\n")
1931 " nodeid p1 p2\n")
1932 elif format == 1:
1932 elif format == 1:
1933 ui.write(" rev flag offset length"
1933 ui.write(" rev flag offset length"
1934 " size " + basehdr + " link p1 p2"
1934 " size " + basehdr + " link p1 p2"
1935 " nodeid\n")
1935 " nodeid\n")
1936
1936
1937 for i in r:
1937 for i in r:
1938 node = r.node(i)
1938 node = r.node(i)
1939 if generaldelta:
1939 if generaldelta:
1940 base = r.deltaparent(i)
1940 base = r.deltaparent(i)
1941 else:
1941 else:
1942 base = r.chainbase(i)
1942 base = r.chainbase(i)
1943 if format == 0:
1943 if format == 0:
1944 try:
1944 try:
1945 pp = r.parents(node)
1945 pp = r.parents(node)
1946 except Exception:
1946 except Exception:
1947 pp = [nullid, nullid]
1947 pp = [nullid, nullid]
1948 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1948 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1949 i, r.start(i), r.length(i), base, r.linkrev(i),
1949 i, r.start(i), r.length(i), base, r.linkrev(i),
1950 short(node), short(pp[0]), short(pp[1])))
1950 short(node), short(pp[0]), short(pp[1])))
1951 elif format == 1:
1951 elif format == 1:
1952 pr = r.parentrevs(i)
1952 pr = r.parentrevs(i)
1953 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1953 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1954 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1954 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1955 base, r.linkrev(i), pr[0], pr[1], short(node)))
1955 base, r.linkrev(i), pr[0], pr[1], short(node)))
1956
1956
1957 @command('debugindexdot', [], _('FILE'))
1957 @command('debugindexdot', [], _('FILE'))
1958 def debugindexdot(ui, repo, file_):
1958 def debugindexdot(ui, repo, file_):
1959 """dump an index DAG as a graphviz dot file"""
1959 """dump an index DAG as a graphviz dot file"""
1960 r = None
1960 r = None
1961 if repo:
1961 if repo:
1962 filelog = repo.file(file_)
1962 filelog = repo.file(file_)
1963 if len(filelog):
1963 if len(filelog):
1964 r = filelog
1964 r = filelog
1965 if not r:
1965 if not r:
1966 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1966 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1967 ui.write("digraph G {\n")
1967 ui.write("digraph G {\n")
1968 for i in r:
1968 for i in r:
1969 node = r.node(i)
1969 node = r.node(i)
1970 pp = r.parents(node)
1970 pp = r.parents(node)
1971 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1971 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1972 if pp[1] != nullid:
1972 if pp[1] != nullid:
1973 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1973 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1974 ui.write("}\n")
1974 ui.write("}\n")
1975
1975
1976 @command('debuginstall', [], '')
1976 @command('debuginstall', [], '')
1977 def debuginstall(ui):
1977 def debuginstall(ui):
1978 '''test Mercurial installation
1978 '''test Mercurial installation
1979
1979
1980 Returns 0 on success.
1980 Returns 0 on success.
1981 '''
1981 '''
1982
1982
1983 def writetemp(contents):
1983 def writetemp(contents):
1984 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1984 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1985 f = os.fdopen(fd, "wb")
1985 f = os.fdopen(fd, "wb")
1986 f.write(contents)
1986 f.write(contents)
1987 f.close()
1987 f.close()
1988 return name
1988 return name
1989
1989
1990 problems = 0
1990 problems = 0
1991
1991
1992 # encoding
1992 # encoding
1993 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
1993 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
1994 try:
1994 try:
1995 encoding.fromlocal("test")
1995 encoding.fromlocal("test")
1996 except util.Abort, inst:
1996 except util.Abort, inst:
1997 ui.write(" %s\n" % inst)
1997 ui.write(" %s\n" % inst)
1998 ui.write(_(" (check that your locale is properly set)\n"))
1998 ui.write(_(" (check that your locale is properly set)\n"))
1999 problems += 1
1999 problems += 1
2000
2000
2001 # Python lib
2001 # Python lib
2002 ui.status(_("checking Python lib (%s)...\n")
2002 ui.status(_("checking Python lib (%s)...\n")
2003 % os.path.dirname(os.__file__))
2003 % os.path.dirname(os.__file__))
2004
2004
2005 # compiled modules
2005 # compiled modules
2006 ui.status(_("checking installed modules (%s)...\n")
2006 ui.status(_("checking installed modules (%s)...\n")
2007 % os.path.dirname(__file__))
2007 % os.path.dirname(__file__))
2008 try:
2008 try:
2009 import bdiff, mpatch, base85, osutil
2009 import bdiff, mpatch, base85, osutil
2010 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2010 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2011 except Exception, inst:
2011 except Exception, inst:
2012 ui.write(" %s\n" % inst)
2012 ui.write(" %s\n" % inst)
2013 ui.write(_(" One or more extensions could not be found"))
2013 ui.write(_(" One or more extensions could not be found"))
2014 ui.write(_(" (check that you compiled the extensions)\n"))
2014 ui.write(_(" (check that you compiled the extensions)\n"))
2015 problems += 1
2015 problems += 1
2016
2016
2017 # templates
2017 # templates
2018 import templater
2018 import templater
2019 p = templater.templatepath()
2019 p = templater.templatepath()
2020 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2020 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2021 try:
2021 try:
2022 templater.templater(templater.templatepath("map-cmdline.default"))
2022 templater.templater(templater.templatepath("map-cmdline.default"))
2023 except Exception, inst:
2023 except Exception, inst:
2024 ui.write(" %s\n" % inst)
2024 ui.write(" %s\n" % inst)
2025 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2025 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2026 problems += 1
2026 problems += 1
2027
2027
2028 # editor
2028 # editor
2029 ui.status(_("checking commit editor...\n"))
2029 ui.status(_("checking commit editor...\n"))
2030 editor = ui.geteditor()
2030 editor = ui.geteditor()
2031 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
2031 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
2032 if not cmdpath:
2032 if not cmdpath:
2033 if editor == 'vi':
2033 if editor == 'vi':
2034 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2034 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2035 ui.write(_(" (specify a commit editor in your configuration"
2035 ui.write(_(" (specify a commit editor in your configuration"
2036 " file)\n"))
2036 " file)\n"))
2037 else:
2037 else:
2038 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2038 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2039 ui.write(_(" (specify a commit editor in your configuration"
2039 ui.write(_(" (specify a commit editor in your configuration"
2040 " file)\n"))
2040 " file)\n"))
2041 problems += 1
2041 problems += 1
2042
2042
2043 # check username
2043 # check username
2044 ui.status(_("checking username...\n"))
2044 ui.status(_("checking username...\n"))
2045 try:
2045 try:
2046 ui.username()
2046 ui.username()
2047 except util.Abort, e:
2047 except util.Abort, e:
2048 ui.write(" %s\n" % e)
2048 ui.write(" %s\n" % e)
2049 ui.write(_(" (specify a username in your configuration file)\n"))
2049 ui.write(_(" (specify a username in your configuration file)\n"))
2050 problems += 1
2050 problems += 1
2051
2051
2052 if not problems:
2052 if not problems:
2053 ui.status(_("no problems detected\n"))
2053 ui.status(_("no problems detected\n"))
2054 else:
2054 else:
2055 ui.write(_("%s problems detected,"
2055 ui.write(_("%s problems detected,"
2056 " please check your install!\n") % problems)
2056 " please check your install!\n") % problems)
2057
2057
2058 return problems
2058 return problems
2059
2059
2060 @command('debugknown', [], _('REPO ID...'))
2060 @command('debugknown', [], _('REPO ID...'))
2061 def debugknown(ui, repopath, *ids, **opts):
2061 def debugknown(ui, repopath, *ids, **opts):
2062 """test whether node ids are known to a repo
2062 """test whether node ids are known to a repo
2063
2063
2064 Every ID must be a full-length hex node id string. Returns a list of 0s
2064 Every ID must be a full-length hex node id string. Returns a list of 0s
2065 and 1s indicating unknown/known.
2065 and 1s indicating unknown/known.
2066 """
2066 """
2067 repo = hg.peer(ui, opts, repopath)
2067 repo = hg.peer(ui, opts, repopath)
2068 if not repo.capable('known'):
2068 if not repo.capable('known'):
2069 raise util.Abort("known() not supported by target repository")
2069 raise util.Abort("known() not supported by target repository")
2070 flags = repo.known([bin(s) for s in ids])
2070 flags = repo.known([bin(s) for s in ids])
2071 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2071 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2072
2072
2073 @command('debugobsolete', [] + commitopts2,
2073 @command('debugobsolete', [] + commitopts2,
2074 _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
2074 _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
2075 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2075 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2076 """create arbitrary obsolete marker"""
2076 """create arbitrary obsolete marker"""
2077 def parsenodeid(s):
2077 def parsenodeid(s):
2078 try:
2078 try:
2079 # We do not use revsingle/revrange functions here to accept
2079 # We do not use revsingle/revrange functions here to accept
2080 # arbitrary node identifiers, possibly not present in the
2080 # arbitrary node identifiers, possibly not present in the
2081 # local repository.
2081 # local repository.
2082 n = bin(s)
2082 n = bin(s)
2083 if len(n) != len(nullid):
2083 if len(n) != len(nullid):
2084 raise TypeError()
2084 raise TypeError()
2085 return n
2085 return n
2086 except TypeError:
2086 except TypeError:
2087 raise util.Abort('changeset references must be full hexadecimal '
2087 raise util.Abort('changeset references must be full hexadecimal '
2088 'node identifiers')
2088 'node identifiers')
2089
2089
2090 if precursor is not None:
2090 if precursor is not None:
2091 metadata = {}
2091 metadata = {}
2092 if 'date' in opts:
2092 if 'date' in opts:
2093 metadata['date'] = opts['date']
2093 metadata['date'] = opts['date']
2094 metadata['user'] = opts['user'] or ui.username()
2094 metadata['user'] = opts['user'] or ui.username()
2095 succs = tuple(parsenodeid(succ) for succ in successors)
2095 succs = tuple(parsenodeid(succ) for succ in successors)
2096 l = repo.lock()
2096 l = repo.lock()
2097 try:
2097 try:
2098 tr = repo.transaction('debugobsolete')
2098 tr = repo.transaction('debugobsolete')
2099 try:
2099 try:
2100 repo.obsstore.create(tr, parsenodeid(precursor), succs, 0,
2100 repo.obsstore.create(tr, parsenodeid(precursor), succs, 0,
2101 metadata)
2101 metadata)
2102 tr.close()
2102 tr.close()
2103 finally:
2103 finally:
2104 tr.release()
2104 tr.release()
2105 finally:
2105 finally:
2106 l.release()
2106 l.release()
2107 else:
2107 else:
2108 for m in obsolete.allmarkers(repo):
2108 for m in obsolete.allmarkers(repo):
2109 ui.write(hex(m.precnode()))
2109 ui.write(hex(m.precnode()))
2110 for repl in m.succnodes():
2110 for repl in m.succnodes():
2111 ui.write(' ')
2111 ui.write(' ')
2112 ui.write(hex(repl))
2112 ui.write(hex(repl))
2113 ui.write(' %X ' % m._data[2])
2113 ui.write(' %X ' % m._data[2])
2114 ui.write(m.metadata())
2114 ui.write(m.metadata())
2115 ui.write('\n')
2115 ui.write('\n')
2116
2116
2117 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
2117 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
2118 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2118 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2119 '''access the pushkey key/value protocol
2119 '''access the pushkey key/value protocol
2120
2120
2121 With two args, list the keys in the given namespace.
2121 With two args, list the keys in the given namespace.
2122
2122
2123 With five args, set a key to new if it currently is set to old.
2123 With five args, set a key to new if it currently is set to old.
2124 Reports success or failure.
2124 Reports success or failure.
2125 '''
2125 '''
2126
2126
2127 target = hg.peer(ui, {}, repopath)
2127 target = hg.peer(ui, {}, repopath)
2128 if keyinfo:
2128 if keyinfo:
2129 key, old, new = keyinfo
2129 key, old, new = keyinfo
2130 r = target.pushkey(namespace, key, old, new)
2130 r = target.pushkey(namespace, key, old, new)
2131 ui.status(str(r) + '\n')
2131 ui.status(str(r) + '\n')
2132 return not r
2132 return not r
2133 else:
2133 else:
2134 for k, v in target.listkeys(namespace).iteritems():
2134 for k, v in target.listkeys(namespace).iteritems():
2135 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2135 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2136 v.encode('string-escape')))
2136 v.encode('string-escape')))
2137
2137
2138 @command('debugpvec', [], _('A B'))
2138 @command('debugpvec', [], _('A B'))
2139 def debugpvec(ui, repo, a, b=None):
2139 def debugpvec(ui, repo, a, b=None):
2140 ca = scmutil.revsingle(repo, a)
2140 ca = scmutil.revsingle(repo, a)
2141 cb = scmutil.revsingle(repo, b)
2141 cb = scmutil.revsingle(repo, b)
2142 pa = pvec.ctxpvec(ca)
2142 pa = pvec.ctxpvec(ca)
2143 pb = pvec.ctxpvec(cb)
2143 pb = pvec.ctxpvec(cb)
2144 if pa == pb:
2144 if pa == pb:
2145 rel = "="
2145 rel = "="
2146 elif pa > pb:
2146 elif pa > pb:
2147 rel = ">"
2147 rel = ">"
2148 elif pa < pb:
2148 elif pa < pb:
2149 rel = "<"
2149 rel = "<"
2150 elif pa | pb:
2150 elif pa | pb:
2151 rel = "|"
2151 rel = "|"
2152 ui.write(_("a: %s\n") % pa)
2152 ui.write(_("a: %s\n") % pa)
2153 ui.write(_("b: %s\n") % pb)
2153 ui.write(_("b: %s\n") % pb)
2154 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2154 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2155 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2155 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2156 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2156 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2157 pa.distance(pb), rel))
2157 pa.distance(pb), rel))
2158
2158
2159 @command('debugrebuildstate',
2159 @command('debugrebuildstate',
2160 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2160 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2161 _('[-r REV] [REV]'))
2161 _('[-r REV] [REV]'))
2162 def debugrebuildstate(ui, repo, rev="tip"):
2162 def debugrebuildstate(ui, repo, rev="tip"):
2163 """rebuild the dirstate as it would look like for the given revision"""
2163 """rebuild the dirstate as it would look like for the given revision"""
2164 ctx = scmutil.revsingle(repo, rev)
2164 ctx = scmutil.revsingle(repo, rev)
2165 wlock = repo.wlock()
2165 wlock = repo.wlock()
2166 try:
2166 try:
2167 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2167 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2168 finally:
2168 finally:
2169 wlock.release()
2169 wlock.release()
2170
2170
2171 @command('debugrename',
2171 @command('debugrename',
2172 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2172 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2173 _('[-r REV] FILE'))
2173 _('[-r REV] FILE'))
2174 def debugrename(ui, repo, file1, *pats, **opts):
2174 def debugrename(ui, repo, file1, *pats, **opts):
2175 """dump rename information"""
2175 """dump rename information"""
2176
2176
2177 ctx = scmutil.revsingle(repo, opts.get('rev'))
2177 ctx = scmutil.revsingle(repo, opts.get('rev'))
2178 m = scmutil.match(ctx, (file1,) + pats, opts)
2178 m = scmutil.match(ctx, (file1,) + pats, opts)
2179 for abs in ctx.walk(m):
2179 for abs in ctx.walk(m):
2180 fctx = ctx[abs]
2180 fctx = ctx[abs]
2181 o = fctx.filelog().renamed(fctx.filenode())
2181 o = fctx.filelog().renamed(fctx.filenode())
2182 rel = m.rel(abs)
2182 rel = m.rel(abs)
2183 if o:
2183 if o:
2184 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2184 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2185 else:
2185 else:
2186 ui.write(_("%s not renamed\n") % rel)
2186 ui.write(_("%s not renamed\n") % rel)
2187
2187
2188 @command('debugrevlog',
2188 @command('debugrevlog',
2189 [('c', 'changelog', False, _('open changelog')),
2189 [('c', 'changelog', False, _('open changelog')),
2190 ('m', 'manifest', False, _('open manifest')),
2190 ('m', 'manifest', False, _('open manifest')),
2191 ('d', 'dump', False, _('dump index data'))],
2191 ('d', 'dump', False, _('dump index data'))],
2192 _('-c|-m|FILE'))
2192 _('-c|-m|FILE'))
2193 def debugrevlog(ui, repo, file_ = None, **opts):
2193 def debugrevlog(ui, repo, file_ = None, **opts):
2194 """show data and statistics about a revlog"""
2194 """show data and statistics about a revlog"""
2195 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2195 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2196
2196
2197 if opts.get("dump"):
2197 if opts.get("dump"):
2198 numrevs = len(r)
2198 numrevs = len(r)
2199 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2199 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2200 " rawsize totalsize compression heads\n")
2200 " rawsize totalsize compression heads\n")
2201 ts = 0
2201 ts = 0
2202 heads = set()
2202 heads = set()
2203 for rev in xrange(numrevs):
2203 for rev in xrange(numrevs):
2204 dbase = r.deltaparent(rev)
2204 dbase = r.deltaparent(rev)
2205 if dbase == -1:
2205 if dbase == -1:
2206 dbase = rev
2206 dbase = rev
2207 cbase = r.chainbase(rev)
2207 cbase = r.chainbase(rev)
2208 p1, p2 = r.parentrevs(rev)
2208 p1, p2 = r.parentrevs(rev)
2209 rs = r.rawsize(rev)
2209 rs = r.rawsize(rev)
2210 ts = ts + rs
2210 ts = ts + rs
2211 heads -= set(r.parentrevs(rev))
2211 heads -= set(r.parentrevs(rev))
2212 heads.add(rev)
2212 heads.add(rev)
2213 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
2213 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
2214 (rev, p1, p2, r.start(rev), r.end(rev),
2214 (rev, p1, p2, r.start(rev), r.end(rev),
2215 r.start(dbase), r.start(cbase),
2215 r.start(dbase), r.start(cbase),
2216 r.start(p1), r.start(p2),
2216 r.start(p1), r.start(p2),
2217 rs, ts, ts / r.end(rev), len(heads)))
2217 rs, ts, ts / r.end(rev), len(heads)))
2218 return 0
2218 return 0
2219
2219
2220 v = r.version
2220 v = r.version
2221 format = v & 0xFFFF
2221 format = v & 0xFFFF
2222 flags = []
2222 flags = []
2223 gdelta = False
2223 gdelta = False
2224 if v & revlog.REVLOGNGINLINEDATA:
2224 if v & revlog.REVLOGNGINLINEDATA:
2225 flags.append('inline')
2225 flags.append('inline')
2226 if v & revlog.REVLOGGENERALDELTA:
2226 if v & revlog.REVLOGGENERALDELTA:
2227 gdelta = True
2227 gdelta = True
2228 flags.append('generaldelta')
2228 flags.append('generaldelta')
2229 if not flags:
2229 if not flags:
2230 flags = ['(none)']
2230 flags = ['(none)']
2231
2231
2232 nummerges = 0
2232 nummerges = 0
2233 numfull = 0
2233 numfull = 0
2234 numprev = 0
2234 numprev = 0
2235 nump1 = 0
2235 nump1 = 0
2236 nump2 = 0
2236 nump2 = 0
2237 numother = 0
2237 numother = 0
2238 nump1prev = 0
2238 nump1prev = 0
2239 nump2prev = 0
2239 nump2prev = 0
2240 chainlengths = []
2240 chainlengths = []
2241
2241
2242 datasize = [None, 0, 0L]
2242 datasize = [None, 0, 0L]
2243 fullsize = [None, 0, 0L]
2243 fullsize = [None, 0, 0L]
2244 deltasize = [None, 0, 0L]
2244 deltasize = [None, 0, 0L]
2245
2245
2246 def addsize(size, l):
2246 def addsize(size, l):
2247 if l[0] is None or size < l[0]:
2247 if l[0] is None or size < l[0]:
2248 l[0] = size
2248 l[0] = size
2249 if size > l[1]:
2249 if size > l[1]:
2250 l[1] = size
2250 l[1] = size
2251 l[2] += size
2251 l[2] += size
2252
2252
2253 numrevs = len(r)
2253 numrevs = len(r)
2254 for rev in xrange(numrevs):
2254 for rev in xrange(numrevs):
2255 p1, p2 = r.parentrevs(rev)
2255 p1, p2 = r.parentrevs(rev)
2256 delta = r.deltaparent(rev)
2256 delta = r.deltaparent(rev)
2257 if format > 0:
2257 if format > 0:
2258 addsize(r.rawsize(rev), datasize)
2258 addsize(r.rawsize(rev), datasize)
2259 if p2 != nullrev:
2259 if p2 != nullrev:
2260 nummerges += 1
2260 nummerges += 1
2261 size = r.length(rev)
2261 size = r.length(rev)
2262 if delta == nullrev:
2262 if delta == nullrev:
2263 chainlengths.append(0)
2263 chainlengths.append(0)
2264 numfull += 1
2264 numfull += 1
2265 addsize(size, fullsize)
2265 addsize(size, fullsize)
2266 else:
2266 else:
2267 chainlengths.append(chainlengths[delta] + 1)
2267 chainlengths.append(chainlengths[delta] + 1)
2268 addsize(size, deltasize)
2268 addsize(size, deltasize)
2269 if delta == rev - 1:
2269 if delta == rev - 1:
2270 numprev += 1
2270 numprev += 1
2271 if delta == p1:
2271 if delta == p1:
2272 nump1prev += 1
2272 nump1prev += 1
2273 elif delta == p2:
2273 elif delta == p2:
2274 nump2prev += 1
2274 nump2prev += 1
2275 elif delta == p1:
2275 elif delta == p1:
2276 nump1 += 1
2276 nump1 += 1
2277 elif delta == p2:
2277 elif delta == p2:
2278 nump2 += 1
2278 nump2 += 1
2279 elif delta != nullrev:
2279 elif delta != nullrev:
2280 numother += 1
2280 numother += 1
2281
2281
2282 # Adjust size min value for empty cases
2282 # Adjust size min value for empty cases
2283 for size in (datasize, fullsize, deltasize):
2283 for size in (datasize, fullsize, deltasize):
2284 if size[0] is None:
2284 if size[0] is None:
2285 size[0] = 0
2285 size[0] = 0
2286
2286
2287 numdeltas = numrevs - numfull
2287 numdeltas = numrevs - numfull
2288 numoprev = numprev - nump1prev - nump2prev
2288 numoprev = numprev - nump1prev - nump2prev
2289 totalrawsize = datasize[2]
2289 totalrawsize = datasize[2]
2290 datasize[2] /= numrevs
2290 datasize[2] /= numrevs
2291 fulltotal = fullsize[2]
2291 fulltotal = fullsize[2]
2292 fullsize[2] /= numfull
2292 fullsize[2] /= numfull
2293 deltatotal = deltasize[2]
2293 deltatotal = deltasize[2]
2294 if numrevs - numfull > 0:
2294 if numrevs - numfull > 0:
2295 deltasize[2] /= numrevs - numfull
2295 deltasize[2] /= numrevs - numfull
2296 totalsize = fulltotal + deltatotal
2296 totalsize = fulltotal + deltatotal
2297 avgchainlen = sum(chainlengths) / numrevs
2297 avgchainlen = sum(chainlengths) / numrevs
2298 compratio = totalrawsize / totalsize
2298 compratio = totalrawsize / totalsize
2299
2299
2300 basedfmtstr = '%%%dd\n'
2300 basedfmtstr = '%%%dd\n'
2301 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2301 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2302
2302
2303 def dfmtstr(max):
2303 def dfmtstr(max):
2304 return basedfmtstr % len(str(max))
2304 return basedfmtstr % len(str(max))
2305 def pcfmtstr(max, padding=0):
2305 def pcfmtstr(max, padding=0):
2306 return basepcfmtstr % (len(str(max)), ' ' * padding)
2306 return basepcfmtstr % (len(str(max)), ' ' * padding)
2307
2307
2308 def pcfmt(value, total):
2308 def pcfmt(value, total):
2309 return (value, 100 * float(value) / total)
2309 return (value, 100 * float(value) / total)
2310
2310
2311 ui.write('format : %d\n' % format)
2311 ui.write('format : %d\n' % format)
2312 ui.write('flags : %s\n' % ', '.join(flags))
2312 ui.write('flags : %s\n' % ', '.join(flags))
2313
2313
2314 ui.write('\n')
2314 ui.write('\n')
2315 fmt = pcfmtstr(totalsize)
2315 fmt = pcfmtstr(totalsize)
2316 fmt2 = dfmtstr(totalsize)
2316 fmt2 = dfmtstr(totalsize)
2317 ui.write('revisions : ' + fmt2 % numrevs)
2317 ui.write('revisions : ' + fmt2 % numrevs)
2318 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
2318 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
2319 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
2319 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
2320 ui.write('revisions : ' + fmt2 % numrevs)
2320 ui.write('revisions : ' + fmt2 % numrevs)
2321 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
2321 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
2322 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
2322 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
2323 ui.write('revision size : ' + fmt2 % totalsize)
2323 ui.write('revision size : ' + fmt2 % totalsize)
2324 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
2324 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
2325 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
2325 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
2326
2326
2327 ui.write('\n')
2327 ui.write('\n')
2328 fmt = dfmtstr(max(avgchainlen, compratio))
2328 fmt = dfmtstr(max(avgchainlen, compratio))
2329 ui.write('avg chain length : ' + fmt % avgchainlen)
2329 ui.write('avg chain length : ' + fmt % avgchainlen)
2330 ui.write('compression ratio : ' + fmt % compratio)
2330 ui.write('compression ratio : ' + fmt % compratio)
2331
2331
2332 if format > 0:
2332 if format > 0:
2333 ui.write('\n')
2333 ui.write('\n')
2334 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
2334 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
2335 % tuple(datasize))
2335 % tuple(datasize))
2336 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
2336 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
2337 % tuple(fullsize))
2337 % tuple(fullsize))
2338 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
2338 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
2339 % tuple(deltasize))
2339 % tuple(deltasize))
2340
2340
2341 if numdeltas > 0:
2341 if numdeltas > 0:
2342 ui.write('\n')
2342 ui.write('\n')
2343 fmt = pcfmtstr(numdeltas)
2343 fmt = pcfmtstr(numdeltas)
2344 fmt2 = pcfmtstr(numdeltas, 4)
2344 fmt2 = pcfmtstr(numdeltas, 4)
2345 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2345 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2346 if numprev > 0:
2346 if numprev > 0:
2347 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev,
2347 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev,
2348 numprev))
2348 numprev))
2349 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev,
2349 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev,
2350 numprev))
2350 numprev))
2351 ui.write(' other : ' + fmt2 % pcfmt(numoprev,
2351 ui.write(' other : ' + fmt2 % pcfmt(numoprev,
2352 numprev))
2352 numprev))
2353 if gdelta:
2353 if gdelta:
2354 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2354 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2355 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2355 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2356 ui.write('deltas against other : ' + fmt % pcfmt(numother,
2356 ui.write('deltas against other : ' + fmt % pcfmt(numother,
2357 numdeltas))
2357 numdeltas))
2358
2358
2359 @command('debugrevspec', [], ('REVSPEC'))
2359 @command('debugrevspec', [], ('REVSPEC'))
2360 def debugrevspec(ui, repo, expr):
2360 def debugrevspec(ui, repo, expr):
2361 """parse and apply a revision specification
2361 """parse and apply a revision specification
2362
2362
2363 Use --verbose to print the parsed tree before and after aliases
2363 Use --verbose to print the parsed tree before and after aliases
2364 expansion.
2364 expansion.
2365 """
2365 """
2366 if ui.verbose:
2366 if ui.verbose:
2367 tree = revset.parse(expr)[0]
2367 tree = revset.parse(expr)[0]
2368 ui.note(revset.prettyformat(tree), "\n")
2368 ui.note(revset.prettyformat(tree), "\n")
2369 newtree = revset.findaliases(ui, tree)
2369 newtree = revset.findaliases(ui, tree)
2370 if newtree != tree:
2370 if newtree != tree:
2371 ui.note(revset.prettyformat(newtree), "\n")
2371 ui.note(revset.prettyformat(newtree), "\n")
2372 func = revset.match(ui, expr)
2372 func = revset.match(ui, expr)
2373 for c in func(repo, range(len(repo))):
2373 for c in func(repo, range(len(repo))):
2374 ui.write("%s\n" % c)
2374 ui.write("%s\n" % c)
2375
2375
2376 @command('debugsetparents', [], _('REV1 [REV2]'))
2376 @command('debugsetparents', [], _('REV1 [REV2]'))
2377 def debugsetparents(ui, repo, rev1, rev2=None):
2377 def debugsetparents(ui, repo, rev1, rev2=None):
2378 """manually set the parents of the current working directory
2378 """manually set the parents of the current working directory
2379
2379
2380 This is useful for writing repository conversion tools, but should
2380 This is useful for writing repository conversion tools, but should
2381 be used with care.
2381 be used with care.
2382
2382
2383 Returns 0 on success.
2383 Returns 0 on success.
2384 """
2384 """
2385
2385
2386 r1 = scmutil.revsingle(repo, rev1).node()
2386 r1 = scmutil.revsingle(repo, rev1).node()
2387 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2387 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2388
2388
2389 wlock = repo.wlock()
2389 wlock = repo.wlock()
2390 try:
2390 try:
2391 repo.setparents(r1, r2)
2391 repo.setparents(r1, r2)
2392 finally:
2392 finally:
2393 wlock.release()
2393 wlock.release()
2394
2394
2395 @command('debugstate',
2395 @command('debugstate',
2396 [('', 'nodates', None, _('do not display the saved mtime')),
2396 [('', 'nodates', None, _('do not display the saved mtime')),
2397 ('', 'datesort', None, _('sort by saved mtime'))],
2397 ('', 'datesort', None, _('sort by saved mtime'))],
2398 _('[OPTION]...'))
2398 _('[OPTION]...'))
2399 def debugstate(ui, repo, nodates=None, datesort=None):
2399 def debugstate(ui, repo, nodates=None, datesort=None):
2400 """show the contents of the current dirstate"""
2400 """show the contents of the current dirstate"""
2401 timestr = ""
2401 timestr = ""
2402 showdate = not nodates
2402 showdate = not nodates
2403 if datesort:
2403 if datesort:
2404 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2404 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2405 else:
2405 else:
2406 keyfunc = None # sort by filename
2406 keyfunc = None # sort by filename
2407 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2407 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2408 if showdate:
2408 if showdate:
2409 if ent[3] == -1:
2409 if ent[3] == -1:
2410 # Pad or slice to locale representation
2410 # Pad or slice to locale representation
2411 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2411 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2412 time.localtime(0)))
2412 time.localtime(0)))
2413 timestr = 'unset'
2413 timestr = 'unset'
2414 timestr = (timestr[:locale_len] +
2414 timestr = (timestr[:locale_len] +
2415 ' ' * (locale_len - len(timestr)))
2415 ' ' * (locale_len - len(timestr)))
2416 else:
2416 else:
2417 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2417 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2418 time.localtime(ent[3]))
2418 time.localtime(ent[3]))
2419 if ent[1] & 020000:
2419 if ent[1] & 020000:
2420 mode = 'lnk'
2420 mode = 'lnk'
2421 else:
2421 else:
2422 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2422 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2423 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2423 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2424 for f in repo.dirstate.copies():
2424 for f in repo.dirstate.copies():
2425 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2425 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2426
2426
2427 @command('debugsub',
2427 @command('debugsub',
2428 [('r', 'rev', '',
2428 [('r', 'rev', '',
2429 _('revision to check'), _('REV'))],
2429 _('revision to check'), _('REV'))],
2430 _('[-r REV] [REV]'))
2430 _('[-r REV] [REV]'))
2431 def debugsub(ui, repo, rev=None):
2431 def debugsub(ui, repo, rev=None):
2432 ctx = scmutil.revsingle(repo, rev, None)
2432 ctx = scmutil.revsingle(repo, rev, None)
2433 for k, v in sorted(ctx.substate.items()):
2433 for k, v in sorted(ctx.substate.items()):
2434 ui.write('path %s\n' % k)
2434 ui.write('path %s\n' % k)
2435 ui.write(' source %s\n' % v[0])
2435 ui.write(' source %s\n' % v[0])
2436 ui.write(' revision %s\n' % v[1])
2436 ui.write(' revision %s\n' % v[1])
2437
2437
2438 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2438 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2439 def debugwalk(ui, repo, *pats, **opts):
2439 def debugwalk(ui, repo, *pats, **opts):
2440 """show how files match on given patterns"""
2440 """show how files match on given patterns"""
2441 m = scmutil.match(repo[None], pats, opts)
2441 m = scmutil.match(repo[None], pats, opts)
2442 items = list(repo.walk(m))
2442 items = list(repo.walk(m))
2443 if not items:
2443 if not items:
2444 return
2444 return
2445 f = lambda fn: fn
2445 f = lambda fn: fn
2446 if ui.configbool('ui', 'slash') and os.sep != '/':
2446 if ui.configbool('ui', 'slash') and os.sep != '/':
2447 f = lambda fn: util.normpath(fn)
2447 f = lambda fn: util.normpath(fn)
2448 fmt = 'f %%-%ds %%-%ds %%s' % (
2448 fmt = 'f %%-%ds %%-%ds %%s' % (
2449 max([len(abs) for abs in items]),
2449 max([len(abs) for abs in items]),
2450 max([len(m.rel(abs)) for abs in items]))
2450 max([len(m.rel(abs)) for abs in items]))
2451 for abs in items:
2451 for abs in items:
2452 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2452 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2453 ui.write("%s\n" % line.rstrip())
2453 ui.write("%s\n" % line.rstrip())
2454
2454
2455 @command('debugwireargs',
2455 @command('debugwireargs',
2456 [('', 'three', '', 'three'),
2456 [('', 'three', '', 'three'),
2457 ('', 'four', '', 'four'),
2457 ('', 'four', '', 'four'),
2458 ('', 'five', '', 'five'),
2458 ('', 'five', '', 'five'),
2459 ] + remoteopts,
2459 ] + remoteopts,
2460 _('REPO [OPTIONS]... [ONE [TWO]]'))
2460 _('REPO [OPTIONS]... [ONE [TWO]]'))
2461 def debugwireargs(ui, repopath, *vals, **opts):
2461 def debugwireargs(ui, repopath, *vals, **opts):
2462 repo = hg.peer(ui, opts, repopath)
2462 repo = hg.peer(ui, opts, repopath)
2463 for opt in remoteopts:
2463 for opt in remoteopts:
2464 del opts[opt[1]]
2464 del opts[opt[1]]
2465 args = {}
2465 args = {}
2466 for k, v in opts.iteritems():
2466 for k, v in opts.iteritems():
2467 if v:
2467 if v:
2468 args[k] = v
2468 args[k] = v
2469 # run twice to check that we don't mess up the stream for the next command
2469 # run twice to check that we don't mess up the stream for the next command
2470 res1 = repo.debugwireargs(*vals, **args)
2470 res1 = repo.debugwireargs(*vals, **args)
2471 res2 = repo.debugwireargs(*vals, **args)
2471 res2 = repo.debugwireargs(*vals, **args)
2472 ui.write("%s\n" % res1)
2472 ui.write("%s\n" % res1)
2473 if res1 != res2:
2473 if res1 != res2:
2474 ui.warn("%s\n" % res2)
2474 ui.warn("%s\n" % res2)
2475
2475
2476 @command('^diff',
2476 @command('^diff',
2477 [('r', 'rev', [], _('revision'), _('REV')),
2477 [('r', 'rev', [], _('revision'), _('REV')),
2478 ('c', 'change', '', _('change made by revision'), _('REV'))
2478 ('c', 'change', '', _('change made by revision'), _('REV'))
2479 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2479 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2480 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2480 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2481 def diff(ui, repo, *pats, **opts):
2481 def diff(ui, repo, *pats, **opts):
2482 """diff repository (or selected files)
2482 """diff repository (or selected files)
2483
2483
2484 Show differences between revisions for the specified files.
2484 Show differences between revisions for the specified files.
2485
2485
2486 Differences between files are shown using the unified diff format.
2486 Differences between files are shown using the unified diff format.
2487
2487
2488 .. note::
2488 .. note::
2489 diff may generate unexpected results for merges, as it will
2489 diff may generate unexpected results for merges, as it will
2490 default to comparing against the working directory's first
2490 default to comparing against the working directory's first
2491 parent changeset if no revisions are specified.
2491 parent changeset if no revisions are specified.
2492
2492
2493 When two revision arguments are given, then changes are shown
2493 When two revision arguments are given, then changes are shown
2494 between those revisions. If only one revision is specified then
2494 between those revisions. If only one revision is specified then
2495 that revision is compared to the working directory, and, when no
2495 that revision is compared to the working directory, and, when no
2496 revisions are specified, the working directory files are compared
2496 revisions are specified, the working directory files are compared
2497 to its parent.
2497 to its parent.
2498
2498
2499 Alternatively you can specify -c/--change with a revision to see
2499 Alternatively you can specify -c/--change with a revision to see
2500 the changes in that changeset relative to its first parent.
2500 the changes in that changeset relative to its first parent.
2501
2501
2502 Without the -a/--text option, diff will avoid generating diffs of
2502 Without the -a/--text option, diff will avoid generating diffs of
2503 files it detects as binary. With -a, diff will generate a diff
2503 files it detects as binary. With -a, diff will generate a diff
2504 anyway, probably with undesirable results.
2504 anyway, probably with undesirable results.
2505
2505
2506 Use the -g/--git option to generate diffs in the git extended diff
2506 Use the -g/--git option to generate diffs in the git extended diff
2507 format. For more information, read :hg:`help diffs`.
2507 format. For more information, read :hg:`help diffs`.
2508
2508
2509 .. container:: verbose
2509 .. container:: verbose
2510
2510
2511 Examples:
2511 Examples:
2512
2512
2513 - compare a file in the current working directory to its parent::
2513 - compare a file in the current working directory to its parent::
2514
2514
2515 hg diff foo.c
2515 hg diff foo.c
2516
2516
2517 - compare two historical versions of a directory, with rename info::
2517 - compare two historical versions of a directory, with rename info::
2518
2518
2519 hg diff --git -r 1.0:1.2 lib/
2519 hg diff --git -r 1.0:1.2 lib/
2520
2520
2521 - get change stats relative to the last change on some date::
2521 - get change stats relative to the last change on some date::
2522
2522
2523 hg diff --stat -r "date('may 2')"
2523 hg diff --stat -r "date('may 2')"
2524
2524
2525 - diff all newly-added files that contain a keyword::
2525 - diff all newly-added files that contain a keyword::
2526
2526
2527 hg diff "set:added() and grep(GNU)"
2527 hg diff "set:added() and grep(GNU)"
2528
2528
2529 - compare a revision and its parents::
2529 - compare a revision and its parents::
2530
2530
2531 hg diff -c 9353 # compare against first parent
2531 hg diff -c 9353 # compare against first parent
2532 hg diff -r 9353^:9353 # same using revset syntax
2532 hg diff -r 9353^:9353 # same using revset syntax
2533 hg diff -r 9353^2:9353 # compare against the second parent
2533 hg diff -r 9353^2:9353 # compare against the second parent
2534
2534
2535 Returns 0 on success.
2535 Returns 0 on success.
2536 """
2536 """
2537
2537
2538 revs = opts.get('rev')
2538 revs = opts.get('rev')
2539 change = opts.get('change')
2539 change = opts.get('change')
2540 stat = opts.get('stat')
2540 stat = opts.get('stat')
2541 reverse = opts.get('reverse')
2541 reverse = opts.get('reverse')
2542
2542
2543 if revs and change:
2543 if revs and change:
2544 msg = _('cannot specify --rev and --change at the same time')
2544 msg = _('cannot specify --rev and --change at the same time')
2545 raise util.Abort(msg)
2545 raise util.Abort(msg)
2546 elif change:
2546 elif change:
2547 node2 = scmutil.revsingle(repo, change, None).node()
2547 node2 = scmutil.revsingle(repo, change, None).node()
2548 node1 = repo[node2].p1().node()
2548 node1 = repo[node2].p1().node()
2549 else:
2549 else:
2550 node1, node2 = scmutil.revpair(repo, revs)
2550 node1, node2 = scmutil.revpair(repo, revs)
2551
2551
2552 if reverse:
2552 if reverse:
2553 node1, node2 = node2, node1
2553 node1, node2 = node2, node1
2554
2554
2555 diffopts = patch.diffopts(ui, opts)
2555 diffopts = patch.diffopts(ui, opts)
2556 m = scmutil.match(repo[node2], pats, opts)
2556 m = scmutil.match(repo[node2], pats, opts)
2557 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2557 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2558 listsubrepos=opts.get('subrepos'))
2558 listsubrepos=opts.get('subrepos'))
2559
2559
2560 @command('^export',
2560 @command('^export',
2561 [('o', 'output', '',
2561 [('o', 'output', '',
2562 _('print output to file with formatted name'), _('FORMAT')),
2562 _('print output to file with formatted name'), _('FORMAT')),
2563 ('', 'switch-parent', None, _('diff against the second parent')),
2563 ('', 'switch-parent', None, _('diff against the second parent')),
2564 ('r', 'rev', [], _('revisions to export'), _('REV')),
2564 ('r', 'rev', [], _('revisions to export'), _('REV')),
2565 ] + diffopts,
2565 ] + diffopts,
2566 _('[OPTION]... [-o OUTFILESPEC] [-r] REV...'))
2566 _('[OPTION]... [-o OUTFILESPEC] [-r] REV...'))
2567 def export(ui, repo, *changesets, **opts):
2567 def export(ui, repo, *changesets, **opts):
2568 """dump the header and diffs for one or more changesets
2568 """dump the header and diffs for one or more changesets
2569
2569
2570 Print the changeset header and diffs for one or more revisions.
2570 Print the changeset header and diffs for one or more revisions.
2571
2571
2572 The information shown in the changeset header is: author, date,
2572 The information shown in the changeset header is: author, date,
2573 branch name (if non-default), changeset hash, parent(s) and commit
2573 branch name (if non-default), changeset hash, parent(s) and commit
2574 comment.
2574 comment.
2575
2575
2576 .. note::
2576 .. note::
2577 export may generate unexpected diff output for merge
2577 export may generate unexpected diff output for merge
2578 changesets, as it will compare the merge changeset against its
2578 changesets, as it will compare the merge changeset against its
2579 first parent only.
2579 first parent only.
2580
2580
2581 Output may be to a file, in which case the name of the file is
2581 Output may be to a file, in which case the name of the file is
2582 given using a format string. The formatting rules are as follows:
2582 given using a format string. The formatting rules are as follows:
2583
2583
2584 :``%%``: literal "%" character
2584 :``%%``: literal "%" character
2585 :``%H``: changeset hash (40 hexadecimal digits)
2585 :``%H``: changeset hash (40 hexadecimal digits)
2586 :``%N``: number of patches being generated
2586 :``%N``: number of patches being generated
2587 :``%R``: changeset revision number
2587 :``%R``: changeset revision number
2588 :``%b``: basename of the exporting repository
2588 :``%b``: basename of the exporting repository
2589 :``%h``: short-form changeset hash (12 hexadecimal digits)
2589 :``%h``: short-form changeset hash (12 hexadecimal digits)
2590 :``%m``: first line of the commit message (only alphanumeric characters)
2590 :``%m``: first line of the commit message (only alphanumeric characters)
2591 :``%n``: zero-padded sequence number, starting at 1
2591 :``%n``: zero-padded sequence number, starting at 1
2592 :``%r``: zero-padded changeset revision number
2592 :``%r``: zero-padded changeset revision number
2593
2593
2594 Without the -a/--text option, export will avoid generating diffs
2594 Without the -a/--text option, export will avoid generating diffs
2595 of files it detects as binary. With -a, export will generate a
2595 of files it detects as binary. With -a, export will generate a
2596 diff anyway, probably with undesirable results.
2596 diff anyway, probably with undesirable results.
2597
2597
2598 Use the -g/--git option to generate diffs in the git extended diff
2598 Use the -g/--git option to generate diffs in the git extended diff
2599 format. See :hg:`help diffs` for more information.
2599 format. See :hg:`help diffs` for more information.
2600
2600
2601 With the --switch-parent option, the diff will be against the
2601 With the --switch-parent option, the diff will be against the
2602 second parent. It can be useful to review a merge.
2602 second parent. It can be useful to review a merge.
2603
2603
2604 .. container:: verbose
2604 .. container:: verbose
2605
2605
2606 Examples:
2606 Examples:
2607
2607
2608 - use export and import to transplant a bugfix to the current
2608 - use export and import to transplant a bugfix to the current
2609 branch::
2609 branch::
2610
2610
2611 hg export -r 9353 | hg import -
2611 hg export -r 9353 | hg import -
2612
2612
2613 - export all the changesets between two revisions to a file with
2613 - export all the changesets between two revisions to a file with
2614 rename information::
2614 rename information::
2615
2615
2616 hg export --git -r 123:150 > changes.txt
2616 hg export --git -r 123:150 > changes.txt
2617
2617
2618 - split outgoing changes into a series of patches with
2618 - split outgoing changes into a series of patches with
2619 descriptive names::
2619 descriptive names::
2620
2620
2621 hg export -r "outgoing()" -o "%n-%m.patch"
2621 hg export -r "outgoing()" -o "%n-%m.patch"
2622
2622
2623 Returns 0 on success.
2623 Returns 0 on success.
2624 """
2624 """
2625 changesets += tuple(opts.get('rev', []))
2625 changesets += tuple(opts.get('rev', []))
2626 revs = scmutil.revrange(repo, changesets)
2626 revs = scmutil.revrange(repo, changesets)
2627 if not revs:
2627 if not revs:
2628 raise util.Abort(_("export requires at least one changeset"))
2628 raise util.Abort(_("export requires at least one changeset"))
2629 if len(revs) > 1:
2629 if len(revs) > 1:
2630 ui.note(_('exporting patches:\n'))
2630 ui.note(_('exporting patches:\n'))
2631 else:
2631 else:
2632 ui.note(_('exporting patch:\n'))
2632 ui.note(_('exporting patch:\n'))
2633 cmdutil.export(repo, revs, template=opts.get('output'),
2633 cmdutil.export(repo, revs, template=opts.get('output'),
2634 switch_parent=opts.get('switch_parent'),
2634 switch_parent=opts.get('switch_parent'),
2635 opts=patch.diffopts(ui, opts))
2635 opts=patch.diffopts(ui, opts))
2636
2636
2637 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2637 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2638 def forget(ui, repo, *pats, **opts):
2638 def forget(ui, repo, *pats, **opts):
2639 """forget the specified files on the next commit
2639 """forget the specified files on the next commit
2640
2640
2641 Mark the specified files so they will no longer be tracked
2641 Mark the specified files so they will no longer be tracked
2642 after the next commit.
2642 after the next commit.
2643
2643
2644 This only removes files from the current branch, not from the
2644 This only removes files from the current branch, not from the
2645 entire project history, and it does not delete them from the
2645 entire project history, and it does not delete them from the
2646 working directory.
2646 working directory.
2647
2647
2648 To undo a forget before the next commit, see :hg:`add`.
2648 To undo a forget before the next commit, see :hg:`add`.
2649
2649
2650 .. container:: verbose
2650 .. container:: verbose
2651
2651
2652 Examples:
2652 Examples:
2653
2653
2654 - forget newly-added binary files::
2654 - forget newly-added binary files::
2655
2655
2656 hg forget "set:added() and binary()"
2656 hg forget "set:added() and binary()"
2657
2657
2658 - forget files that would be excluded by .hgignore::
2658 - forget files that would be excluded by .hgignore::
2659
2659
2660 hg forget "set:hgignore()"
2660 hg forget "set:hgignore()"
2661
2661
2662 Returns 0 on success.
2662 Returns 0 on success.
2663 """
2663 """
2664
2664
2665 if not pats:
2665 if not pats:
2666 raise util.Abort(_('no files specified'))
2666 raise util.Abort(_('no files specified'))
2667
2667
2668 m = scmutil.match(repo[None], pats, opts)
2668 m = scmutil.match(repo[None], pats, opts)
2669 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2669 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2670 return rejected and 1 or 0
2670 return rejected and 1 or 0
2671
2671
2672 @command(
2672 @command(
2673 'graft',
2673 'graft',
2674 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2674 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2675 ('c', 'continue', False, _('resume interrupted graft')),
2675 ('c', 'continue', False, _('resume interrupted graft')),
2676 ('e', 'edit', False, _('invoke editor on commit messages')),
2676 ('e', 'edit', False, _('invoke editor on commit messages')),
2677 ('', 'log', None, _('append graft info to log message')),
2677 ('', 'log', None, _('append graft info to log message')),
2678 ('D', 'currentdate', False,
2678 ('D', 'currentdate', False,
2679 _('record the current date as commit date')),
2679 _('record the current date as commit date')),
2680 ('U', 'currentuser', False,
2680 ('U', 'currentuser', False,
2681 _('record the current user as committer'), _('DATE'))]
2681 _('record the current user as committer'), _('DATE'))]
2682 + commitopts2 + mergetoolopts + dryrunopts,
2682 + commitopts2 + mergetoolopts + dryrunopts,
2683 _('[OPTION]... [-r] REV...'))
2683 _('[OPTION]... [-r] REV...'))
2684 def graft(ui, repo, *revs, **opts):
2684 def graft(ui, repo, *revs, **opts):
2685 '''copy changes from other branches onto the current branch
2685 '''copy changes from other branches onto the current branch
2686
2686
2687 This command uses Mercurial's merge logic to copy individual
2687 This command uses Mercurial's merge logic to copy individual
2688 changes from other branches without merging branches in the
2688 changes from other branches without merging branches in the
2689 history graph. This is sometimes known as 'backporting' or
2689 history graph. This is sometimes known as 'backporting' or
2690 'cherry-picking'. By default, graft will copy user, date, and
2690 'cherry-picking'. By default, graft will copy user, date, and
2691 description from the source changesets.
2691 description from the source changesets.
2692
2692
2693 Changesets that are ancestors of the current revision, that have
2693 Changesets that are ancestors of the current revision, that have
2694 already been grafted, or that are merges will be skipped.
2694 already been grafted, or that are merges will be skipped.
2695
2695
2696 If --log is specified, log messages will have a comment appended
2696 If --log is specified, log messages will have a comment appended
2697 of the form::
2697 of the form::
2698
2698
2699 (grafted from CHANGESETHASH)
2699 (grafted from CHANGESETHASH)
2700
2700
2701 If a graft merge results in conflicts, the graft process is
2701 If a graft merge results in conflicts, the graft process is
2702 interrupted so that the current merge can be manually resolved.
2702 interrupted so that the current merge can be manually resolved.
2703 Once all conflicts are addressed, the graft process can be
2703 Once all conflicts are addressed, the graft process can be
2704 continued with the -c/--continue option.
2704 continued with the -c/--continue option.
2705
2705
2706 .. note::
2706 .. note::
2707 The -c/--continue option does not reapply earlier options.
2707 The -c/--continue option does not reapply earlier options.
2708
2708
2709 .. container:: verbose
2709 .. container:: verbose
2710
2710
2711 Examples:
2711 Examples:
2712
2712
2713 - copy a single change to the stable branch and edit its description::
2713 - copy a single change to the stable branch and edit its description::
2714
2714
2715 hg update stable
2715 hg update stable
2716 hg graft --edit 9393
2716 hg graft --edit 9393
2717
2717
2718 - graft a range of changesets with one exception, updating dates::
2718 - graft a range of changesets with one exception, updating dates::
2719
2719
2720 hg graft -D "2085::2093 and not 2091"
2720 hg graft -D "2085::2093 and not 2091"
2721
2721
2722 - continue a graft after resolving conflicts::
2722 - continue a graft after resolving conflicts::
2723
2723
2724 hg graft -c
2724 hg graft -c
2725
2725
2726 - show the source of a grafted changeset::
2726 - show the source of a grafted changeset::
2727
2727
2728 hg log --debug -r tip
2728 hg log --debug -r tip
2729
2729
2730 Returns 0 on successful completion.
2730 Returns 0 on successful completion.
2731 '''
2731 '''
2732
2732
2733 revs = list(revs)
2733 revs = list(revs)
2734 revs.extend(opts['rev'])
2734 revs.extend(opts['rev'])
2735
2735
2736 if not opts.get('user') and opts.get('currentuser'):
2736 if not opts.get('user') and opts.get('currentuser'):
2737 opts['user'] = ui.username()
2737 opts['user'] = ui.username()
2738 if not opts.get('date') and opts.get('currentdate'):
2738 if not opts.get('date') and opts.get('currentdate'):
2739 opts['date'] = "%d %d" % util.makedate()
2739 opts['date'] = "%d %d" % util.makedate()
2740
2740
2741 editor = None
2741 editor = None
2742 if opts.get('edit'):
2742 if opts.get('edit'):
2743 editor = cmdutil.commitforceeditor
2743 editor = cmdutil.commitforceeditor
2744
2744
2745 cont = False
2745 cont = False
2746 if opts['continue']:
2746 if opts['continue']:
2747 cont = True
2747 cont = True
2748 if revs:
2748 if revs:
2749 raise util.Abort(_("can't specify --continue and revisions"))
2749 raise util.Abort(_("can't specify --continue and revisions"))
2750 # read in unfinished revisions
2750 # read in unfinished revisions
2751 try:
2751 try:
2752 nodes = repo.opener.read('graftstate').splitlines()
2752 nodes = repo.opener.read('graftstate').splitlines()
2753 revs = [repo[node].rev() for node in nodes]
2753 revs = [repo[node].rev() for node in nodes]
2754 except IOError, inst:
2754 except IOError, inst:
2755 if inst.errno != errno.ENOENT:
2755 if inst.errno != errno.ENOENT:
2756 raise
2756 raise
2757 raise util.Abort(_("no graft state found, can't continue"))
2757 raise util.Abort(_("no graft state found, can't continue"))
2758 else:
2758 else:
2759 cmdutil.bailifchanged(repo)
2759 cmdutil.bailifchanged(repo)
2760 if not revs:
2760 if not revs:
2761 raise util.Abort(_('no revisions specified'))
2761 raise util.Abort(_('no revisions specified'))
2762 revs = scmutil.revrange(repo, revs)
2762 revs = scmutil.revrange(repo, revs)
2763
2763
2764 # check for merges
2764 # check for merges
2765 for rev in repo.revs('%ld and merge()', revs):
2765 for rev in repo.revs('%ld and merge()', revs):
2766 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2766 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2767 revs.remove(rev)
2767 revs.remove(rev)
2768 if not revs:
2768 if not revs:
2769 return -1
2769 return -1
2770
2770
2771 # check for ancestors of dest branch
2771 # check for ancestors of dest branch
2772 for rev in repo.revs('::. and %ld', revs):
2772 for rev in repo.revs('::. and %ld', revs):
2773 ui.warn(_('skipping ancestor revision %s\n') % rev)
2773 ui.warn(_('skipping ancestor revision %s\n') % rev)
2774 revs.remove(rev)
2774 revs.remove(rev)
2775 if not revs:
2775 if not revs:
2776 return -1
2776 return -1
2777
2777
2778 # analyze revs for earlier grafts
2778 # analyze revs for earlier grafts
2779 ids = {}
2779 ids = {}
2780 for ctx in repo.set("%ld", revs):
2780 for ctx in repo.set("%ld", revs):
2781 ids[ctx.hex()] = ctx.rev()
2781 ids[ctx.hex()] = ctx.rev()
2782 n = ctx.extra().get('source')
2782 n = ctx.extra().get('source')
2783 if n:
2783 if n:
2784 ids[n] = ctx.rev()
2784 ids[n] = ctx.rev()
2785
2785
2786 # check ancestors for earlier grafts
2786 # check ancestors for earlier grafts
2787 ui.debug('scanning for duplicate grafts\n')
2787 ui.debug('scanning for duplicate grafts\n')
2788 for ctx in repo.set("::. - ::%ld", revs):
2788 for ctx in repo.set("::. - ::%ld", revs):
2789 n = ctx.extra().get('source')
2789 n = ctx.extra().get('source')
2790 if n in ids:
2790 if n in ids:
2791 r = repo[n].rev()
2791 r = repo[n].rev()
2792 if r in revs:
2792 if r in revs:
2793 ui.warn(_('skipping already grafted revision %s\n') % r)
2793 ui.warn(_('skipping already grafted revision %s\n') % r)
2794 revs.remove(r)
2794 revs.remove(r)
2795 elif ids[n] in revs:
2795 elif ids[n] in revs:
2796 ui.warn(_('skipping already grafted revision %s '
2796 ui.warn(_('skipping already grafted revision %s '
2797 '(same origin %d)\n') % (ids[n], r))
2797 '(same origin %d)\n') % (ids[n], r))
2798 revs.remove(ids[n])
2798 revs.remove(ids[n])
2799 elif ctx.hex() in ids:
2799 elif ctx.hex() in ids:
2800 r = ids[ctx.hex()]
2800 r = ids[ctx.hex()]
2801 ui.warn(_('skipping already grafted revision %s '
2801 ui.warn(_('skipping already grafted revision %s '
2802 '(was grafted from %d)\n') % (r, ctx.rev()))
2802 '(was grafted from %d)\n') % (r, ctx.rev()))
2803 revs.remove(r)
2803 revs.remove(r)
2804 if not revs:
2804 if not revs:
2805 return -1
2805 return -1
2806
2806
2807 wlock = repo.wlock()
2807 wlock = repo.wlock()
2808 try:
2808 try:
2809 for pos, ctx in enumerate(repo.set("%ld", revs)):
2809 for pos, ctx in enumerate(repo.set("%ld", revs)):
2810 current = repo['.']
2810 current = repo['.']
2811
2811
2812 ui.status(_('grafting revision %s\n') % ctx.rev())
2812 ui.status(_('grafting revision %s\n') % ctx.rev())
2813 if opts.get('dry_run'):
2813 if opts.get('dry_run'):
2814 continue
2814 continue
2815
2815
2816 # we don't merge the first commit when continuing
2816 # we don't merge the first commit when continuing
2817 if not cont:
2817 if not cont:
2818 # perform the graft merge with p1(rev) as 'ancestor'
2818 # perform the graft merge with p1(rev) as 'ancestor'
2819 try:
2819 try:
2820 # ui.forcemerge is an internal variable, do not document
2820 # ui.forcemerge is an internal variable, do not document
2821 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2821 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2822 stats = mergemod.update(repo, ctx.node(), True, True, False,
2822 stats = mergemod.update(repo, ctx.node(), True, True, False,
2823 ctx.p1().node())
2823 ctx.p1().node())
2824 finally:
2824 finally:
2825 repo.ui.setconfig('ui', 'forcemerge', '')
2825 repo.ui.setconfig('ui', 'forcemerge', '')
2826 # report any conflicts
2826 # report any conflicts
2827 if stats and stats[3] > 0:
2827 if stats and stats[3] > 0:
2828 # write out state for --continue
2828 # write out state for --continue
2829 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2829 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2830 repo.opener.write('graftstate', ''.join(nodelines))
2830 repo.opener.write('graftstate', ''.join(nodelines))
2831 raise util.Abort(
2831 raise util.Abort(
2832 _("unresolved conflicts, can't continue"),
2832 _("unresolved conflicts, can't continue"),
2833 hint=_('use hg resolve and hg graft --continue'))
2833 hint=_('use hg resolve and hg graft --continue'))
2834 else:
2834 else:
2835 cont = False
2835 cont = False
2836
2836
2837 # drop the second merge parent
2837 # drop the second merge parent
2838 repo.setparents(current.node(), nullid)
2838 repo.setparents(current.node(), nullid)
2839 repo.dirstate.write()
2839 repo.dirstate.write()
2840 # fix up dirstate for copies and renames
2840 # fix up dirstate for copies and renames
2841 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
2841 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
2842
2842
2843 # commit
2843 # commit
2844 source = ctx.extra().get('source')
2844 source = ctx.extra().get('source')
2845 if not source:
2845 if not source:
2846 source = ctx.hex()
2846 source = ctx.hex()
2847 extra = {'source': source}
2847 extra = {'source': source}
2848 user = ctx.user()
2848 user = ctx.user()
2849 if opts.get('user'):
2849 if opts.get('user'):
2850 user = opts['user']
2850 user = opts['user']
2851 date = ctx.date()
2851 date = ctx.date()
2852 if opts.get('date'):
2852 if opts.get('date'):
2853 date = opts['date']
2853 date = opts['date']
2854 message = ctx.description()
2854 message = ctx.description()
2855 if opts.get('log'):
2855 if opts.get('log'):
2856 message += '\n(grafted from %s)' % ctx.hex()
2856 message += '\n(grafted from %s)' % ctx.hex()
2857 node = repo.commit(text=message, user=user,
2857 node = repo.commit(text=message, user=user,
2858 date=date, extra=extra, editor=editor)
2858 date=date, extra=extra, editor=editor)
2859 if node is None:
2859 if node is None:
2860 ui.status(_('graft for revision %s is empty\n') % ctx.rev())
2860 ui.status(_('graft for revision %s is empty\n') % ctx.rev())
2861 finally:
2861 finally:
2862 wlock.release()
2862 wlock.release()
2863
2863
2864 # remove state when we complete successfully
2864 # remove state when we complete successfully
2865 if not opts.get('dry_run') and os.path.exists(repo.join('graftstate')):
2865 if not opts.get('dry_run') and os.path.exists(repo.join('graftstate')):
2866 util.unlinkpath(repo.join('graftstate'))
2866 util.unlinkpath(repo.join('graftstate'))
2867
2867
2868 return 0
2868 return 0
2869
2869
2870 @command('grep',
2870 @command('grep',
2871 [('0', 'print0', None, _('end fields with NUL')),
2871 [('0', 'print0', None, _('end fields with NUL')),
2872 ('', 'all', None, _('print all revisions that match')),
2872 ('', 'all', None, _('print all revisions that match')),
2873 ('a', 'text', None, _('treat all files as text')),
2873 ('a', 'text', None, _('treat all files as text')),
2874 ('f', 'follow', None,
2874 ('f', 'follow', None,
2875 _('follow changeset history,'
2875 _('follow changeset history,'
2876 ' or file history across copies and renames')),
2876 ' or file history across copies and renames')),
2877 ('i', 'ignore-case', None, _('ignore case when matching')),
2877 ('i', 'ignore-case', None, _('ignore case when matching')),
2878 ('l', 'files-with-matches', None,
2878 ('l', 'files-with-matches', None,
2879 _('print only filenames and revisions that match')),
2879 _('print only filenames and revisions that match')),
2880 ('n', 'line-number', None, _('print matching line numbers')),
2880 ('n', 'line-number', None, _('print matching line numbers')),
2881 ('r', 'rev', [],
2881 ('r', 'rev', [],
2882 _('only search files changed within revision range'), _('REV')),
2882 _('only search files changed within revision range'), _('REV')),
2883 ('u', 'user', None, _('list the author (long with -v)')),
2883 ('u', 'user', None, _('list the author (long with -v)')),
2884 ('d', 'date', None, _('list the date (short with -q)')),
2884 ('d', 'date', None, _('list the date (short with -q)')),
2885 ] + walkopts,
2885 ] + walkopts,
2886 _('[OPTION]... PATTERN [FILE]...'))
2886 _('[OPTION]... PATTERN [FILE]...'))
2887 def grep(ui, repo, pattern, *pats, **opts):
2887 def grep(ui, repo, pattern, *pats, **opts):
2888 """search for a pattern in specified files and revisions
2888 """search for a pattern in specified files and revisions
2889
2889
2890 Search revisions of files for a regular expression.
2890 Search revisions of files for a regular expression.
2891
2891
2892 This command behaves differently than Unix grep. It only accepts
2892 This command behaves differently than Unix grep. It only accepts
2893 Python/Perl regexps. It searches repository history, not the
2893 Python/Perl regexps. It searches repository history, not the
2894 working directory. It always prints the revision number in which a
2894 working directory. It always prints the revision number in which a
2895 match appears.
2895 match appears.
2896
2896
2897 By default, grep only prints output for the first revision of a
2897 By default, grep only prints output for the first revision of a
2898 file in which it finds a match. To get it to print every revision
2898 file in which it finds a match. To get it to print every revision
2899 that contains a change in match status ("-" for a match that
2899 that contains a change in match status ("-" for a match that
2900 becomes a non-match, or "+" for a non-match that becomes a match),
2900 becomes a non-match, or "+" for a non-match that becomes a match),
2901 use the --all flag.
2901 use the --all flag.
2902
2902
2903 Returns 0 if a match is found, 1 otherwise.
2903 Returns 0 if a match is found, 1 otherwise.
2904 """
2904 """
2905 reflags = re.M
2905 reflags = re.M
2906 if opts.get('ignore_case'):
2906 if opts.get('ignore_case'):
2907 reflags |= re.I
2907 reflags |= re.I
2908 try:
2908 try:
2909 regexp = re.compile(pattern, reflags)
2909 regexp = re.compile(pattern, reflags)
2910 except re.error, inst:
2910 except re.error, inst:
2911 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2911 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2912 return 1
2912 return 1
2913 sep, eol = ':', '\n'
2913 sep, eol = ':', '\n'
2914 if opts.get('print0'):
2914 if opts.get('print0'):
2915 sep = eol = '\0'
2915 sep = eol = '\0'
2916
2916
2917 getfile = util.lrucachefunc(repo.file)
2917 getfile = util.lrucachefunc(repo.file)
2918
2918
2919 def matchlines(body):
2919 def matchlines(body):
2920 begin = 0
2920 begin = 0
2921 linenum = 0
2921 linenum = 0
2922 while True:
2922 while True:
2923 match = regexp.search(body, begin)
2923 match = regexp.search(body, begin)
2924 if not match:
2924 if not match:
2925 break
2925 break
2926 mstart, mend = match.span()
2926 mstart, mend = match.span()
2927 linenum += body.count('\n', begin, mstart) + 1
2927 linenum += body.count('\n', begin, mstart) + 1
2928 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2928 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2929 begin = body.find('\n', mend) + 1 or len(body) + 1
2929 begin = body.find('\n', mend) + 1 or len(body) + 1
2930 lend = begin - 1
2930 lend = begin - 1
2931 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2931 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2932
2932
2933 class linestate(object):
2933 class linestate(object):
2934 def __init__(self, line, linenum, colstart, colend):
2934 def __init__(self, line, linenum, colstart, colend):
2935 self.line = line
2935 self.line = line
2936 self.linenum = linenum
2936 self.linenum = linenum
2937 self.colstart = colstart
2937 self.colstart = colstart
2938 self.colend = colend
2938 self.colend = colend
2939
2939
2940 def __hash__(self):
2940 def __hash__(self):
2941 return hash((self.linenum, self.line))
2941 return hash((self.linenum, self.line))
2942
2942
2943 def __eq__(self, other):
2943 def __eq__(self, other):
2944 return self.line == other.line
2944 return self.line == other.line
2945
2945
2946 matches = {}
2946 matches = {}
2947 copies = {}
2947 copies = {}
2948 def grepbody(fn, rev, body):
2948 def grepbody(fn, rev, body):
2949 matches[rev].setdefault(fn, [])
2949 matches[rev].setdefault(fn, [])
2950 m = matches[rev][fn]
2950 m = matches[rev][fn]
2951 for lnum, cstart, cend, line in matchlines(body):
2951 for lnum, cstart, cend, line in matchlines(body):
2952 s = linestate(line, lnum, cstart, cend)
2952 s = linestate(line, lnum, cstart, cend)
2953 m.append(s)
2953 m.append(s)
2954
2954
2955 def difflinestates(a, b):
2955 def difflinestates(a, b):
2956 sm = difflib.SequenceMatcher(None, a, b)
2956 sm = difflib.SequenceMatcher(None, a, b)
2957 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2957 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2958 if tag == 'insert':
2958 if tag == 'insert':
2959 for i in xrange(blo, bhi):
2959 for i in xrange(blo, bhi):
2960 yield ('+', b[i])
2960 yield ('+', b[i])
2961 elif tag == 'delete':
2961 elif tag == 'delete':
2962 for i in xrange(alo, ahi):
2962 for i in xrange(alo, ahi):
2963 yield ('-', a[i])
2963 yield ('-', a[i])
2964 elif tag == 'replace':
2964 elif tag == 'replace':
2965 for i in xrange(alo, ahi):
2965 for i in xrange(alo, ahi):
2966 yield ('-', a[i])
2966 yield ('-', a[i])
2967 for i in xrange(blo, bhi):
2967 for i in xrange(blo, bhi):
2968 yield ('+', b[i])
2968 yield ('+', b[i])
2969
2969
2970 def display(fn, ctx, pstates, states):
2970 def display(fn, ctx, pstates, states):
2971 rev = ctx.rev()
2971 rev = ctx.rev()
2972 datefunc = ui.quiet and util.shortdate or util.datestr
2972 datefunc = ui.quiet and util.shortdate or util.datestr
2973 found = False
2973 found = False
2974 filerevmatches = {}
2974 filerevmatches = {}
2975 def binary():
2975 def binary():
2976 flog = getfile(fn)
2976 flog = getfile(fn)
2977 return util.binary(flog.read(ctx.filenode(fn)))
2977 return util.binary(flog.read(ctx.filenode(fn)))
2978
2978
2979 if opts.get('all'):
2979 if opts.get('all'):
2980 iter = difflinestates(pstates, states)
2980 iter = difflinestates(pstates, states)
2981 else:
2981 else:
2982 iter = [('', l) for l in states]
2982 iter = [('', l) for l in states]
2983 for change, l in iter:
2983 for change, l in iter:
2984 cols = [fn, str(rev)]
2984 cols = [fn, str(rev)]
2985 before, match, after = None, None, None
2985 before, match, after = None, None, None
2986 if opts.get('line_number'):
2986 if opts.get('line_number'):
2987 cols.append(str(l.linenum))
2987 cols.append(str(l.linenum))
2988 if opts.get('all'):
2988 if opts.get('all'):
2989 cols.append(change)
2989 cols.append(change)
2990 if opts.get('user'):
2990 if opts.get('user'):
2991 cols.append(ui.shortuser(ctx.user()))
2991 cols.append(ui.shortuser(ctx.user()))
2992 if opts.get('date'):
2992 if opts.get('date'):
2993 cols.append(datefunc(ctx.date()))
2993 cols.append(datefunc(ctx.date()))
2994 if opts.get('files_with_matches'):
2994 if opts.get('files_with_matches'):
2995 c = (fn, rev)
2995 c = (fn, rev)
2996 if c in filerevmatches:
2996 if c in filerevmatches:
2997 continue
2997 continue
2998 filerevmatches[c] = 1
2998 filerevmatches[c] = 1
2999 else:
2999 else:
3000 before = l.line[:l.colstart]
3000 before = l.line[:l.colstart]
3001 match = l.line[l.colstart:l.colend]
3001 match = l.line[l.colstart:l.colend]
3002 after = l.line[l.colend:]
3002 after = l.line[l.colend:]
3003 ui.write(sep.join(cols))
3003 ui.write(sep.join(cols))
3004 if before is not None:
3004 if before is not None:
3005 if not opts.get('text') and binary():
3005 if not opts.get('text') and binary():
3006 ui.write(sep + " Binary file matches")
3006 ui.write(sep + " Binary file matches")
3007 else:
3007 else:
3008 ui.write(sep + before)
3008 ui.write(sep + before)
3009 ui.write(match, label='grep.match')
3009 ui.write(match, label='grep.match')
3010 ui.write(after)
3010 ui.write(after)
3011 ui.write(eol)
3011 ui.write(eol)
3012 found = True
3012 found = True
3013 return found
3013 return found
3014
3014
3015 skip = {}
3015 skip = {}
3016 revfiles = {}
3016 revfiles = {}
3017 matchfn = scmutil.match(repo[None], pats, opts)
3017 matchfn = scmutil.match(repo[None], pats, opts)
3018 found = False
3018 found = False
3019 follow = opts.get('follow')
3019 follow = opts.get('follow')
3020
3020
3021 def prep(ctx, fns):
3021 def prep(ctx, fns):
3022 rev = ctx.rev()
3022 rev = ctx.rev()
3023 pctx = ctx.p1()
3023 pctx = ctx.p1()
3024 parent = pctx.rev()
3024 parent = pctx.rev()
3025 matches.setdefault(rev, {})
3025 matches.setdefault(rev, {})
3026 matches.setdefault(parent, {})
3026 matches.setdefault(parent, {})
3027 files = revfiles.setdefault(rev, [])
3027 files = revfiles.setdefault(rev, [])
3028 for fn in fns:
3028 for fn in fns:
3029 flog = getfile(fn)
3029 flog = getfile(fn)
3030 try:
3030 try:
3031 fnode = ctx.filenode(fn)
3031 fnode = ctx.filenode(fn)
3032 except error.LookupError:
3032 except error.LookupError:
3033 continue
3033 continue
3034
3034
3035 copied = flog.renamed(fnode)
3035 copied = flog.renamed(fnode)
3036 copy = follow and copied and copied[0]
3036 copy = follow and copied and copied[0]
3037 if copy:
3037 if copy:
3038 copies.setdefault(rev, {})[fn] = copy
3038 copies.setdefault(rev, {})[fn] = copy
3039 if fn in skip:
3039 if fn in skip:
3040 if copy:
3040 if copy:
3041 skip[copy] = True
3041 skip[copy] = True
3042 continue
3042 continue
3043 files.append(fn)
3043 files.append(fn)
3044
3044
3045 if fn not in matches[rev]:
3045 if fn not in matches[rev]:
3046 grepbody(fn, rev, flog.read(fnode))
3046 grepbody(fn, rev, flog.read(fnode))
3047
3047
3048 pfn = copy or fn
3048 pfn = copy or fn
3049 if pfn not in matches[parent]:
3049 if pfn not in matches[parent]:
3050 try:
3050 try:
3051 fnode = pctx.filenode(pfn)
3051 fnode = pctx.filenode(pfn)
3052 grepbody(pfn, parent, flog.read(fnode))
3052 grepbody(pfn, parent, flog.read(fnode))
3053 except error.LookupError:
3053 except error.LookupError:
3054 pass
3054 pass
3055
3055
3056 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3056 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3057 rev = ctx.rev()
3057 rev = ctx.rev()
3058 parent = ctx.p1().rev()
3058 parent = ctx.p1().rev()
3059 for fn in sorted(revfiles.get(rev, [])):
3059 for fn in sorted(revfiles.get(rev, [])):
3060 states = matches[rev][fn]
3060 states = matches[rev][fn]
3061 copy = copies.get(rev, {}).get(fn)
3061 copy = copies.get(rev, {}).get(fn)
3062 if fn in skip:
3062 if fn in skip:
3063 if copy:
3063 if copy:
3064 skip[copy] = True
3064 skip[copy] = True
3065 continue
3065 continue
3066 pstates = matches.get(parent, {}).get(copy or fn, [])
3066 pstates = matches.get(parent, {}).get(copy or fn, [])
3067 if pstates or states:
3067 if pstates or states:
3068 r = display(fn, ctx, pstates, states)
3068 r = display(fn, ctx, pstates, states)
3069 found = found or r
3069 found = found or r
3070 if r and not opts.get('all'):
3070 if r and not opts.get('all'):
3071 skip[fn] = True
3071 skip[fn] = True
3072 if copy:
3072 if copy:
3073 skip[copy] = True
3073 skip[copy] = True
3074 del matches[rev]
3074 del matches[rev]
3075 del revfiles[rev]
3075 del revfiles[rev]
3076
3076
3077 return not found
3077 return not found
3078
3078
3079 @command('heads',
3079 @command('heads',
3080 [('r', 'rev', '',
3080 [('r', 'rev', '',
3081 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3081 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3082 ('t', 'topo', False, _('show topological heads only')),
3082 ('t', 'topo', False, _('show topological heads only')),
3083 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3083 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3084 ('c', 'closed', False, _('show normal and closed branch heads')),
3084 ('c', 'closed', False, _('show normal and closed branch heads')),
3085 ] + templateopts,
3085 ] + templateopts,
3086 _('[-ct] [-r STARTREV] [REV]...'))
3086 _('[-ct] [-r STARTREV] [REV]...'))
3087 def heads(ui, repo, *branchrevs, **opts):
3087 def heads(ui, repo, *branchrevs, **opts):
3088 """show current repository heads or show branch heads
3088 """show current repository heads or show branch heads
3089
3089
3090 With no arguments, show all repository branch heads.
3090 With no arguments, show all repository branch heads.
3091
3091
3092 Repository "heads" are changesets with no child changesets. They are
3092 Repository "heads" are changesets with no child changesets. They are
3093 where development generally takes place and are the usual targets
3093 where development generally takes place and are the usual targets
3094 for update and merge operations. Branch heads are changesets that have
3094 for update and merge operations. Branch heads are changesets that have
3095 no child changeset on the same branch.
3095 no child changeset on the same branch.
3096
3096
3097 If one or more REVs are given, only branch heads on the branches
3097 If one or more REVs are given, only branch heads on the branches
3098 associated with the specified changesets are shown. This means
3098 associated with the specified changesets are shown. This means
3099 that you can use :hg:`heads foo` to see the heads on a branch
3099 that you can use :hg:`heads foo` to see the heads on a branch
3100 named ``foo``.
3100 named ``foo``.
3101
3101
3102 If -c/--closed is specified, also show branch heads marked closed
3102 If -c/--closed is specified, also show branch heads marked closed
3103 (see :hg:`commit --close-branch`).
3103 (see :hg:`commit --close-branch`).
3104
3104
3105 If STARTREV is specified, only those heads that are descendants of
3105 If STARTREV is specified, only those heads that are descendants of
3106 STARTREV will be displayed.
3106 STARTREV will be displayed.
3107
3107
3108 If -t/--topo is specified, named branch mechanics will be ignored and only
3108 If -t/--topo is specified, named branch mechanics will be ignored and only
3109 changesets without children will be shown.
3109 changesets without children will be shown.
3110
3110
3111 Returns 0 if matching heads are found, 1 if not.
3111 Returns 0 if matching heads are found, 1 if not.
3112 """
3112 """
3113
3113
3114 start = None
3114 start = None
3115 if 'rev' in opts:
3115 if 'rev' in opts:
3116 start = scmutil.revsingle(repo, opts['rev'], None).node()
3116 start = scmutil.revsingle(repo, opts['rev'], None).node()
3117
3117
3118 if opts.get('topo'):
3118 if opts.get('topo'):
3119 heads = [repo[h] for h in repo.heads(start)]
3119 heads = [repo[h] for h in repo.heads(start)]
3120 else:
3120 else:
3121 heads = []
3121 heads = []
3122 for branch in repo.branchmap():
3122 for branch in repo.branchmap():
3123 heads += repo.branchheads(branch, start, opts.get('closed'))
3123 heads += repo.branchheads(branch, start, opts.get('closed'))
3124 heads = [repo[h] for h in heads]
3124 heads = [repo[h] for h in heads]
3125
3125
3126 if branchrevs:
3126 if branchrevs:
3127 branches = set(repo[br].branch() for br in branchrevs)
3127 branches = set(repo[br].branch() for br in branchrevs)
3128 heads = [h for h in heads if h.branch() in branches]
3128 heads = [h for h in heads if h.branch() in branches]
3129
3129
3130 if opts.get('active') and branchrevs:
3130 if opts.get('active') and branchrevs:
3131 dagheads = repo.heads(start)
3131 dagheads = repo.heads(start)
3132 heads = [h for h in heads if h.node() in dagheads]
3132 heads = [h for h in heads if h.node() in dagheads]
3133
3133
3134 if branchrevs:
3134 if branchrevs:
3135 haveheads = set(h.branch() for h in heads)
3135 haveheads = set(h.branch() for h in heads)
3136 if branches - haveheads:
3136 if branches - haveheads:
3137 headless = ', '.join(b for b in branches - haveheads)
3137 headless = ', '.join(b for b in branches - haveheads)
3138 msg = _('no open branch heads found on branches %s')
3138 msg = _('no open branch heads found on branches %s')
3139 if opts.get('rev'):
3139 if opts.get('rev'):
3140 msg += _(' (started at %s)') % opts['rev']
3140 msg += _(' (started at %s)') % opts['rev']
3141 ui.warn((msg + '\n') % headless)
3141 ui.warn((msg + '\n') % headless)
3142
3142
3143 if not heads:
3143 if not heads:
3144 return 1
3144 return 1
3145
3145
3146 heads = sorted(heads, key=lambda x: -x.rev())
3146 heads = sorted(heads, key=lambda x: -x.rev())
3147 displayer = cmdutil.show_changeset(ui, repo, opts)
3147 displayer = cmdutil.show_changeset(ui, repo, opts)
3148 for ctx in heads:
3148 for ctx in heads:
3149 displayer.show(ctx)
3149 displayer.show(ctx)
3150 displayer.close()
3150 displayer.close()
3151
3151
3152 @command('help',
3152 @command('help',
3153 [('e', 'extension', None, _('show only help for extensions')),
3153 [('e', 'extension', None, _('show only help for extensions')),
3154 ('c', 'command', None, _('show only help for commands')),
3154 ('c', 'command', None, _('show only help for commands')),
3155 ('k', 'keyword', '', _('show topics matching keyword')),
3155 ('k', 'keyword', '', _('show topics matching keyword')),
3156 ],
3156 ],
3157 _('[-ec] [TOPIC]'))
3157 _('[-ec] [TOPIC]'))
3158 def help_(ui, name=None, unknowncmd=False, full=True, **opts):
3158 def help_(ui, name=None, unknowncmd=False, full=True, **opts):
3159 """show help for a given topic or a help overview
3159 """show help for a given topic or a help overview
3160
3160
3161 With no arguments, print a list of commands with short help messages.
3161 With no arguments, print a list of commands with short help messages.
3162
3162
3163 Given a topic, extension, or command name, print help for that
3163 Given a topic, extension, or command name, print help for that
3164 topic.
3164 topic.
3165
3165
3166 Returns 0 if successful.
3166 Returns 0 if successful.
3167 """
3167 """
3168
3168
3169 textwidth = min(ui.termwidth(), 80) - 2
3169 textwidth = min(ui.termwidth(), 80) - 2
3170
3170
3171 def helpcmd(name):
3171 def helpcmd(name):
3172 try:
3172 try:
3173 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
3173 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
3174 except error.AmbiguousCommand, inst:
3174 except error.AmbiguousCommand, inst:
3175 # py3k fix: except vars can't be used outside the scope of the
3175 # py3k fix: except vars can't be used outside the scope of the
3176 # except block, nor can be used inside a lambda. python issue4617
3176 # except block, nor can be used inside a lambda. python issue4617
3177 prefix = inst.args[0]
3177 prefix = inst.args[0]
3178 select = lambda c: c.lstrip('^').startswith(prefix)
3178 select = lambda c: c.lstrip('^').startswith(prefix)
3179 rst = helplist(select)
3179 rst = helplist(select)
3180 return rst
3180 return rst
3181
3181
3182 rst = []
3182 rst = []
3183
3183
3184 # check if it's an invalid alias and display its error if it is
3184 # check if it's an invalid alias and display its error if it is
3185 if getattr(entry[0], 'badalias', False):
3185 if getattr(entry[0], 'badalias', False):
3186 if not unknowncmd:
3186 if not unknowncmd:
3187 ui.pushbuffer()
3187 ui.pushbuffer()
3188 entry[0](ui)
3188 entry[0](ui)
3189 rst.append(ui.popbuffer())
3189 rst.append(ui.popbuffer())
3190 return rst
3190 return rst
3191
3191
3192 # synopsis
3192 # synopsis
3193 if len(entry) > 2:
3193 if len(entry) > 2:
3194 if entry[2].startswith('hg'):
3194 if entry[2].startswith('hg'):
3195 rst.append("%s\n" % entry[2])
3195 rst.append("%s\n" % entry[2])
3196 else:
3196 else:
3197 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
3197 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
3198 else:
3198 else:
3199 rst.append('hg %s\n' % aliases[0])
3199 rst.append('hg %s\n' % aliases[0])
3200 # aliases
3200 # aliases
3201 if full and not ui.quiet and len(aliases) > 1:
3201 if full and not ui.quiet and len(aliases) > 1:
3202 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
3202 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
3203 rst.append('\n')
3203 rst.append('\n')
3204
3204
3205 # description
3205 # description
3206 doc = gettext(entry[0].__doc__)
3206 doc = gettext(entry[0].__doc__)
3207 if not doc:
3207 if not doc:
3208 doc = _("(no help text available)")
3208 doc = _("(no help text available)")
3209 if util.safehasattr(entry[0], 'definition'): # aliased command
3209 if util.safehasattr(entry[0], 'definition'): # aliased command
3210 if entry[0].definition.startswith('!'): # shell alias
3210 if entry[0].definition.startswith('!'): # shell alias
3211 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
3211 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
3212 else:
3212 else:
3213 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
3213 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
3214 doc = doc.splitlines(True)
3214 doc = doc.splitlines(True)
3215 if ui.quiet or not full:
3215 if ui.quiet or not full:
3216 rst.append(doc[0])
3216 rst.append(doc[0])
3217 else:
3217 else:
3218 rst.extend(doc)
3218 rst.extend(doc)
3219 rst.append('\n')
3219 rst.append('\n')
3220
3220
3221 # check if this command shadows a non-trivial (multi-line)
3221 # check if this command shadows a non-trivial (multi-line)
3222 # extension help text
3222 # extension help text
3223 try:
3223 try:
3224 mod = extensions.find(name)
3224 mod = extensions.find(name)
3225 doc = gettext(mod.__doc__) or ''
3225 doc = gettext(mod.__doc__) or ''
3226 if '\n' in doc.strip():
3226 if '\n' in doc.strip():
3227 msg = _('use "hg help -e %s" to show help for '
3227 msg = _('use "hg help -e %s" to show help for '
3228 'the %s extension') % (name, name)
3228 'the %s extension') % (name, name)
3229 rst.append('\n%s\n' % msg)
3229 rst.append('\n%s\n' % msg)
3230 except KeyError:
3230 except KeyError:
3231 pass
3231 pass
3232
3232
3233 # options
3233 # options
3234 if not ui.quiet and entry[1]:
3234 if not ui.quiet and entry[1]:
3235 rst.append('\n%s\n\n' % _("options:"))
3235 rst.append('\n%s\n\n' % _("options:"))
3236 rst.append(help.optrst(entry[1], ui.verbose))
3236 rst.append(help.optrst(entry[1], ui.verbose))
3237
3237
3238 if ui.verbose:
3238 if ui.verbose:
3239 rst.append('\n%s\n\n' % _("global options:"))
3239 rst.append('\n%s\n\n' % _("global options:"))
3240 rst.append(help.optrst(globalopts, ui.verbose))
3240 rst.append(help.optrst(globalopts, ui.verbose))
3241
3241
3242 if not ui.verbose:
3242 if not ui.verbose:
3243 if not full:
3243 if not full:
3244 rst.append(_('\nuse "hg help %s" to show the full help text\n')
3244 rst.append(_('\nuse "hg help %s" to show the full help text\n')
3245 % name)
3245 % name)
3246 elif not ui.quiet:
3246 elif not ui.quiet:
3247 rst.append(_('\nuse "hg -v help %s" to show more info\n')
3247 rst.append(_('\nuse "hg -v help %s" to show more info\n')
3248 % name)
3248 % name)
3249 return rst
3249 return rst
3250
3250
3251
3251
3252 def helplist(select=None):
3252 def helplist(select=None):
3253 # list of commands
3253 # list of commands
3254 if name == "shortlist":
3254 if name == "shortlist":
3255 header = _('basic commands:\n\n')
3255 header = _('basic commands:\n\n')
3256 else:
3256 else:
3257 header = _('list of commands:\n\n')
3257 header = _('list of commands:\n\n')
3258
3258
3259 h = {}
3259 h = {}
3260 cmds = {}
3260 cmds = {}
3261 for c, e in table.iteritems():
3261 for c, e in table.iteritems():
3262 f = c.split("|", 1)[0]
3262 f = c.split("|", 1)[0]
3263 if select and not select(f):
3263 if select and not select(f):
3264 continue
3264 continue
3265 if (not select and name != 'shortlist' and
3265 if (not select and name != 'shortlist' and
3266 e[0].__module__ != __name__):
3266 e[0].__module__ != __name__):
3267 continue
3267 continue
3268 if name == "shortlist" and not f.startswith("^"):
3268 if name == "shortlist" and not f.startswith("^"):
3269 continue
3269 continue
3270 f = f.lstrip("^")
3270 f = f.lstrip("^")
3271 if not ui.debugflag and f.startswith("debug"):
3271 if not ui.debugflag and f.startswith("debug"):
3272 continue
3272 continue
3273 doc = e[0].__doc__
3273 doc = e[0].__doc__
3274 if doc and 'DEPRECATED' in doc and not ui.verbose:
3274 if doc and 'DEPRECATED' in doc and not ui.verbose:
3275 continue
3275 continue
3276 doc = gettext(doc)
3276 doc = gettext(doc)
3277 if not doc:
3277 if not doc:
3278 doc = _("(no help text available)")
3278 doc = _("(no help text available)")
3279 h[f] = doc.splitlines()[0].rstrip()
3279 h[f] = doc.splitlines()[0].rstrip()
3280 cmds[f] = c.lstrip("^")
3280 cmds[f] = c.lstrip("^")
3281
3281
3282 rst = []
3282 rst = []
3283 if not h:
3283 if not h:
3284 if not ui.quiet:
3284 if not ui.quiet:
3285 rst.append(_('no commands defined\n'))
3285 rst.append(_('no commands defined\n'))
3286 return rst
3286 return rst
3287
3287
3288 if not ui.quiet:
3288 if not ui.quiet:
3289 rst.append(header)
3289 rst.append(header)
3290 fns = sorted(h)
3290 fns = sorted(h)
3291 for f in fns:
3291 for f in fns:
3292 if ui.verbose:
3292 if ui.verbose:
3293 commands = cmds[f].replace("|",", ")
3293 commands = cmds[f].replace("|",", ")
3294 rst.append(" :%s: %s\n" % (commands, h[f]))
3294 rst.append(" :%s: %s\n" % (commands, h[f]))
3295 else:
3295 else:
3296 rst.append(' :%s: %s\n' % (f, h[f]))
3296 rst.append(' :%s: %s\n' % (f, h[f]))
3297
3297
3298 if not name:
3298 if not name:
3299 exts = help.listexts(_('enabled extensions:'), extensions.enabled())
3299 exts = help.listexts(_('enabled extensions:'), extensions.enabled())
3300 if exts:
3300 if exts:
3301 rst.append('\n')
3301 rst.append('\n')
3302 rst.extend(exts)
3302 rst.extend(exts)
3303
3303
3304 rst.append(_("\nadditional help topics:\n\n"))
3304 rst.append(_("\nadditional help topics:\n\n"))
3305 topics = []
3305 topics = []
3306 for names, header, doc in help.helptable:
3306 for names, header, doc in help.helptable:
3307 topics.append((names[0], header))
3307 topics.append((names[0], header))
3308 for t, desc in topics:
3308 for t, desc in topics:
3309 rst.append(" :%s: %s\n" % (t, desc))
3309 rst.append(" :%s: %s\n" % (t, desc))
3310
3310
3311 optlist = []
3311 optlist = []
3312 if not ui.quiet:
3312 if not ui.quiet:
3313 if ui.verbose:
3313 if ui.verbose:
3314 optlist.append((_("global options:"), globalopts))
3314 optlist.append((_("global options:"), globalopts))
3315 if name == 'shortlist':
3315 if name == 'shortlist':
3316 optlist.append((_('use "hg help" for the full list '
3316 optlist.append((_('use "hg help" for the full list '
3317 'of commands'), ()))
3317 'of commands'), ()))
3318 else:
3318 else:
3319 if name == 'shortlist':
3319 if name == 'shortlist':
3320 msg = _('use "hg help" for the full list of commands '
3320 msg = _('use "hg help" for the full list of commands '
3321 'or "hg -v" for details')
3321 'or "hg -v" for details')
3322 elif name and not full:
3322 elif name and not full:
3323 msg = _('use "hg help %s" to show the full help '
3323 msg = _('use "hg help %s" to show the full help '
3324 'text') % name
3324 'text') % name
3325 else:
3325 else:
3326 msg = _('use "hg -v help%s" to show builtin aliases and '
3326 msg = _('use "hg -v help%s" to show builtin aliases and '
3327 'global options') % (name and " " + name or "")
3327 'global options') % (name and " " + name or "")
3328 optlist.append((msg, ()))
3328 optlist.append((msg, ()))
3329
3329
3330 if optlist:
3330 if optlist:
3331 for title, options in optlist:
3331 for title, options in optlist:
3332 rst.append('\n%s\n' % title)
3332 rst.append('\n%s\n' % title)
3333 if options:
3333 if options:
3334 rst.append('\n%s\n' % help.optrst(options, ui.verbose))
3334 rst.append('\n%s\n' % help.optrst(options, ui.verbose))
3335 return rst
3335 return rst
3336
3336
3337 def helptopic(name):
3337 def helptopic(name):
3338 for names, header, doc in help.helptable:
3338 for names, header, doc in help.helptable:
3339 if name in names:
3339 if name in names:
3340 break
3340 break
3341 else:
3341 else:
3342 raise error.UnknownCommand(name)
3342 raise error.UnknownCommand(name)
3343
3343
3344 rst = ["%s\n\n" % header]
3344 rst = ["%s\n\n" % header]
3345 # description
3345 # description
3346 if not doc:
3346 if not doc:
3347 rst.append(" %s\n" % _("(no help text available)"))
3347 rst.append(" %s\n" % _("(no help text available)"))
3348 if util.safehasattr(doc, '__call__'):
3348 if util.safehasattr(doc, '__call__'):
3349 rst += [" %s\n" % l for l in doc().splitlines()]
3349 rst += [" %s\n" % l for l in doc().splitlines()]
3350
3350
3351 try:
3351 try:
3352 cmdutil.findcmd(name, table)
3352 cmdutil.findcmd(name, table)
3353 rst.append(_('\nuse "hg help -c %s" to see help for '
3353 rst.append(_('\nuse "hg help -c %s" to see help for '
3354 'the %s command\n') % (name, name))
3354 'the %s command\n') % (name, name))
3355 except error.UnknownCommand:
3355 except error.UnknownCommand:
3356 pass
3356 pass
3357 return rst
3357 return rst
3358
3358
3359 def helpext(name):
3359 def helpext(name):
3360 try:
3360 try:
3361 mod = extensions.find(name)
3361 mod = extensions.find(name)
3362 doc = gettext(mod.__doc__) or _('no help text available')
3362 doc = gettext(mod.__doc__) or _('no help text available')
3363 except KeyError:
3363 except KeyError:
3364 mod = None
3364 mod = None
3365 doc = extensions.disabledext(name)
3365 doc = extensions.disabledext(name)
3366 if not doc:
3366 if not doc:
3367 raise error.UnknownCommand(name)
3367 raise error.UnknownCommand(name)
3368
3368
3369 if '\n' not in doc:
3369 if '\n' not in doc:
3370 head, tail = doc, ""
3370 head, tail = doc, ""
3371 else:
3371 else:
3372 head, tail = doc.split('\n', 1)
3372 head, tail = doc.split('\n', 1)
3373 rst = [_('%s extension - %s\n\n') % (name.split('.')[-1], head)]
3373 rst = [_('%s extension - %s\n\n') % (name.split('.')[-1], head)]
3374 if tail:
3374 if tail:
3375 rst.extend(tail.splitlines(True))
3375 rst.extend(tail.splitlines(True))
3376 rst.append('\n')
3376 rst.append('\n')
3377
3377
3378 if mod:
3378 if mod:
3379 try:
3379 try:
3380 ct = mod.cmdtable
3380 ct = mod.cmdtable
3381 except AttributeError:
3381 except AttributeError:
3382 ct = {}
3382 ct = {}
3383 modcmds = set([c.split('|', 1)[0] for c in ct])
3383 modcmds = set([c.split('|', 1)[0] for c in ct])
3384 rst.extend(helplist(modcmds.__contains__))
3384 rst.extend(helplist(modcmds.__contains__))
3385 else:
3385 else:
3386 rst.append(_('use "hg help extensions" for information on enabling '
3386 rst.append(_('use "hg help extensions" for information on enabling '
3387 'extensions\n'))
3387 'extensions\n'))
3388 return rst
3388 return rst
3389
3389
3390 def helpextcmd(name):
3390 def helpextcmd(name):
3391 cmd, ext, mod = extensions.disabledcmd(ui, name,
3391 cmd, ext, mod = extensions.disabledcmd(ui, name,
3392 ui.configbool('ui', 'strict'))
3392 ui.configbool('ui', 'strict'))
3393 doc = gettext(mod.__doc__).splitlines()[0]
3393 doc = gettext(mod.__doc__).splitlines()[0]
3394
3394
3395 rst = help.listexts(_("'%s' is provided by the following "
3395 rst = help.listexts(_("'%s' is provided by the following "
3396 "extension:") % cmd, {ext: doc}, indent=4)
3396 "extension:") % cmd, {ext: doc}, indent=4)
3397 rst.append('\n')
3397 rst.append('\n')
3398 rst.append(_('use "hg help extensions" for information on enabling '
3398 rst.append(_('use "hg help extensions" for information on enabling '
3399 'extensions\n'))
3399 'extensions\n'))
3400 return rst
3400 return rst
3401
3401
3402
3402
3403 rst = []
3403 rst = []
3404 kw = opts.get('keyword')
3404 kw = opts.get('keyword')
3405 if kw:
3405 if kw:
3406 matches = help.topicmatch(kw)
3406 matches = help.topicmatch(kw)
3407 for t, title in (('topics', _('Topics')),
3407 for t, title in (('topics', _('Topics')),
3408 ('commands', _('Commands')),
3408 ('commands', _('Commands')),
3409 ('extensions', _('Extensions')),
3409 ('extensions', _('Extensions')),
3410 ('extensioncommands', _('Extension Commands'))):
3410 ('extensioncommands', _('Extension Commands'))):
3411 if matches[t]:
3411 if matches[t]:
3412 rst.append('%s:\n\n' % title)
3412 rst.append('%s:\n\n' % title)
3413 rst.extend(minirst.maketable(sorted(matches[t]), 1))
3413 rst.extend(minirst.maketable(sorted(matches[t]), 1))
3414 rst.append('\n')
3414 rst.append('\n')
3415 elif name and name != 'shortlist':
3415 elif name and name != 'shortlist':
3416 i = None
3416 i = None
3417 if unknowncmd:
3417 if unknowncmd:
3418 queries = (helpextcmd,)
3418 queries = (helpextcmd,)
3419 elif opts.get('extension'):
3419 elif opts.get('extension'):
3420 queries = (helpext,)
3420 queries = (helpext,)
3421 elif opts.get('command'):
3421 elif opts.get('command'):
3422 queries = (helpcmd,)
3422 queries = (helpcmd,)
3423 else:
3423 else:
3424 queries = (helptopic, helpcmd, helpext, helpextcmd)
3424 queries = (helptopic, helpcmd, helpext, helpextcmd)
3425 for f in queries:
3425 for f in queries:
3426 try:
3426 try:
3427 rst = f(name)
3427 rst = f(name)
3428 i = None
3428 i = None
3429 break
3429 break
3430 except error.UnknownCommand, inst:
3430 except error.UnknownCommand, inst:
3431 i = inst
3431 i = inst
3432 if i:
3432 if i:
3433 raise i
3433 raise i
3434 else:
3434 else:
3435 # program name
3435 # program name
3436 if not ui.quiet:
3436 if not ui.quiet:
3437 rst = [_("Mercurial Distributed SCM\n"), '\n']
3437 rst = [_("Mercurial Distributed SCM\n"), '\n']
3438 rst.extend(helplist())
3438 rst.extend(helplist())
3439
3439
3440 keep = ui.verbose and ['verbose'] or []
3440 keep = ui.verbose and ['verbose'] or []
3441 formatted, pruned = minirst.format(''.join(rst), textwidth, keep=keep)
3441 formatted, pruned = minirst.format(''.join(rst), textwidth, keep=keep)
3442 ui.write(formatted)
3442 ui.write(formatted)
3443
3443
3444
3444
3445 @command('identify|id',
3445 @command('identify|id',
3446 [('r', 'rev', '',
3446 [('r', 'rev', '',
3447 _('identify the specified revision'), _('REV')),
3447 _('identify the specified revision'), _('REV')),
3448 ('n', 'num', None, _('show local revision number')),
3448 ('n', 'num', None, _('show local revision number')),
3449 ('i', 'id', None, _('show global revision id')),
3449 ('i', 'id', None, _('show global revision id')),
3450 ('b', 'branch', None, _('show branch')),
3450 ('b', 'branch', None, _('show branch')),
3451 ('t', 'tags', None, _('show tags')),
3451 ('t', 'tags', None, _('show tags')),
3452 ('B', 'bookmarks', None, _('show bookmarks')),
3452 ('B', 'bookmarks', None, _('show bookmarks')),
3453 ] + remoteopts,
3453 ] + remoteopts,
3454 _('[-nibtB] [-r REV] [SOURCE]'))
3454 _('[-nibtB] [-r REV] [SOURCE]'))
3455 def identify(ui, repo, source=None, rev=None,
3455 def identify(ui, repo, source=None, rev=None,
3456 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3456 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3457 """identify the working copy or specified revision
3457 """identify the working copy or specified revision
3458
3458
3459 Print a summary identifying the repository state at REV using one or
3459 Print a summary identifying the repository state at REV using one or
3460 two parent hash identifiers, followed by a "+" if the working
3460 two parent hash identifiers, followed by a "+" if the working
3461 directory has uncommitted changes, the branch name (if not default),
3461 directory has uncommitted changes, the branch name (if not default),
3462 a list of tags, and a list of bookmarks.
3462 a list of tags, and a list of bookmarks.
3463
3463
3464 When REV is not given, print a summary of the current state of the
3464 When REV is not given, print a summary of the current state of the
3465 repository.
3465 repository.
3466
3466
3467 Specifying a path to a repository root or Mercurial bundle will
3467 Specifying a path to a repository root or Mercurial bundle will
3468 cause lookup to operate on that repository/bundle.
3468 cause lookup to operate on that repository/bundle.
3469
3469
3470 .. container:: verbose
3470 .. container:: verbose
3471
3471
3472 Examples:
3472 Examples:
3473
3473
3474 - generate a build identifier for the working directory::
3474 - generate a build identifier for the working directory::
3475
3475
3476 hg id --id > build-id.dat
3476 hg id --id > build-id.dat
3477
3477
3478 - find the revision corresponding to a tag::
3478 - find the revision corresponding to a tag::
3479
3479
3480 hg id -n -r 1.3
3480 hg id -n -r 1.3
3481
3481
3482 - check the most recent revision of a remote repository::
3482 - check the most recent revision of a remote repository::
3483
3483
3484 hg id -r tip http://selenic.com/hg/
3484 hg id -r tip http://selenic.com/hg/
3485
3485
3486 Returns 0 if successful.
3486 Returns 0 if successful.
3487 """
3487 """
3488
3488
3489 if not repo and not source:
3489 if not repo and not source:
3490 raise util.Abort(_("there is no Mercurial repository here "
3490 raise util.Abort(_("there is no Mercurial repository here "
3491 "(.hg not found)"))
3491 "(.hg not found)"))
3492
3492
3493 hexfunc = ui.debugflag and hex or short
3493 hexfunc = ui.debugflag and hex or short
3494 default = not (num or id or branch or tags or bookmarks)
3494 default = not (num or id or branch or tags or bookmarks)
3495 output = []
3495 output = []
3496 revs = []
3496 revs = []
3497
3497
3498 if source:
3498 if source:
3499 source, branches = hg.parseurl(ui.expandpath(source))
3499 source, branches = hg.parseurl(ui.expandpath(source))
3500 peer = hg.peer(ui, opts, source)
3500 peer = hg.peer(ui, opts, source)
3501 repo = peer.local()
3501 repo = peer.local()
3502 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3502 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3503
3503
3504 if not repo:
3504 if not repo:
3505 if num or branch or tags:
3505 if num or branch or tags:
3506 raise util.Abort(
3506 raise util.Abort(
3507 _("can't query remote revision number, branch, or tags"))
3507 _("can't query remote revision number, branch, or tags"))
3508 if not rev and revs:
3508 if not rev and revs:
3509 rev = revs[0]
3509 rev = revs[0]
3510 if not rev:
3510 if not rev:
3511 rev = "tip"
3511 rev = "tip"
3512
3512
3513 remoterev = peer.lookup(rev)
3513 remoterev = peer.lookup(rev)
3514 if default or id:
3514 if default or id:
3515 output = [hexfunc(remoterev)]
3515 output = [hexfunc(remoterev)]
3516
3516
3517 def getbms():
3517 def getbms():
3518 bms = []
3518 bms = []
3519
3519
3520 if 'bookmarks' in peer.listkeys('namespaces'):
3520 if 'bookmarks' in peer.listkeys('namespaces'):
3521 hexremoterev = hex(remoterev)
3521 hexremoterev = hex(remoterev)
3522 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3522 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3523 if bmr == hexremoterev]
3523 if bmr == hexremoterev]
3524
3524
3525 return bms
3525 return bms
3526
3526
3527 if bookmarks:
3527 if bookmarks:
3528 output.extend(getbms())
3528 output.extend(getbms())
3529 elif default and not ui.quiet:
3529 elif default and not ui.quiet:
3530 # multiple bookmarks for a single parent separated by '/'
3530 # multiple bookmarks for a single parent separated by '/'
3531 bm = '/'.join(getbms())
3531 bm = '/'.join(getbms())
3532 if bm:
3532 if bm:
3533 output.append(bm)
3533 output.append(bm)
3534 else:
3534 else:
3535 if not rev:
3535 if not rev:
3536 ctx = repo[None]
3536 ctx = repo[None]
3537 parents = ctx.parents()
3537 parents = ctx.parents()
3538 changed = ""
3538 changed = ""
3539 if default or id or num:
3539 if default or id or num:
3540 if (util.any(repo.status())
3540 if (util.any(repo.status())
3541 or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
3541 or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
3542 changed = '+'
3542 changed = '+'
3543 if default or id:
3543 if default or id:
3544 output = ["%s%s" %
3544 output = ["%s%s" %
3545 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3545 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3546 if num:
3546 if num:
3547 output.append("%s%s" %
3547 output.append("%s%s" %
3548 ('+'.join([str(p.rev()) for p in parents]), changed))
3548 ('+'.join([str(p.rev()) for p in parents]), changed))
3549 else:
3549 else:
3550 ctx = scmutil.revsingle(repo, rev)
3550 ctx = scmutil.revsingle(repo, rev)
3551 if default or id:
3551 if default or id:
3552 output = [hexfunc(ctx.node())]
3552 output = [hexfunc(ctx.node())]
3553 if num:
3553 if num:
3554 output.append(str(ctx.rev()))
3554 output.append(str(ctx.rev()))
3555
3555
3556 if default and not ui.quiet:
3556 if default and not ui.quiet:
3557 b = ctx.branch()
3557 b = ctx.branch()
3558 if b != 'default':
3558 if b != 'default':
3559 output.append("(%s)" % b)
3559 output.append("(%s)" % b)
3560
3560
3561 # multiple tags for a single parent separated by '/'
3561 # multiple tags for a single parent separated by '/'
3562 t = '/'.join(ctx.tags())
3562 t = '/'.join(ctx.tags())
3563 if t:
3563 if t:
3564 output.append(t)
3564 output.append(t)
3565
3565
3566 # multiple bookmarks for a single parent separated by '/'
3566 # multiple bookmarks for a single parent separated by '/'
3567 bm = '/'.join(ctx.bookmarks())
3567 bm = '/'.join(ctx.bookmarks())
3568 if bm:
3568 if bm:
3569 output.append(bm)
3569 output.append(bm)
3570 else:
3570 else:
3571 if branch:
3571 if branch:
3572 output.append(ctx.branch())
3572 output.append(ctx.branch())
3573
3573
3574 if tags:
3574 if tags:
3575 output.extend(ctx.tags())
3575 output.extend(ctx.tags())
3576
3576
3577 if bookmarks:
3577 if bookmarks:
3578 output.extend(ctx.bookmarks())
3578 output.extend(ctx.bookmarks())
3579
3579
3580 ui.write("%s\n" % ' '.join(output))
3580 ui.write("%s\n" % ' '.join(output))
3581
3581
3582 @command('import|patch',
3582 @command('import|patch',
3583 [('p', 'strip', 1,
3583 [('p', 'strip', 1,
3584 _('directory strip option for patch. This has the same '
3584 _('directory strip option for patch. This has the same '
3585 'meaning as the corresponding patch option'), _('NUM')),
3585 'meaning as the corresponding patch option'), _('NUM')),
3586 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3586 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3587 ('e', 'edit', False, _('invoke editor on commit messages')),
3587 ('e', 'edit', False, _('invoke editor on commit messages')),
3588 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3588 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3589 ('', 'no-commit', None,
3589 ('', 'no-commit', None,
3590 _("don't commit, just update the working directory")),
3590 _("don't commit, just update the working directory")),
3591 ('', 'bypass', None,
3591 ('', 'bypass', None,
3592 _("apply patch without touching the working directory")),
3592 _("apply patch without touching the working directory")),
3593 ('', 'exact', None,
3593 ('', 'exact', None,
3594 _('apply patch to the nodes from which it was generated')),
3594 _('apply patch to the nodes from which it was generated')),
3595 ('', 'import-branch', None,
3595 ('', 'import-branch', None,
3596 _('use any branch information in patch (implied by --exact)'))] +
3596 _('use any branch information in patch (implied by --exact)'))] +
3597 commitopts + commitopts2 + similarityopts,
3597 commitopts + commitopts2 + similarityopts,
3598 _('[OPTION]... PATCH...'))
3598 _('[OPTION]... PATCH...'))
3599 def import_(ui, repo, patch1=None, *patches, **opts):
3599 def import_(ui, repo, patch1=None, *patches, **opts):
3600 """import an ordered set of patches
3600 """import an ordered set of patches
3601
3601
3602 Import a list of patches and commit them individually (unless
3602 Import a list of patches and commit them individually (unless
3603 --no-commit is specified).
3603 --no-commit is specified).
3604
3604
3605 If there are outstanding changes in the working directory, import
3605 If there are outstanding changes in the working directory, import
3606 will abort unless given the -f/--force flag.
3606 will abort unless given the -f/--force flag.
3607
3607
3608 You can import a patch straight from a mail message. Even patches
3608 You can import a patch straight from a mail message. Even patches
3609 as attachments work (to use the body part, it must have type
3609 as attachments work (to use the body part, it must have type
3610 text/plain or text/x-patch). From and Subject headers of email
3610 text/plain or text/x-patch). From and Subject headers of email
3611 message are used as default committer and commit message. All
3611 message are used as default committer and commit message. All
3612 text/plain body parts before first diff are added to commit
3612 text/plain body parts before first diff are added to commit
3613 message.
3613 message.
3614
3614
3615 If the imported patch was generated by :hg:`export`, user and
3615 If the imported patch was generated by :hg:`export`, user and
3616 description from patch override values from message headers and
3616 description from patch override values from message headers and
3617 body. Values given on command line with -m/--message and -u/--user
3617 body. Values given on command line with -m/--message and -u/--user
3618 override these.
3618 override these.
3619
3619
3620 If --exact is specified, import will set the working directory to
3620 If --exact is specified, import will set the working directory to
3621 the parent of each patch before applying it, and will abort if the
3621 the parent of each patch before applying it, and will abort if the
3622 resulting changeset has a different ID than the one recorded in
3622 resulting changeset has a different ID than the one recorded in
3623 the patch. This may happen due to character set problems or other
3623 the patch. This may happen due to character set problems or other
3624 deficiencies in the text patch format.
3624 deficiencies in the text patch format.
3625
3625
3626 Use --bypass to apply and commit patches directly to the
3626 Use --bypass to apply and commit patches directly to the
3627 repository, not touching the working directory. Without --exact,
3627 repository, not touching the working directory. Without --exact,
3628 patches will be applied on top of the working directory parent
3628 patches will be applied on top of the working directory parent
3629 revision.
3629 revision.
3630
3630
3631 With -s/--similarity, hg will attempt to discover renames and
3631 With -s/--similarity, hg will attempt to discover renames and
3632 copies in the patch in the same way as :hg:`addremove`.
3632 copies in the patch in the same way as :hg:`addremove`.
3633
3633
3634 To read a patch from standard input, use "-" as the patch name. If
3634 To read a patch from standard input, use "-" as the patch name. If
3635 a URL is specified, the patch will be downloaded from it.
3635 a URL is specified, the patch will be downloaded from it.
3636 See :hg:`help dates` for a list of formats valid for -d/--date.
3636 See :hg:`help dates` for a list of formats valid for -d/--date.
3637
3637
3638 .. container:: verbose
3638 .. container:: verbose
3639
3639
3640 Examples:
3640 Examples:
3641
3641
3642 - import a traditional patch from a website and detect renames::
3642 - import a traditional patch from a website and detect renames::
3643
3643
3644 hg import -s 80 http://example.com/bugfix.patch
3644 hg import -s 80 http://example.com/bugfix.patch
3645
3645
3646 - import a changeset from an hgweb server::
3646 - import a changeset from an hgweb server::
3647
3647
3648 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3648 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3649
3649
3650 - import all the patches in an Unix-style mbox::
3650 - import all the patches in an Unix-style mbox::
3651
3651
3652 hg import incoming-patches.mbox
3652 hg import incoming-patches.mbox
3653
3653
3654 - attempt to exactly restore an exported changeset (not always
3654 - attempt to exactly restore an exported changeset (not always
3655 possible)::
3655 possible)::
3656
3656
3657 hg import --exact proposed-fix.patch
3657 hg import --exact proposed-fix.patch
3658
3658
3659 Returns 0 on success.
3659 Returns 0 on success.
3660 """
3660 """
3661
3661
3662 if not patch1:
3662 if not patch1:
3663 raise util.Abort(_('need at least one patch to import'))
3663 raise util.Abort(_('need at least one patch to import'))
3664
3664
3665 patches = (patch1,) + patches
3665 patches = (patch1,) + patches
3666
3666
3667 date = opts.get('date')
3667 date = opts.get('date')
3668 if date:
3668 if date:
3669 opts['date'] = util.parsedate(date)
3669 opts['date'] = util.parsedate(date)
3670
3670
3671 editor = cmdutil.commiteditor
3671 editor = cmdutil.commiteditor
3672 if opts.get('edit'):
3672 if opts.get('edit'):
3673 editor = cmdutil.commitforceeditor
3673 editor = cmdutil.commitforceeditor
3674
3674
3675 update = not opts.get('bypass')
3675 update = not opts.get('bypass')
3676 if not update and opts.get('no_commit'):
3676 if not update and opts.get('no_commit'):
3677 raise util.Abort(_('cannot use --no-commit with --bypass'))
3677 raise util.Abort(_('cannot use --no-commit with --bypass'))
3678 try:
3678 try:
3679 sim = float(opts.get('similarity') or 0)
3679 sim = float(opts.get('similarity') or 0)
3680 except ValueError:
3680 except ValueError:
3681 raise util.Abort(_('similarity must be a number'))
3681 raise util.Abort(_('similarity must be a number'))
3682 if sim < 0 or sim > 100:
3682 if sim < 0 or sim > 100:
3683 raise util.Abort(_('similarity must be between 0 and 100'))
3683 raise util.Abort(_('similarity must be between 0 and 100'))
3684 if sim and not update:
3684 if sim and not update:
3685 raise util.Abort(_('cannot use --similarity with --bypass'))
3685 raise util.Abort(_('cannot use --similarity with --bypass'))
3686
3686
3687 if (opts.get('exact') or not opts.get('force')) and update:
3687 if (opts.get('exact') or not opts.get('force')) and update:
3688 cmdutil.bailifchanged(repo)
3688 cmdutil.bailifchanged(repo)
3689
3689
3690 base = opts["base"]
3690 base = opts["base"]
3691 strip = opts["strip"]
3691 strip = opts["strip"]
3692 wlock = lock = tr = None
3692 wlock = lock = tr = None
3693 msgs = []
3693 msgs = []
3694
3694
3695 def checkexact(repo, n, nodeid):
3695 def checkexact(repo, n, nodeid):
3696 if opts.get('exact') and hex(n) != nodeid:
3696 if opts.get('exact') and hex(n) != nodeid:
3697 repo.rollback()
3697 repo.rollback()
3698 raise util.Abort(_('patch is damaged or loses information'))
3698 raise util.Abort(_('patch is damaged or loses information'))
3699
3699
3700 def tryone(ui, hunk, parents):
3700 def tryone(ui, hunk, parents):
3701 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3701 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3702 patch.extract(ui, hunk)
3702 patch.extract(ui, hunk)
3703
3703
3704 if not tmpname:
3704 if not tmpname:
3705 return (None, None)
3705 return (None, None)
3706 msg = _('applied to working directory')
3706 msg = _('applied to working directory')
3707
3707
3708 try:
3708 try:
3709 cmdline_message = cmdutil.logmessage(ui, opts)
3709 cmdline_message = cmdutil.logmessage(ui, opts)
3710 if cmdline_message:
3710 if cmdline_message:
3711 # pickup the cmdline msg
3711 # pickup the cmdline msg
3712 message = cmdline_message
3712 message = cmdline_message
3713 elif message:
3713 elif message:
3714 # pickup the patch msg
3714 # pickup the patch msg
3715 message = message.strip()
3715 message = message.strip()
3716 else:
3716 else:
3717 # launch the editor
3717 # launch the editor
3718 message = None
3718 message = None
3719 ui.debug('message:\n%s\n' % message)
3719 ui.debug('message:\n%s\n' % message)
3720
3720
3721 if len(parents) == 1:
3721 if len(parents) == 1:
3722 parents.append(repo[nullid])
3722 parents.append(repo[nullid])
3723 if opts.get('exact'):
3723 if opts.get('exact'):
3724 if not nodeid or not p1:
3724 if not nodeid or not p1:
3725 raise util.Abort(_('not a Mercurial patch'))
3725 raise util.Abort(_('not a Mercurial patch'))
3726 p1 = repo[p1]
3726 p1 = repo[p1]
3727 p2 = repo[p2 or nullid]
3727 p2 = repo[p2 or nullid]
3728 elif p2:
3728 elif p2:
3729 try:
3729 try:
3730 p1 = repo[p1]
3730 p1 = repo[p1]
3731 p2 = repo[p2]
3731 p2 = repo[p2]
3732 # Without any options, consider p2 only if the
3732 # Without any options, consider p2 only if the
3733 # patch is being applied on top of the recorded
3733 # patch is being applied on top of the recorded
3734 # first parent.
3734 # first parent.
3735 if p1 != parents[0]:
3735 if p1 != parents[0]:
3736 p1 = parents[0]
3736 p1 = parents[0]
3737 p2 = repo[nullid]
3737 p2 = repo[nullid]
3738 except error.RepoError:
3738 except error.RepoError:
3739 p1, p2 = parents
3739 p1, p2 = parents
3740 else:
3740 else:
3741 p1, p2 = parents
3741 p1, p2 = parents
3742
3742
3743 n = None
3743 n = None
3744 if update:
3744 if update:
3745 if p1 != parents[0]:
3745 if p1 != parents[0]:
3746 hg.clean(repo, p1.node())
3746 hg.clean(repo, p1.node())
3747 if p2 != parents[1]:
3747 if p2 != parents[1]:
3748 repo.setparents(p1.node(), p2.node())
3748 repo.setparents(p1.node(), p2.node())
3749
3749
3750 if opts.get('exact') or opts.get('import_branch'):
3750 if opts.get('exact') or opts.get('import_branch'):
3751 repo.dirstate.setbranch(branch or 'default')
3751 repo.dirstate.setbranch(branch or 'default')
3752
3752
3753 files = set()
3753 files = set()
3754 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3754 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3755 eolmode=None, similarity=sim / 100.0)
3755 eolmode=None, similarity=sim / 100.0)
3756 files = list(files)
3756 files = list(files)
3757 if opts.get('no_commit'):
3757 if opts.get('no_commit'):
3758 if message:
3758 if message:
3759 msgs.append(message)
3759 msgs.append(message)
3760 else:
3760 else:
3761 if opts.get('exact') or p2:
3761 if opts.get('exact') or p2:
3762 # If you got here, you either use --force and know what
3762 # If you got here, you either use --force and know what
3763 # you are doing or used --exact or a merge patch while
3763 # you are doing or used --exact or a merge patch while
3764 # being updated to its first parent.
3764 # being updated to its first parent.
3765 m = None
3765 m = None
3766 else:
3766 else:
3767 m = scmutil.matchfiles(repo, files or [])
3767 m = scmutil.matchfiles(repo, files or [])
3768 n = repo.commit(message, opts.get('user') or user,
3768 n = repo.commit(message, opts.get('user') or user,
3769 opts.get('date') or date, match=m,
3769 opts.get('date') or date, match=m,
3770 editor=editor)
3770 editor=editor)
3771 checkexact(repo, n, nodeid)
3771 checkexact(repo, n, nodeid)
3772 else:
3772 else:
3773 if opts.get('exact') or opts.get('import_branch'):
3773 if opts.get('exact') or opts.get('import_branch'):
3774 branch = branch or 'default'
3774 branch = branch or 'default'
3775 else:
3775 else:
3776 branch = p1.branch()
3776 branch = p1.branch()
3777 store = patch.filestore()
3777 store = patch.filestore()
3778 try:
3778 try:
3779 files = set()
3779 files = set()
3780 try:
3780 try:
3781 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3781 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3782 files, eolmode=None)
3782 files, eolmode=None)
3783 except patch.PatchError, e:
3783 except patch.PatchError, e:
3784 raise util.Abort(str(e))
3784 raise util.Abort(str(e))
3785 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3785 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3786 message,
3786 message,
3787 opts.get('user') or user,
3787 opts.get('user') or user,
3788 opts.get('date') or date,
3788 opts.get('date') or date,
3789 branch, files, store,
3789 branch, files, store,
3790 editor=cmdutil.commiteditor)
3790 editor=cmdutil.commiteditor)
3791 repo.savecommitmessage(memctx.description())
3791 repo.savecommitmessage(memctx.description())
3792 n = memctx.commit()
3792 n = memctx.commit()
3793 checkexact(repo, n, nodeid)
3793 checkexact(repo, n, nodeid)
3794 finally:
3794 finally:
3795 store.close()
3795 store.close()
3796 if n:
3796 if n:
3797 # i18n: refers to a short changeset id
3797 # i18n: refers to a short changeset id
3798 msg = _('created %s') % short(n)
3798 msg = _('created %s') % short(n)
3799 return (msg, n)
3799 return (msg, n)
3800 finally:
3800 finally:
3801 os.unlink(tmpname)
3801 os.unlink(tmpname)
3802
3802
3803 try:
3803 try:
3804 try:
3804 try:
3805 wlock = repo.wlock()
3805 wlock = repo.wlock()
3806 if not opts.get('no_commit'):
3806 if not opts.get('no_commit'):
3807 lock = repo.lock()
3807 lock = repo.lock()
3808 tr = repo.transaction('import')
3808 tr = repo.transaction('import')
3809 parents = repo.parents()
3809 parents = repo.parents()
3810 for patchurl in patches:
3810 for patchurl in patches:
3811 if patchurl == '-':
3811 if patchurl == '-':
3812 ui.status(_('applying patch from stdin\n'))
3812 ui.status(_('applying patch from stdin\n'))
3813 patchfile = ui.fin
3813 patchfile = ui.fin
3814 patchurl = 'stdin' # for error message
3814 patchurl = 'stdin' # for error message
3815 else:
3815 else:
3816 patchurl = os.path.join(base, patchurl)
3816 patchurl = os.path.join(base, patchurl)
3817 ui.status(_('applying %s\n') % patchurl)
3817 ui.status(_('applying %s\n') % patchurl)
3818 patchfile = url.open(ui, patchurl)
3818 patchfile = url.open(ui, patchurl)
3819
3819
3820 haspatch = False
3820 haspatch = False
3821 for hunk in patch.split(patchfile):
3821 for hunk in patch.split(patchfile):
3822 (msg, node) = tryone(ui, hunk, parents)
3822 (msg, node) = tryone(ui, hunk, parents)
3823 if msg:
3823 if msg:
3824 haspatch = True
3824 haspatch = True
3825 ui.note(msg + '\n')
3825 ui.note(msg + '\n')
3826 if update or opts.get('exact'):
3826 if update or opts.get('exact'):
3827 parents = repo.parents()
3827 parents = repo.parents()
3828 else:
3828 else:
3829 parents = [repo[node]]
3829 parents = [repo[node]]
3830
3830
3831 if not haspatch:
3831 if not haspatch:
3832 raise util.Abort(_('%s: no diffs found') % patchurl)
3832 raise util.Abort(_('%s: no diffs found') % patchurl)
3833
3833
3834 if tr:
3834 if tr:
3835 tr.close()
3835 tr.close()
3836 if msgs:
3836 if msgs:
3837 repo.savecommitmessage('\n* * *\n'.join(msgs))
3837 repo.savecommitmessage('\n* * *\n'.join(msgs))
3838 except: # re-raises
3838 except: # re-raises
3839 # wlock.release() indirectly calls dirstate.write(): since
3839 # wlock.release() indirectly calls dirstate.write(): since
3840 # we're crashing, we do not want to change the working dir
3840 # we're crashing, we do not want to change the working dir
3841 # parent after all, so make sure it writes nothing
3841 # parent after all, so make sure it writes nothing
3842 repo.dirstate.invalidate()
3842 repo.dirstate.invalidate()
3843 raise
3843 raise
3844 finally:
3844 finally:
3845 if tr:
3845 if tr:
3846 tr.release()
3846 tr.release()
3847 release(lock, wlock)
3847 release(lock, wlock)
3848
3848
3849 @command('incoming|in',
3849 @command('incoming|in',
3850 [('f', 'force', None,
3850 [('f', 'force', None,
3851 _('run even if remote repository is unrelated')),
3851 _('run even if remote repository is unrelated')),
3852 ('n', 'newest-first', None, _('show newest record first')),
3852 ('n', 'newest-first', None, _('show newest record first')),
3853 ('', 'bundle', '',
3853 ('', 'bundle', '',
3854 _('file to store the bundles into'), _('FILE')),
3854 _('file to store the bundles into'), _('FILE')),
3855 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3855 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3856 ('B', 'bookmarks', False, _("compare bookmarks")),
3856 ('B', 'bookmarks', False, _("compare bookmarks")),
3857 ('b', 'branch', [],
3857 ('b', 'branch', [],
3858 _('a specific branch you would like to pull'), _('BRANCH')),
3858 _('a specific branch you would like to pull'), _('BRANCH')),
3859 ] + logopts + remoteopts + subrepoopts,
3859 ] + logopts + remoteopts + subrepoopts,
3860 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3860 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3861 def incoming(ui, repo, source="default", **opts):
3861 def incoming(ui, repo, source="default", **opts):
3862 """show new changesets found in source
3862 """show new changesets found in source
3863
3863
3864 Show new changesets found in the specified path/URL or the default
3864 Show new changesets found in the specified path/URL or the default
3865 pull location. These are the changesets that would have been pulled
3865 pull location. These are the changesets that would have been pulled
3866 if a pull at the time you issued this command.
3866 if a pull at the time you issued this command.
3867
3867
3868 For remote repository, using --bundle avoids downloading the
3868 For remote repository, using --bundle avoids downloading the
3869 changesets twice if the incoming is followed by a pull.
3869 changesets twice if the incoming is followed by a pull.
3870
3870
3871 See pull for valid source format details.
3871 See pull for valid source format details.
3872
3872
3873 Returns 0 if there are incoming changes, 1 otherwise.
3873 Returns 0 if there are incoming changes, 1 otherwise.
3874 """
3874 """
3875 if opts.get('graph'):
3875 if opts.get('graph'):
3876 cmdutil.checkunsupportedgraphflags([], opts)
3876 cmdutil.checkunsupportedgraphflags([], opts)
3877 def display(other, chlist, displayer):
3877 def display(other, chlist, displayer):
3878 revdag = cmdutil.graphrevs(other, chlist, opts)
3878 revdag = cmdutil.graphrevs(other, chlist, opts)
3879 showparents = [ctx.node() for ctx in repo[None].parents()]
3879 showparents = [ctx.node() for ctx in repo[None].parents()]
3880 cmdutil.displaygraph(ui, revdag, displayer, showparents,
3880 cmdutil.displaygraph(ui, revdag, displayer, showparents,
3881 graphmod.asciiedges)
3881 graphmod.asciiedges)
3882
3882
3883 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3883 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3884 return 0
3884 return 0
3885
3885
3886 if opts.get('bundle') and opts.get('subrepos'):
3886 if opts.get('bundle') and opts.get('subrepos'):
3887 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3887 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3888
3888
3889 if opts.get('bookmarks'):
3889 if opts.get('bookmarks'):
3890 source, branches = hg.parseurl(ui.expandpath(source),
3890 source, branches = hg.parseurl(ui.expandpath(source),
3891 opts.get('branch'))
3891 opts.get('branch'))
3892 other = hg.peer(repo, opts, source)
3892 other = hg.peer(repo, opts, source)
3893 if 'bookmarks' not in other.listkeys('namespaces'):
3893 if 'bookmarks' not in other.listkeys('namespaces'):
3894 ui.warn(_("remote doesn't support bookmarks\n"))
3894 ui.warn(_("remote doesn't support bookmarks\n"))
3895 return 0
3895 return 0
3896 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3896 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3897 return bookmarks.diff(ui, repo, other)
3897 return bookmarks.diff(ui, repo, other)
3898
3898
3899 repo._subtoppath = ui.expandpath(source)
3899 repo._subtoppath = ui.expandpath(source)
3900 try:
3900 try:
3901 return hg.incoming(ui, repo, source, opts)
3901 return hg.incoming(ui, repo, source, opts)
3902 finally:
3902 finally:
3903 del repo._subtoppath
3903 del repo._subtoppath
3904
3904
3905
3905
3906 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3906 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3907 def init(ui, dest=".", **opts):
3907 def init(ui, dest=".", **opts):
3908 """create a new repository in the given directory
3908 """create a new repository in the given directory
3909
3909
3910 Initialize a new repository in the given directory. If the given
3910 Initialize a new repository in the given directory. If the given
3911 directory does not exist, it will be created.
3911 directory does not exist, it will be created.
3912
3912
3913 If no directory is given, the current directory is used.
3913 If no directory is given, the current directory is used.
3914
3914
3915 It is possible to specify an ``ssh://`` URL as the destination.
3915 It is possible to specify an ``ssh://`` URL as the destination.
3916 See :hg:`help urls` for more information.
3916 See :hg:`help urls` for more information.
3917
3917
3918 Returns 0 on success.
3918 Returns 0 on success.
3919 """
3919 """
3920 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3920 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3921
3921
3922 @command('locate',
3922 @command('locate',
3923 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3923 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3924 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3924 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3925 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3925 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3926 ] + walkopts,
3926 ] + walkopts,
3927 _('[OPTION]... [PATTERN]...'))
3927 _('[OPTION]... [PATTERN]...'))
3928 def locate(ui, repo, *pats, **opts):
3928 def locate(ui, repo, *pats, **opts):
3929 """locate files matching specific patterns
3929 """locate files matching specific patterns
3930
3930
3931 Print files under Mercurial control in the working directory whose
3931 Print files under Mercurial control in the working directory whose
3932 names match the given patterns.
3932 names match the given patterns.
3933
3933
3934 By default, this command searches all directories in the working
3934 By default, this command searches all directories in the working
3935 directory. To search just the current directory and its
3935 directory. To search just the current directory and its
3936 subdirectories, use "--include .".
3936 subdirectories, use "--include .".
3937
3937
3938 If no patterns are given to match, this command prints the names
3938 If no patterns are given to match, this command prints the names
3939 of all files under Mercurial control in the working directory.
3939 of all files under Mercurial control in the working directory.
3940
3940
3941 If you want to feed the output of this command into the "xargs"
3941 If you want to feed the output of this command into the "xargs"
3942 command, use the -0 option to both this command and "xargs". This
3942 command, use the -0 option to both this command and "xargs". This
3943 will avoid the problem of "xargs" treating single filenames that
3943 will avoid the problem of "xargs" treating single filenames that
3944 contain whitespace as multiple filenames.
3944 contain whitespace as multiple filenames.
3945
3945
3946 Returns 0 if a match is found, 1 otherwise.
3946 Returns 0 if a match is found, 1 otherwise.
3947 """
3947 """
3948 end = opts.get('print0') and '\0' or '\n'
3948 end = opts.get('print0') and '\0' or '\n'
3949 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3949 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3950
3950
3951 ret = 1
3951 ret = 1
3952 m = scmutil.match(repo[rev], pats, opts, default='relglob')
3952 m = scmutil.match(repo[rev], pats, opts, default='relglob')
3953 m.bad = lambda x, y: False
3953 m.bad = lambda x, y: False
3954 for abs in repo[rev].walk(m):
3954 for abs in repo[rev].walk(m):
3955 if not rev and abs not in repo.dirstate:
3955 if not rev and abs not in repo.dirstate:
3956 continue
3956 continue
3957 if opts.get('fullpath'):
3957 if opts.get('fullpath'):
3958 ui.write(repo.wjoin(abs), end)
3958 ui.write(repo.wjoin(abs), end)
3959 else:
3959 else:
3960 ui.write(((pats and m.rel(abs)) or abs), end)
3960 ui.write(((pats and m.rel(abs)) or abs), end)
3961 ret = 0
3961 ret = 0
3962
3962
3963 return ret
3963 return ret
3964
3964
3965 @command('^log|history',
3965 @command('^log|history',
3966 [('f', 'follow', None,
3966 [('f', 'follow', None,
3967 _('follow changeset history, or file history across copies and renames')),
3967 _('follow changeset history, or file history across copies and renames')),
3968 ('', 'follow-first', None,
3968 ('', 'follow-first', None,
3969 _('only follow the first parent of merge changesets (DEPRECATED)')),
3969 _('only follow the first parent of merge changesets (DEPRECATED)')),
3970 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3970 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3971 ('C', 'copies', None, _('show copied files')),
3971 ('C', 'copies', None, _('show copied files')),
3972 ('k', 'keyword', [],
3972 ('k', 'keyword', [],
3973 _('do case-insensitive search for a given text'), _('TEXT')),
3973 _('do case-insensitive search for a given text'), _('TEXT')),
3974 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3974 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3975 ('', 'removed', None, _('include revisions where files were removed')),
3975 ('', 'removed', None, _('include revisions where files were removed')),
3976 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3976 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3977 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3977 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3978 ('', 'only-branch', [],
3978 ('', 'only-branch', [],
3979 _('show only changesets within the given named branch (DEPRECATED)'),
3979 _('show only changesets within the given named branch (DEPRECATED)'),
3980 _('BRANCH')),
3980 _('BRANCH')),
3981 ('b', 'branch', [],
3981 ('b', 'branch', [],
3982 _('show changesets within the given named branch'), _('BRANCH')),
3982 _('show changesets within the given named branch'), _('BRANCH')),
3983 ('P', 'prune', [],
3983 ('P', 'prune', [],
3984 _('do not display revision or any of its ancestors'), _('REV')),
3984 _('do not display revision or any of its ancestors'), _('REV')),
3985 ('', 'hidden', False, _('show hidden changesets (DEPRECATED)')),
3985 ('', 'hidden', False, _('show hidden changesets (DEPRECATED)')),
3986 ] + logopts + walkopts,
3986 ] + logopts + walkopts,
3987 _('[OPTION]... [FILE]'))
3987 _('[OPTION]... [FILE]'))
3988 def log(ui, repo, *pats, **opts):
3988 def log(ui, repo, *pats, **opts):
3989 """show revision history of entire repository or files
3989 """show revision history of entire repository or files
3990
3990
3991 Print the revision history of the specified files or the entire
3991 Print the revision history of the specified files or the entire
3992 project.
3992 project.
3993
3993
3994 If no revision range is specified, the default is ``tip:0`` unless
3994 If no revision range is specified, the default is ``tip:0`` unless
3995 --follow is set, in which case the working directory parent is
3995 --follow is set, in which case the working directory parent is
3996 used as the starting revision.
3996 used as the starting revision.
3997
3997
3998 File history is shown without following rename or copy history of
3998 File history is shown without following rename or copy history of
3999 files. Use -f/--follow with a filename to follow history across
3999 files. Use -f/--follow with a filename to follow history across
4000 renames and copies. --follow without a filename will only show
4000 renames and copies. --follow without a filename will only show
4001 ancestors or descendants of the starting revision.
4001 ancestors or descendants of the starting revision.
4002
4002
4003 By default this command prints revision number and changeset id,
4003 By default this command prints revision number and changeset id,
4004 tags, non-trivial parents, user, date and time, and a summary for
4004 tags, non-trivial parents, user, date and time, and a summary for
4005 each commit. When the -v/--verbose switch is used, the list of
4005 each commit. When the -v/--verbose switch is used, the list of
4006 changed files and full commit message are shown.
4006 changed files and full commit message are shown.
4007
4007
4008 .. note::
4008 .. note::
4009 log -p/--patch may generate unexpected diff output for merge
4009 log -p/--patch may generate unexpected diff output for merge
4010 changesets, as it will only compare the merge changeset against
4010 changesets, as it will only compare the merge changeset against
4011 its first parent. Also, only files different from BOTH parents
4011 its first parent. Also, only files different from BOTH parents
4012 will appear in files:.
4012 will appear in files:.
4013
4013
4014 .. note::
4014 .. note::
4015 for performance reasons, log FILE may omit duplicate changes
4015 for performance reasons, log FILE may omit duplicate changes
4016 made on branches and will not show deletions. To see all
4016 made on branches and will not show deletions. To see all
4017 changes including duplicates and deletions, use the --removed
4017 changes including duplicates and deletions, use the --removed
4018 switch.
4018 switch.
4019
4019
4020 .. container:: verbose
4020 .. container:: verbose
4021
4021
4022 Some examples:
4022 Some examples:
4023
4023
4024 - changesets with full descriptions and file lists::
4024 - changesets with full descriptions and file lists::
4025
4025
4026 hg log -v
4026 hg log -v
4027
4027
4028 - changesets ancestral to the working directory::
4028 - changesets ancestral to the working directory::
4029
4029
4030 hg log -f
4030 hg log -f
4031
4031
4032 - last 10 commits on the current branch::
4032 - last 10 commits on the current branch::
4033
4033
4034 hg log -l 10 -b .
4034 hg log -l 10 -b .
4035
4035
4036 - changesets showing all modifications of a file, including removals::
4036 - changesets showing all modifications of a file, including removals::
4037
4037
4038 hg log --removed file.c
4038 hg log --removed file.c
4039
4039
4040 - all changesets that touch a directory, with diffs, excluding merges::
4040 - all changesets that touch a directory, with diffs, excluding merges::
4041
4041
4042 hg log -Mp lib/
4042 hg log -Mp lib/
4043
4043
4044 - all revision numbers that match a keyword::
4044 - all revision numbers that match a keyword::
4045
4045
4046 hg log -k bug --template "{rev}\\n"
4046 hg log -k bug --template "{rev}\\n"
4047
4047
4048 - check if a given changeset is included is a tagged release::
4048 - check if a given changeset is included is a tagged release::
4049
4049
4050 hg log -r "a21ccf and ancestor(1.9)"
4050 hg log -r "a21ccf and ancestor(1.9)"
4051
4051
4052 - find all changesets by some user in a date range::
4052 - find all changesets by some user in a date range::
4053
4053
4054 hg log -k alice -d "may 2008 to jul 2008"
4054 hg log -k alice -d "may 2008 to jul 2008"
4055
4055
4056 - summary of all changesets after the last tag::
4056 - summary of all changesets after the last tag::
4057
4057
4058 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4058 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4059
4059
4060 See :hg:`help dates` for a list of formats valid for -d/--date.
4060 See :hg:`help dates` for a list of formats valid for -d/--date.
4061
4061
4062 See :hg:`help revisions` and :hg:`help revsets` for more about
4062 See :hg:`help revisions` and :hg:`help revsets` for more about
4063 specifying revisions.
4063 specifying revisions.
4064
4064
4065 See :hg:`help templates` for more about pre-packaged styles and
4065 See :hg:`help templates` for more about pre-packaged styles and
4066 specifying custom templates.
4066 specifying custom templates.
4067
4067
4068 Returns 0 on success.
4068 Returns 0 on success.
4069 """
4069 """
4070 if opts.get('graph'):
4070 if opts.get('graph'):
4071 return cmdutil.graphlog(ui, repo, *pats, **opts)
4071 return cmdutil.graphlog(ui, repo, *pats, **opts)
4072
4072
4073 matchfn = scmutil.match(repo[None], pats, opts)
4073 matchfn = scmutil.match(repo[None], pats, opts)
4074 limit = cmdutil.loglimit(opts)
4074 limit = cmdutil.loglimit(opts)
4075 count = 0
4075 count = 0
4076
4076
4077 getrenamed, endrev = None, None
4077 getrenamed, endrev = None, None
4078 if opts.get('copies'):
4078 if opts.get('copies'):
4079 if opts.get('rev'):
4079 if opts.get('rev'):
4080 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
4080 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
4081 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
4081 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
4082
4082
4083 df = False
4083 df = False
4084 if opts.get("date"):
4084 if opts.get("date"):
4085 df = util.matchdate(opts["date"])
4085 df = util.matchdate(opts["date"])
4086
4086
4087 branches = opts.get('branch', []) + opts.get('only_branch', [])
4087 branches = opts.get('branch', []) + opts.get('only_branch', [])
4088 opts['branch'] = [repo.lookupbranch(b) for b in branches]
4088 opts['branch'] = [repo.lookupbranch(b) for b in branches]
4089
4089
4090 displayer = cmdutil.show_changeset(ui, repo, opts, True)
4090 displayer = cmdutil.show_changeset(ui, repo, opts, True)
4091 def prep(ctx, fns):
4091 def prep(ctx, fns):
4092 rev = ctx.rev()
4092 rev = ctx.rev()
4093 parents = [p for p in repo.changelog.parentrevs(rev)
4093 parents = [p for p in repo.changelog.parentrevs(rev)
4094 if p != nullrev]
4094 if p != nullrev]
4095 if opts.get('no_merges') and len(parents) == 2:
4095 if opts.get('no_merges') and len(parents) == 2:
4096 return
4096 return
4097 if opts.get('only_merges') and len(parents) != 2:
4097 if opts.get('only_merges') and len(parents) != 2:
4098 return
4098 return
4099 if opts.get('branch') and ctx.branch() not in opts['branch']:
4099 if opts.get('branch') and ctx.branch() not in opts['branch']:
4100 return
4100 return
4101 if not opts.get('hidden') and ctx.hidden():
4101 if not opts.get('hidden') and ctx.hidden():
4102 return
4102 return
4103 if df and not df(ctx.date()[0]):
4103 if df and not df(ctx.date()[0]):
4104 return
4104 return
4105
4105
4106 lower = encoding.lower
4106 lower = encoding.lower
4107 if opts.get('user'):
4107 if opts.get('user'):
4108 luser = lower(ctx.user())
4108 luser = lower(ctx.user())
4109 for k in [lower(x) for x in opts['user']]:
4109 for k in [lower(x) for x in opts['user']]:
4110 if (k in luser):
4110 if (k in luser):
4111 break
4111 break
4112 else:
4112 else:
4113 return
4113 return
4114 if opts.get('keyword'):
4114 if opts.get('keyword'):
4115 luser = lower(ctx.user())
4115 luser = lower(ctx.user())
4116 ldesc = lower(ctx.description())
4116 ldesc = lower(ctx.description())
4117 lfiles = lower(" ".join(ctx.files()))
4117 lfiles = lower(" ".join(ctx.files()))
4118 for k in [lower(x) for x in opts['keyword']]:
4118 for k in [lower(x) for x in opts['keyword']]:
4119 if (k in luser or k in ldesc or k in lfiles):
4119 if (k in luser or k in ldesc or k in lfiles):
4120 break
4120 break
4121 else:
4121 else:
4122 return
4122 return
4123
4123
4124 copies = None
4124 copies = None
4125 if getrenamed is not None and rev:
4125 if getrenamed is not None and rev:
4126 copies = []
4126 copies = []
4127 for fn in ctx.files():
4127 for fn in ctx.files():
4128 rename = getrenamed(fn, rev)
4128 rename = getrenamed(fn, rev)
4129 if rename:
4129 if rename:
4130 copies.append((fn, rename[0]))
4130 copies.append((fn, rename[0]))
4131
4131
4132 revmatchfn = None
4132 revmatchfn = None
4133 if opts.get('patch') or opts.get('stat'):
4133 if opts.get('patch') or opts.get('stat'):
4134 if opts.get('follow') or opts.get('follow_first'):
4134 if opts.get('follow') or opts.get('follow_first'):
4135 # note: this might be wrong when following through merges
4135 # note: this might be wrong when following through merges
4136 revmatchfn = scmutil.match(repo[None], fns, default='path')
4136 revmatchfn = scmutil.match(repo[None], fns, default='path')
4137 else:
4137 else:
4138 revmatchfn = matchfn
4138 revmatchfn = matchfn
4139
4139
4140 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4140 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4141
4141
4142 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4142 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4143 if count == limit:
4143 if count == limit:
4144 break
4144 break
4145 if displayer.flush(ctx.rev()):
4145 if displayer.flush(ctx.rev()):
4146 count += 1
4146 count += 1
4147 displayer.close()
4147 displayer.close()
4148
4148
4149 @command('manifest',
4149 @command('manifest',
4150 [('r', 'rev', '', _('revision to display'), _('REV')),
4150 [('r', 'rev', '', _('revision to display'), _('REV')),
4151 ('', 'all', False, _("list files from all revisions"))],
4151 ('', 'all', False, _("list files from all revisions"))],
4152 _('[-r REV]'))
4152 _('[-r REV]'))
4153 def manifest(ui, repo, node=None, rev=None, **opts):
4153 def manifest(ui, repo, node=None, rev=None, **opts):
4154 """output the current or given revision of the project manifest
4154 """output the current or given revision of the project manifest
4155
4155
4156 Print a list of version controlled files for the given revision.
4156 Print a list of version controlled files for the given revision.
4157 If no revision is given, the first parent of the working directory
4157 If no revision is given, the first parent of the working directory
4158 is used, or the null revision if no revision is checked out.
4158 is used, or the null revision if no revision is checked out.
4159
4159
4160 With -v, print file permissions, symlink and executable bits.
4160 With -v, print file permissions, symlink and executable bits.
4161 With --debug, print file revision hashes.
4161 With --debug, print file revision hashes.
4162
4162
4163 If option --all is specified, the list of all files from all revisions
4163 If option --all is specified, the list of all files from all revisions
4164 is printed. This includes deleted and renamed files.
4164 is printed. This includes deleted and renamed files.
4165
4165
4166 Returns 0 on success.
4166 Returns 0 on success.
4167 """
4167 """
4168 if opts.get('all'):
4168 if opts.get('all'):
4169 if rev or node:
4169 if rev or node:
4170 raise util.Abort(_("can't specify a revision with --all"))
4170 raise util.Abort(_("can't specify a revision with --all"))
4171
4171
4172 res = []
4172 res = []
4173 prefix = "data/"
4173 prefix = "data/"
4174 suffix = ".i"
4174 suffix = ".i"
4175 plen = len(prefix)
4175 plen = len(prefix)
4176 slen = len(suffix)
4176 slen = len(suffix)
4177 lock = repo.lock()
4177 lock = repo.lock()
4178 try:
4178 try:
4179 for fn, b, size in repo.store.datafiles():
4179 for fn, b, size in repo.store.datafiles():
4180 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4180 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4181 res.append(fn[plen:-slen])
4181 res.append(fn[plen:-slen])
4182 finally:
4182 finally:
4183 lock.release()
4183 lock.release()
4184 for f in res:
4184 for f in res:
4185 ui.write("%s\n" % f)
4185 ui.write("%s\n" % f)
4186 return
4186 return
4187
4187
4188 if rev and node:
4188 if rev and node:
4189 raise util.Abort(_("please specify just one revision"))
4189 raise util.Abort(_("please specify just one revision"))
4190
4190
4191 if not node:
4191 if not node:
4192 node = rev
4192 node = rev
4193
4193
4194 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
4194 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
4195 ctx = scmutil.revsingle(repo, node)
4195 ctx = scmutil.revsingle(repo, node)
4196 for f in ctx:
4196 for f in ctx:
4197 if ui.debugflag:
4197 if ui.debugflag:
4198 ui.write("%40s " % hex(ctx.manifest()[f]))
4198 ui.write("%40s " % hex(ctx.manifest()[f]))
4199 if ui.verbose:
4199 if ui.verbose:
4200 ui.write(decor[ctx.flags(f)])
4200 ui.write(decor[ctx.flags(f)])
4201 ui.write("%s\n" % f)
4201 ui.write("%s\n" % f)
4202
4202
4203 @command('^merge',
4203 @command('^merge',
4204 [('f', 'force', None, _('force a merge with outstanding changes')),
4204 [('f', 'force', None, _('force a merge with outstanding changes')),
4205 ('r', 'rev', '', _('revision to merge'), _('REV')),
4205 ('r', 'rev', '', _('revision to merge'), _('REV')),
4206 ('P', 'preview', None,
4206 ('P', 'preview', None,
4207 _('review revisions to merge (no merge is performed)'))
4207 _('review revisions to merge (no merge is performed)'))
4208 ] + mergetoolopts,
4208 ] + mergetoolopts,
4209 _('[-P] [-f] [[-r] REV]'))
4209 _('[-P] [-f] [[-r] REV]'))
4210 def merge(ui, repo, node=None, **opts):
4210 def merge(ui, repo, node=None, **opts):
4211 """merge working directory with another revision
4211 """merge working directory with another revision
4212
4212
4213 The current working directory is updated with all changes made in
4213 The current working directory is updated with all changes made in
4214 the requested revision since the last common predecessor revision.
4214 the requested revision since the last common predecessor revision.
4215
4215
4216 Files that changed between either parent are marked as changed for
4216 Files that changed between either parent are marked as changed for
4217 the next commit and a commit must be performed before any further
4217 the next commit and a commit must be performed before any further
4218 updates to the repository are allowed. The next commit will have
4218 updates to the repository are allowed. The next commit will have
4219 two parents.
4219 two parents.
4220
4220
4221 ``--tool`` can be used to specify the merge tool used for file
4221 ``--tool`` can be used to specify the merge tool used for file
4222 merges. It overrides the HGMERGE environment variable and your
4222 merges. It overrides the HGMERGE environment variable and your
4223 configuration files. See :hg:`help merge-tools` for options.
4223 configuration files. See :hg:`help merge-tools` for options.
4224
4224
4225 If no revision is specified, the working directory's parent is a
4225 If no revision is specified, the working directory's parent is a
4226 head revision, and the current branch contains exactly one other
4226 head revision, and the current branch contains exactly one other
4227 head, the other head is merged with by default. Otherwise, an
4227 head, the other head is merged with by default. Otherwise, an
4228 explicit revision with which to merge with must be provided.
4228 explicit revision with which to merge with must be provided.
4229
4229
4230 :hg:`resolve` must be used to resolve unresolved files.
4230 :hg:`resolve` must be used to resolve unresolved files.
4231
4231
4232 To undo an uncommitted merge, use :hg:`update --clean .` which
4232 To undo an uncommitted merge, use :hg:`update --clean .` which
4233 will check out a clean copy of the original merge parent, losing
4233 will check out a clean copy of the original merge parent, losing
4234 all changes.
4234 all changes.
4235
4235
4236 Returns 0 on success, 1 if there are unresolved files.
4236 Returns 0 on success, 1 if there are unresolved files.
4237 """
4237 """
4238
4238
4239 if opts.get('rev') and node:
4239 if opts.get('rev') and node:
4240 raise util.Abort(_("please specify just one revision"))
4240 raise util.Abort(_("please specify just one revision"))
4241 if not node:
4241 if not node:
4242 node = opts.get('rev')
4242 node = opts.get('rev')
4243
4243
4244 if node:
4244 if node:
4245 node = scmutil.revsingle(repo, node).node()
4245 node = scmutil.revsingle(repo, node).node()
4246
4246
4247 if not node and repo._bookmarkcurrent:
4247 if not node and repo._bookmarkcurrent:
4248 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4248 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4249 curhead = repo[repo._bookmarkcurrent]
4249 curhead = repo[repo._bookmarkcurrent]
4250 if len(bmheads) == 2:
4250 if len(bmheads) == 2:
4251 if curhead == bmheads[0]:
4251 if curhead == bmheads[0]:
4252 node = bmheads[1]
4252 node = bmheads[1]
4253 else:
4253 else:
4254 node = bmheads[0]
4254 node = bmheads[0]
4255 elif len(bmheads) > 2:
4255 elif len(bmheads) > 2:
4256 raise util.Abort(_("multiple matching bookmarks to merge - "
4256 raise util.Abort(_("multiple matching bookmarks to merge - "
4257 "please merge with an explicit rev or bookmark"),
4257 "please merge with an explicit rev or bookmark"),
4258 hint=_("run 'hg heads' to see all heads"))
4258 hint=_("run 'hg heads' to see all heads"))
4259 elif len(bmheads) <= 1:
4259 elif len(bmheads) <= 1:
4260 raise util.Abort(_("no matching bookmark to merge - "
4260 raise util.Abort(_("no matching bookmark to merge - "
4261 "please merge with an explicit rev or bookmark"),
4261 "please merge with an explicit rev or bookmark"),
4262 hint=_("run 'hg heads' to see all heads"))
4262 hint=_("run 'hg heads' to see all heads"))
4263
4263
4264 if not node and not repo._bookmarkcurrent:
4264 if not node and not repo._bookmarkcurrent:
4265 branch = repo[None].branch()
4265 branch = repo[None].branch()
4266 bheads = repo.branchheads(branch)
4266 bheads = repo.branchheads(branch)
4267 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4267 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4268
4268
4269 if len(nbhs) > 2:
4269 if len(nbhs) > 2:
4270 raise util.Abort(_("branch '%s' has %d heads - "
4270 raise util.Abort(_("branch '%s' has %d heads - "
4271 "please merge with an explicit rev")
4271 "please merge with an explicit rev")
4272 % (branch, len(bheads)),
4272 % (branch, len(bheads)),
4273 hint=_("run 'hg heads .' to see heads"))
4273 hint=_("run 'hg heads .' to see heads"))
4274
4274
4275 parent = repo.dirstate.p1()
4275 parent = repo.dirstate.p1()
4276 if len(nbhs) <= 1:
4276 if len(nbhs) <= 1:
4277 if len(bheads) > 1:
4277 if len(bheads) > 1:
4278 raise util.Abort(_("heads are bookmarked - "
4278 raise util.Abort(_("heads are bookmarked - "
4279 "please merge with an explicit rev"),
4279 "please merge with an explicit rev"),
4280 hint=_("run 'hg heads' to see all heads"))
4280 hint=_("run 'hg heads' to see all heads"))
4281 if len(repo.heads()) > 1:
4281 if len(repo.heads()) > 1:
4282 raise util.Abort(_("branch '%s' has one head - "
4282 raise util.Abort(_("branch '%s' has one head - "
4283 "please merge with an explicit rev")
4283 "please merge with an explicit rev")
4284 % branch,
4284 % branch,
4285 hint=_("run 'hg heads' to see all heads"))
4285 hint=_("run 'hg heads' to see all heads"))
4286 msg, hint = _('nothing to merge'), None
4286 msg, hint = _('nothing to merge'), None
4287 if parent != repo.lookup(branch):
4287 if parent != repo.lookup(branch):
4288 hint = _("use 'hg update' instead")
4288 hint = _("use 'hg update' instead")
4289 raise util.Abort(msg, hint=hint)
4289 raise util.Abort(msg, hint=hint)
4290
4290
4291 if parent not in bheads:
4291 if parent not in bheads:
4292 raise util.Abort(_('working directory not at a head revision'),
4292 raise util.Abort(_('working directory not at a head revision'),
4293 hint=_("use 'hg update' or merge with an "
4293 hint=_("use 'hg update' or merge with an "
4294 "explicit revision"))
4294 "explicit revision"))
4295 if parent == nbhs[0]:
4295 if parent == nbhs[0]:
4296 node = nbhs[-1]
4296 node = nbhs[-1]
4297 else:
4297 else:
4298 node = nbhs[0]
4298 node = nbhs[0]
4299
4299
4300 if opts.get('preview'):
4300 if opts.get('preview'):
4301 # find nodes that are ancestors of p2 but not of p1
4301 # find nodes that are ancestors of p2 but not of p1
4302 p1 = repo.lookup('.')
4302 p1 = repo.lookup('.')
4303 p2 = repo.lookup(node)
4303 p2 = repo.lookup(node)
4304 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4304 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4305
4305
4306 displayer = cmdutil.show_changeset(ui, repo, opts)
4306 displayer = cmdutil.show_changeset(ui, repo, opts)
4307 for node in nodes:
4307 for node in nodes:
4308 displayer.show(repo[node])
4308 displayer.show(repo[node])
4309 displayer.close()
4309 displayer.close()
4310 return 0
4310 return 0
4311
4311
4312 try:
4312 try:
4313 # ui.forcemerge is an internal variable, do not document
4313 # ui.forcemerge is an internal variable, do not document
4314 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4314 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4315 return hg.merge(repo, node, force=opts.get('force'))
4315 return hg.merge(repo, node, force=opts.get('force'))
4316 finally:
4316 finally:
4317 ui.setconfig('ui', 'forcemerge', '')
4317 ui.setconfig('ui', 'forcemerge', '')
4318
4318
4319 @command('outgoing|out',
4319 @command('outgoing|out',
4320 [('f', 'force', None, _('run even when the destination is unrelated')),
4320 [('f', 'force', None, _('run even when the destination is unrelated')),
4321 ('r', 'rev', [],
4321 ('r', 'rev', [],
4322 _('a changeset intended to be included in the destination'), _('REV')),
4322 _('a changeset intended to be included in the destination'), _('REV')),
4323 ('n', 'newest-first', None, _('show newest record first')),
4323 ('n', 'newest-first', None, _('show newest record first')),
4324 ('B', 'bookmarks', False, _('compare bookmarks')),
4324 ('B', 'bookmarks', False, _('compare bookmarks')),
4325 ('b', 'branch', [], _('a specific branch you would like to push'),
4325 ('b', 'branch', [], _('a specific branch you would like to push'),
4326 _('BRANCH')),
4326 _('BRANCH')),
4327 ] + logopts + remoteopts + subrepoopts,
4327 ] + logopts + remoteopts + subrepoopts,
4328 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4328 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4329 def outgoing(ui, repo, dest=None, **opts):
4329 def outgoing(ui, repo, dest=None, **opts):
4330 """show changesets not found in the destination
4330 """show changesets not found in the destination
4331
4331
4332 Show changesets not found in the specified destination repository
4332 Show changesets not found in the specified destination repository
4333 or the default push location. These are the changesets that would
4333 or the default push location. These are the changesets that would
4334 be pushed if a push was requested.
4334 be pushed if a push was requested.
4335
4335
4336 See pull for details of valid destination formats.
4336 See pull for details of valid destination formats.
4337
4337
4338 Returns 0 if there are outgoing changes, 1 otherwise.
4338 Returns 0 if there are outgoing changes, 1 otherwise.
4339 """
4339 """
4340 if opts.get('graph'):
4340 if opts.get('graph'):
4341 cmdutil.checkunsupportedgraphflags([], opts)
4341 cmdutil.checkunsupportedgraphflags([], opts)
4342 o = hg._outgoing(ui, repo, dest, opts)
4342 o = hg._outgoing(ui, repo, dest, opts)
4343 if o is None:
4343 if o is None:
4344 return
4344 return
4345
4345
4346 revdag = cmdutil.graphrevs(repo, o, opts)
4346 revdag = cmdutil.graphrevs(repo, o, opts)
4347 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4347 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4348 showparents = [ctx.node() for ctx in repo[None].parents()]
4348 showparents = [ctx.node() for ctx in repo[None].parents()]
4349 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4349 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4350 graphmod.asciiedges)
4350 graphmod.asciiedges)
4351 return 0
4351 return 0
4352
4352
4353 if opts.get('bookmarks'):
4353 if opts.get('bookmarks'):
4354 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4354 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4355 dest, branches = hg.parseurl(dest, opts.get('branch'))
4355 dest, branches = hg.parseurl(dest, opts.get('branch'))
4356 other = hg.peer(repo, opts, dest)
4356 other = hg.peer(repo, opts, dest)
4357 if 'bookmarks' not in other.listkeys('namespaces'):
4357 if 'bookmarks' not in other.listkeys('namespaces'):
4358 ui.warn(_("remote doesn't support bookmarks\n"))
4358 ui.warn(_("remote doesn't support bookmarks\n"))
4359 return 0
4359 return 0
4360 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4360 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4361 return bookmarks.diff(ui, other, repo)
4361 return bookmarks.diff(ui, other, repo)
4362
4362
4363 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4363 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4364 try:
4364 try:
4365 return hg.outgoing(ui, repo, dest, opts)
4365 return hg.outgoing(ui, repo, dest, opts)
4366 finally:
4366 finally:
4367 del repo._subtoppath
4367 del repo._subtoppath
4368
4368
4369 @command('parents',
4369 @command('parents',
4370 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4370 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4371 ] + templateopts,
4371 ] + templateopts,
4372 _('[-r REV] [FILE]'))
4372 _('[-r REV] [FILE]'))
4373 def parents(ui, repo, file_=None, **opts):
4373 def parents(ui, repo, file_=None, **opts):
4374 """show the parents of the working directory or revision
4374 """show the parents of the working directory or revision
4375
4375
4376 Print the working directory's parent revisions. If a revision is
4376 Print the working directory's parent revisions. If a revision is
4377 given via -r/--rev, the parent of that revision will be printed.
4377 given via -r/--rev, the parent of that revision will be printed.
4378 If a file argument is given, the revision in which the file was
4378 If a file argument is given, the revision in which the file was
4379 last changed (before the working directory revision or the
4379 last changed (before the working directory revision or the
4380 argument to --rev if given) is printed.
4380 argument to --rev if given) is printed.
4381
4381
4382 Returns 0 on success.
4382 Returns 0 on success.
4383 """
4383 """
4384
4384
4385 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4385 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4386
4386
4387 if file_:
4387 if file_:
4388 m = scmutil.match(ctx, (file_,), opts)
4388 m = scmutil.match(ctx, (file_,), opts)
4389 if m.anypats() or len(m.files()) != 1:
4389 if m.anypats() or len(m.files()) != 1:
4390 raise util.Abort(_('can only specify an explicit filename'))
4390 raise util.Abort(_('can only specify an explicit filename'))
4391 file_ = m.files()[0]
4391 file_ = m.files()[0]
4392 filenodes = []
4392 filenodes = []
4393 for cp in ctx.parents():
4393 for cp in ctx.parents():
4394 if not cp:
4394 if not cp:
4395 continue
4395 continue
4396 try:
4396 try:
4397 filenodes.append(cp.filenode(file_))
4397 filenodes.append(cp.filenode(file_))
4398 except error.LookupError:
4398 except error.LookupError:
4399 pass
4399 pass
4400 if not filenodes:
4400 if not filenodes:
4401 raise util.Abort(_("'%s' not found in manifest!") % file_)
4401 raise util.Abort(_("'%s' not found in manifest!") % file_)
4402 fl = repo.file(file_)
4402 fl = repo.file(file_)
4403 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
4403 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
4404 else:
4404 else:
4405 p = [cp.node() for cp in ctx.parents()]
4405 p = [cp.node() for cp in ctx.parents()]
4406
4406
4407 displayer = cmdutil.show_changeset(ui, repo, opts)
4407 displayer = cmdutil.show_changeset(ui, repo, opts)
4408 for n in p:
4408 for n in p:
4409 if n != nullid:
4409 if n != nullid:
4410 displayer.show(repo[n])
4410 displayer.show(repo[n])
4411 displayer.close()
4411 displayer.close()
4412
4412
4413 @command('paths', [], _('[NAME]'))
4413 @command('paths', [], _('[NAME]'))
4414 def paths(ui, repo, search=None):
4414 def paths(ui, repo, search=None):
4415 """show aliases for remote repositories
4415 """show aliases for remote repositories
4416
4416
4417 Show definition of symbolic path name NAME. If no name is given,
4417 Show definition of symbolic path name NAME. If no name is given,
4418 show definition of all available names.
4418 show definition of all available names.
4419
4419
4420 Option -q/--quiet suppresses all output when searching for NAME
4420 Option -q/--quiet suppresses all output when searching for NAME
4421 and shows only the path names when listing all definitions.
4421 and shows only the path names when listing all definitions.
4422
4422
4423 Path names are defined in the [paths] section of your
4423 Path names are defined in the [paths] section of your
4424 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4424 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4425 repository, ``.hg/hgrc`` is used, too.
4425 repository, ``.hg/hgrc`` is used, too.
4426
4426
4427 The path names ``default`` and ``default-push`` have a special
4427 The path names ``default`` and ``default-push`` have a special
4428 meaning. When performing a push or pull operation, they are used
4428 meaning. When performing a push or pull operation, they are used
4429 as fallbacks if no location is specified on the command-line.
4429 as fallbacks if no location is specified on the command-line.
4430 When ``default-push`` is set, it will be used for push and
4430 When ``default-push`` is set, it will be used for push and
4431 ``default`` will be used for pull; otherwise ``default`` is used
4431 ``default`` will be used for pull; otherwise ``default`` is used
4432 as the fallback for both. When cloning a repository, the clone
4432 as the fallback for both. When cloning a repository, the clone
4433 source is written as ``default`` in ``.hg/hgrc``. Note that
4433 source is written as ``default`` in ``.hg/hgrc``. Note that
4434 ``default`` and ``default-push`` apply to all inbound (e.g.
4434 ``default`` and ``default-push`` apply to all inbound (e.g.
4435 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4435 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4436 :hg:`bundle`) operations.
4436 :hg:`bundle`) operations.
4437
4437
4438 See :hg:`help urls` for more information.
4438 See :hg:`help urls` for more information.
4439
4439
4440 Returns 0 on success.
4440 Returns 0 on success.
4441 """
4441 """
4442 if search:
4442 if search:
4443 for name, path in ui.configitems("paths"):
4443 for name, path in ui.configitems("paths"):
4444 if name == search:
4444 if name == search:
4445 ui.status("%s\n" % util.hidepassword(path))
4445 ui.status("%s\n" % util.hidepassword(path))
4446 return
4446 return
4447 if not ui.quiet:
4447 if not ui.quiet:
4448 ui.warn(_("not found!\n"))
4448 ui.warn(_("not found!\n"))
4449 return 1
4449 return 1
4450 else:
4450 else:
4451 for name, path in ui.configitems("paths"):
4451 for name, path in ui.configitems("paths"):
4452 if ui.quiet:
4452 if ui.quiet:
4453 ui.write("%s\n" % name)
4453 ui.write("%s\n" % name)
4454 else:
4454 else:
4455 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4455 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4456
4456
4457 @command('^phase',
4457 @command('^phase',
4458 [('p', 'public', False, _('set changeset phase to public')),
4458 [('p', 'public', False, _('set changeset phase to public')),
4459 ('d', 'draft', False, _('set changeset phase to draft')),
4459 ('d', 'draft', False, _('set changeset phase to draft')),
4460 ('s', 'secret', False, _('set changeset phase to secret')),
4460 ('s', 'secret', False, _('set changeset phase to secret')),
4461 ('f', 'force', False, _('allow to move boundary backward')),
4461 ('f', 'force', False, _('allow to move boundary backward')),
4462 ('r', 'rev', [], _('target revision'), _('REV')),
4462 ('r', 'rev', [], _('target revision'), _('REV')),
4463 ],
4463 ],
4464 _('[-p|-d|-s] [-f] [-r] REV...'))
4464 _('[-p|-d|-s] [-f] [-r] REV...'))
4465 def phase(ui, repo, *revs, **opts):
4465 def phase(ui, repo, *revs, **opts):
4466 """set or show the current phase name
4466 """set or show the current phase name
4467
4467
4468 With no argument, show the phase name of specified revisions.
4468 With no argument, show the phase name of specified revisions.
4469
4469
4470 With one of -p/--public, -d/--draft or -s/--secret, change the
4470 With one of -p/--public, -d/--draft or -s/--secret, change the
4471 phase value of the specified revisions.
4471 phase value of the specified revisions.
4472
4472
4473 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4473 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4474 lower phase to an higher phase. Phases are ordered as follows::
4474 lower phase to an higher phase. Phases are ordered as follows::
4475
4475
4476 public < draft < secret
4476 public < draft < secret
4477
4477
4478 Return 0 on success, 1 if no phases were changed or some could not
4478 Return 0 on success, 1 if no phases were changed or some could not
4479 be changed.
4479 be changed.
4480 """
4480 """
4481 # search for a unique phase argument
4481 # search for a unique phase argument
4482 targetphase = None
4482 targetphase = None
4483 for idx, name in enumerate(phases.phasenames):
4483 for idx, name in enumerate(phases.phasenames):
4484 if opts[name]:
4484 if opts[name]:
4485 if targetphase is not None:
4485 if targetphase is not None:
4486 raise util.Abort(_('only one phase can be specified'))
4486 raise util.Abort(_('only one phase can be specified'))
4487 targetphase = idx
4487 targetphase = idx
4488
4488
4489 # look for specified revision
4489 # look for specified revision
4490 revs = list(revs)
4490 revs = list(revs)
4491 revs.extend(opts['rev'])
4491 revs.extend(opts['rev'])
4492 if not revs:
4492 if not revs:
4493 raise util.Abort(_('no revisions specified'))
4493 raise util.Abort(_('no revisions specified'))
4494
4494
4495 revs = scmutil.revrange(repo, revs)
4495 revs = scmutil.revrange(repo, revs)
4496
4496
4497 lock = None
4497 lock = None
4498 ret = 0
4498 ret = 0
4499 if targetphase is None:
4499 if targetphase is None:
4500 # display
4500 # display
4501 for r in revs:
4501 for r in revs:
4502 ctx = repo[r]
4502 ctx = repo[r]
4503 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4503 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4504 else:
4504 else:
4505 lock = repo.lock()
4505 lock = repo.lock()
4506 try:
4506 try:
4507 # set phase
4507 # set phase
4508 if not revs:
4508 if not revs:
4509 raise util.Abort(_('empty revision set'))
4509 raise util.Abort(_('empty revision set'))
4510 nodes = [repo[r].node() for r in revs]
4510 nodes = [repo[r].node() for r in revs]
4511 olddata = repo._phasecache.getphaserevs(repo)[:]
4511 olddata = repo._phasecache.getphaserevs(repo)[:]
4512 phases.advanceboundary(repo, targetphase, nodes)
4512 phases.advanceboundary(repo, targetphase, nodes)
4513 if opts['force']:
4513 if opts['force']:
4514 phases.retractboundary(repo, targetphase, nodes)
4514 phases.retractboundary(repo, targetphase, nodes)
4515 finally:
4515 finally:
4516 lock.release()
4516 lock.release()
4517 newdata = repo._phasecache.getphaserevs(repo)
4517 newdata = repo._phasecache.getphaserevs(repo)
4518 changes = sum(o != newdata[i] for i, o in enumerate(olddata))
4518 changes = sum(o != newdata[i] for i, o in enumerate(olddata))
4519 rejected = [n for n in nodes
4519 rejected = [n for n in nodes
4520 if newdata[repo[n].rev()] < targetphase]
4520 if newdata[repo[n].rev()] < targetphase]
4521 if rejected:
4521 if rejected:
4522 ui.warn(_('cannot move %i changesets to a more permissive '
4522 ui.warn(_('cannot move %i changesets to a more permissive '
4523 'phase, use --force\n') % len(rejected))
4523 'phase, use --force\n') % len(rejected))
4524 ret = 1
4524 ret = 1
4525 if changes:
4525 if changes:
4526 msg = _('phase changed for %i changesets\n') % changes
4526 msg = _('phase changed for %i changesets\n') % changes
4527 if ret:
4527 if ret:
4528 ui.status(msg)
4528 ui.status(msg)
4529 else:
4529 else:
4530 ui.note(msg)
4530 ui.note(msg)
4531 else:
4531 else:
4532 ui.warn(_('no phases changed\n'))
4532 ui.warn(_('no phases changed\n'))
4533 ret = 1
4533 ret = 1
4534 return ret
4534 return ret
4535
4535
4536 def postincoming(ui, repo, modheads, optupdate, checkout):
4536 def postincoming(ui, repo, modheads, optupdate, checkout):
4537 if modheads == 0:
4537 if modheads == 0:
4538 return
4538 return
4539 if optupdate:
4539 if optupdate:
4540 movemarkfrom = repo['.'].node()
4540 movemarkfrom = repo['.'].node()
4541 try:
4541 try:
4542 ret = hg.update(repo, checkout)
4542 ret = hg.update(repo, checkout)
4543 except util.Abort, inst:
4543 except util.Abort, inst:
4544 ui.warn(_("not updating: %s\n") % str(inst))
4544 ui.warn(_("not updating: %s\n") % str(inst))
4545 return 0
4545 return 0
4546 if not ret and not checkout:
4546 if not ret and not checkout:
4547 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4547 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4548 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4548 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4549 return ret
4549 return ret
4550 if modheads > 1:
4550 if modheads > 1:
4551 currentbranchheads = len(repo.branchheads())
4551 currentbranchheads = len(repo.branchheads())
4552 if currentbranchheads == modheads:
4552 if currentbranchheads == modheads:
4553 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4553 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4554 elif currentbranchheads > 1:
4554 elif currentbranchheads > 1:
4555 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4555 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4556 "merge)\n"))
4556 "merge)\n"))
4557 else:
4557 else:
4558 ui.status(_("(run 'hg heads' to see heads)\n"))
4558 ui.status(_("(run 'hg heads' to see heads)\n"))
4559 else:
4559 else:
4560 ui.status(_("(run 'hg update' to get a working copy)\n"))
4560 ui.status(_("(run 'hg update' to get a working copy)\n"))
4561
4561
4562 @command('^pull',
4562 @command('^pull',
4563 [('u', 'update', None,
4563 [('u', 'update', None,
4564 _('update to new branch head if changesets were pulled')),
4564 _('update to new branch head if changesets were pulled')),
4565 ('f', 'force', None, _('run even when remote repository is unrelated')),
4565 ('f', 'force', None, _('run even when remote repository is unrelated')),
4566 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4566 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4567 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4567 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4568 ('b', 'branch', [], _('a specific branch you would like to pull'),
4568 ('b', 'branch', [], _('a specific branch you would like to pull'),
4569 _('BRANCH')),
4569 _('BRANCH')),
4570 ] + remoteopts,
4570 ] + remoteopts,
4571 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4571 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4572 def pull(ui, repo, source="default", **opts):
4572 def pull(ui, repo, source="default", **opts):
4573 """pull changes from the specified source
4573 """pull changes from the specified source
4574
4574
4575 Pull changes from a remote repository to a local one.
4575 Pull changes from a remote repository to a local one.
4576
4576
4577 This finds all changes from the repository at the specified path
4577 This finds all changes from the repository at the specified path
4578 or URL and adds them to a local repository (the current one unless
4578 or URL and adds them to a local repository (the current one unless
4579 -R is specified). By default, this does not update the copy of the
4579 -R is specified). By default, this does not update the copy of the
4580 project in the working directory.
4580 project in the working directory.
4581
4581
4582 Use :hg:`incoming` if you want to see what would have been added
4582 Use :hg:`incoming` if you want to see what would have been added
4583 by a pull at the time you issued this command. If you then decide
4583 by a pull at the time you issued this command. If you then decide
4584 to add those changes to the repository, you should use :hg:`pull
4584 to add those changes to the repository, you should use :hg:`pull
4585 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4585 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4586
4586
4587 If SOURCE is omitted, the 'default' path will be used.
4587 If SOURCE is omitted, the 'default' path will be used.
4588 See :hg:`help urls` for more information.
4588 See :hg:`help urls` for more information.
4589
4589
4590 Returns 0 on success, 1 if an update had unresolved files.
4590 Returns 0 on success, 1 if an update had unresolved files.
4591 """
4591 """
4592 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4592 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4593 other = hg.peer(repo, opts, source)
4593 other = hg.peer(repo, opts, source)
4594 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4594 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4595 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4595 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4596
4596
4597 if opts.get('bookmark'):
4597 if opts.get('bookmark'):
4598 if not revs:
4598 if not revs:
4599 revs = []
4599 revs = []
4600 rb = other.listkeys('bookmarks')
4600 rb = other.listkeys('bookmarks')
4601 for b in opts['bookmark']:
4601 for b in opts['bookmark']:
4602 if b not in rb:
4602 if b not in rb:
4603 raise util.Abort(_('remote bookmark %s not found!') % b)
4603 raise util.Abort(_('remote bookmark %s not found!') % b)
4604 revs.append(rb[b])
4604 revs.append(rb[b])
4605
4605
4606 if revs:
4606 if revs:
4607 try:
4607 try:
4608 revs = [other.lookup(rev) for rev in revs]
4608 revs = [other.lookup(rev) for rev in revs]
4609 except error.CapabilityError:
4609 except error.CapabilityError:
4610 err = _("other repository doesn't support revision lookup, "
4610 err = _("other repository doesn't support revision lookup, "
4611 "so a rev cannot be specified.")
4611 "so a rev cannot be specified.")
4612 raise util.Abort(err)
4612 raise util.Abort(err)
4613
4613
4614 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4614 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4615 bookmarks.updatefromremote(ui, repo, other, source)
4615 bookmarks.updatefromremote(ui, repo, other, source)
4616 if checkout:
4616 if checkout:
4617 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4617 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4618 repo._subtoppath = source
4618 repo._subtoppath = source
4619 try:
4619 try:
4620 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4620 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4621
4621
4622 finally:
4622 finally:
4623 del repo._subtoppath
4623 del repo._subtoppath
4624
4624
4625 # update specified bookmarks
4625 # update specified bookmarks
4626 if opts.get('bookmark'):
4626 if opts.get('bookmark'):
4627 for b in opts['bookmark']:
4627 for b in opts['bookmark']:
4628 # explicit pull overrides local bookmark if any
4628 # explicit pull overrides local bookmark if any
4629 ui.status(_("importing bookmark %s\n") % b)
4629 ui.status(_("importing bookmark %s\n") % b)
4630 repo._bookmarks[b] = repo[rb[b]].node()
4630 repo._bookmarks[b] = repo[rb[b]].node()
4631 bookmarks.write(repo)
4631 bookmarks.write(repo)
4632
4632
4633 return ret
4633 return ret
4634
4634
4635 @command('^push',
4635 @command('^push',
4636 [('f', 'force', None, _('force push')),
4636 [('f', 'force', None, _('force push')),
4637 ('r', 'rev', [],
4637 ('r', 'rev', [],
4638 _('a changeset intended to be included in the destination'),
4638 _('a changeset intended to be included in the destination'),
4639 _('REV')),
4639 _('REV')),
4640 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4640 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4641 ('b', 'branch', [],
4641 ('b', 'branch', [],
4642 _('a specific branch you would like to push'), _('BRANCH')),
4642 _('a specific branch you would like to push'), _('BRANCH')),
4643 ('', 'new-branch', False, _('allow pushing a new branch')),
4643 ('', 'new-branch', False, _('allow pushing a new branch')),
4644 ] + remoteopts,
4644 ] + remoteopts,
4645 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4645 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4646 def push(ui, repo, dest=None, **opts):
4646 def push(ui, repo, dest=None, **opts):
4647 """push changes to the specified destination
4647 """push changes to the specified destination
4648
4648
4649 Push changesets from the local repository to the specified
4649 Push changesets from the local repository to the specified
4650 destination.
4650 destination.
4651
4651
4652 This operation is symmetrical to pull: it is identical to a pull
4652 This operation is symmetrical to pull: it is identical to a pull
4653 in the destination repository from the current one.
4653 in the destination repository from the current one.
4654
4654
4655 By default, push will not allow creation of new heads at the
4655 By default, push will not allow creation of new heads at the
4656 destination, since multiple heads would make it unclear which head
4656 destination, since multiple heads would make it unclear which head
4657 to use. In this situation, it is recommended to pull and merge
4657 to use. In this situation, it is recommended to pull and merge
4658 before pushing.
4658 before pushing.
4659
4659
4660 Use --new-branch if you want to allow push to create a new named
4660 Use --new-branch if you want to allow push to create a new named
4661 branch that is not present at the destination. This allows you to
4661 branch that is not present at the destination. This allows you to
4662 only create a new branch without forcing other changes.
4662 only create a new branch without forcing other changes.
4663
4663
4664 Use -f/--force to override the default behavior and push all
4664 Use -f/--force to override the default behavior and push all
4665 changesets on all branches.
4665 changesets on all branches.
4666
4666
4667 If -r/--rev is used, the specified revision and all its ancestors
4667 If -r/--rev is used, the specified revision and all its ancestors
4668 will be pushed to the remote repository.
4668 will be pushed to the remote repository.
4669
4669
4670 If -B/--bookmark is used, the specified bookmarked revision, its
4670 If -B/--bookmark is used, the specified bookmarked revision, its
4671 ancestors, and the bookmark will be pushed to the remote
4671 ancestors, and the bookmark will be pushed to the remote
4672 repository.
4672 repository.
4673
4673
4674 Please see :hg:`help urls` for important details about ``ssh://``
4674 Please see :hg:`help urls` for important details about ``ssh://``
4675 URLs. If DESTINATION is omitted, a default path will be used.
4675 URLs. If DESTINATION is omitted, a default path will be used.
4676
4676
4677 Returns 0 if push was successful, 1 if nothing to push.
4677 Returns 0 if push was successful, 1 if nothing to push.
4678 """
4678 """
4679
4679
4680 if opts.get('bookmark'):
4680 if opts.get('bookmark'):
4681 for b in opts['bookmark']:
4681 for b in opts['bookmark']:
4682 # translate -B options to -r so changesets get pushed
4682 # translate -B options to -r so changesets get pushed
4683 if b in repo._bookmarks:
4683 if b in repo._bookmarks:
4684 opts.setdefault('rev', []).append(b)
4684 opts.setdefault('rev', []).append(b)
4685 else:
4685 else:
4686 # if we try to push a deleted bookmark, translate it to null
4686 # if we try to push a deleted bookmark, translate it to null
4687 # this lets simultaneous -r, -b options continue working
4687 # this lets simultaneous -r, -b options continue working
4688 opts.setdefault('rev', []).append("null")
4688 opts.setdefault('rev', []).append("null")
4689
4689
4690 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4690 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4691 dest, branches = hg.parseurl(dest, opts.get('branch'))
4691 dest, branches = hg.parseurl(dest, opts.get('branch'))
4692 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4692 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4693 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4693 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4694 other = hg.peer(repo, opts, dest)
4694 other = hg.peer(repo, opts, dest)
4695 if revs:
4695 if revs:
4696 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4696 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4697
4697
4698 repo._subtoppath = dest
4698 repo._subtoppath = dest
4699 try:
4699 try:
4700 # push subrepos depth-first for coherent ordering
4700 # push subrepos depth-first for coherent ordering
4701 c = repo['']
4701 c = repo['']
4702 subs = c.substate # only repos that are committed
4702 subs = c.substate # only repos that are committed
4703 for s in sorted(subs):
4703 for s in sorted(subs):
4704 if c.sub(s).push(opts) == 0:
4704 if c.sub(s).push(opts) == 0:
4705 return False
4705 return False
4706 finally:
4706 finally:
4707 del repo._subtoppath
4707 del repo._subtoppath
4708 result = repo.push(other, opts.get('force'), revs=revs,
4708 result = repo.push(other, opts.get('force'), revs=revs,
4709 newbranch=opts.get('new_branch'))
4709 newbranch=opts.get('new_branch'))
4710
4710
4711 result = not result
4711 result = not result
4712
4712
4713 if opts.get('bookmark'):
4713 if opts.get('bookmark'):
4714 rb = other.listkeys('bookmarks')
4714 rb = other.listkeys('bookmarks')
4715 for b in opts['bookmark']:
4715 for b in opts['bookmark']:
4716 # explicit push overrides remote bookmark if any
4716 # explicit push overrides remote bookmark if any
4717 if b in repo._bookmarks:
4717 if b in repo._bookmarks:
4718 ui.status(_("exporting bookmark %s\n") % b)
4718 ui.status(_("exporting bookmark %s\n") % b)
4719 new = repo[b].hex()
4719 new = repo[b].hex()
4720 elif b in rb:
4720 elif b in rb:
4721 ui.status(_("deleting remote bookmark %s\n") % b)
4721 ui.status(_("deleting remote bookmark %s\n") % b)
4722 new = '' # delete
4722 new = '' # delete
4723 else:
4723 else:
4724 ui.warn(_('bookmark %s does not exist on the local '
4724 ui.warn(_('bookmark %s does not exist on the local '
4725 'or remote repository!\n') % b)
4725 'or remote repository!\n') % b)
4726 return 2
4726 return 2
4727 old = rb.get(b, '')
4727 old = rb.get(b, '')
4728 r = other.pushkey('bookmarks', b, old, new)
4728 r = other.pushkey('bookmarks', b, old, new)
4729 if not r:
4729 if not r:
4730 ui.warn(_('updating bookmark %s failed!\n') % b)
4730 ui.warn(_('updating bookmark %s failed!\n') % b)
4731 if not result:
4731 if not result:
4732 result = 2
4732 result = 2
4733
4733
4734 return result
4734 return result
4735
4735
4736 @command('recover', [])
4736 @command('recover', [])
4737 def recover(ui, repo):
4737 def recover(ui, repo):
4738 """roll back an interrupted transaction
4738 """roll back an interrupted transaction
4739
4739
4740 Recover from an interrupted commit or pull.
4740 Recover from an interrupted commit or pull.
4741
4741
4742 This command tries to fix the repository status after an
4742 This command tries to fix the repository status after an
4743 interrupted operation. It should only be necessary when Mercurial
4743 interrupted operation. It should only be necessary when Mercurial
4744 suggests it.
4744 suggests it.
4745
4745
4746 Returns 0 if successful, 1 if nothing to recover or verify fails.
4746 Returns 0 if successful, 1 if nothing to recover or verify fails.
4747 """
4747 """
4748 if repo.recover():
4748 if repo.recover():
4749 return hg.verify(repo)
4749 return hg.verify(repo)
4750 return 1
4750 return 1
4751
4751
4752 @command('^remove|rm',
4752 @command('^remove|rm',
4753 [('A', 'after', None, _('record delete for missing files')),
4753 [('A', 'after', None, _('record delete for missing files')),
4754 ('f', 'force', None,
4754 ('f', 'force', None,
4755 _('remove (and delete) file even if added or modified')),
4755 _('remove (and delete) file even if added or modified')),
4756 ] + walkopts,
4756 ] + walkopts,
4757 _('[OPTION]... FILE...'))
4757 _('[OPTION]... FILE...'))
4758 def remove(ui, repo, *pats, **opts):
4758 def remove(ui, repo, *pats, **opts):
4759 """remove the specified files on the next commit
4759 """remove the specified files on the next commit
4760
4760
4761 Schedule the indicated files for removal from the current branch.
4761 Schedule the indicated files for removal from the current branch.
4762
4762
4763 This command schedules the files to be removed at the next commit.
4763 This command schedules the files to be removed at the next commit.
4764 To undo a remove before that, see :hg:`revert`. To undo added
4764 To undo a remove before that, see :hg:`revert`. To undo added
4765 files, see :hg:`forget`.
4765 files, see :hg:`forget`.
4766
4766
4767 .. container:: verbose
4767 .. container:: verbose
4768
4768
4769 -A/--after can be used to remove only files that have already
4769 -A/--after can be used to remove only files that have already
4770 been deleted, -f/--force can be used to force deletion, and -Af
4770 been deleted, -f/--force can be used to force deletion, and -Af
4771 can be used to remove files from the next revision without
4771 can be used to remove files from the next revision without
4772 deleting them from the working directory.
4772 deleting them from the working directory.
4773
4773
4774 The following table details the behavior of remove for different
4774 The following table details the behavior of remove for different
4775 file states (columns) and option combinations (rows). The file
4775 file states (columns) and option combinations (rows). The file
4776 states are Added [A], Clean [C], Modified [M] and Missing [!]
4776 states are Added [A], Clean [C], Modified [M] and Missing [!]
4777 (as reported by :hg:`status`). The actions are Warn, Remove
4777 (as reported by :hg:`status`). The actions are Warn, Remove
4778 (from branch) and Delete (from disk):
4778 (from branch) and Delete (from disk):
4779
4779
4780 ======= == == == ==
4780 ======= == == == ==
4781 A C M !
4781 A C M !
4782 ======= == == == ==
4782 ======= == == == ==
4783 none W RD W R
4783 none W RD W R
4784 -f R RD RD R
4784 -f R RD RD R
4785 -A W W W R
4785 -A W W W R
4786 -Af R R R R
4786 -Af R R R R
4787 ======= == == == ==
4787 ======= == == == ==
4788
4788
4789 Note that remove never deletes files in Added [A] state from the
4789 Note that remove never deletes files in Added [A] state from the
4790 working directory, not even if option --force is specified.
4790 working directory, not even if option --force is specified.
4791
4791
4792 Returns 0 on success, 1 if any warnings encountered.
4792 Returns 0 on success, 1 if any warnings encountered.
4793 """
4793 """
4794
4794
4795 ret = 0
4795 ret = 0
4796 after, force = opts.get('after'), opts.get('force')
4796 after, force = opts.get('after'), opts.get('force')
4797 if not pats and not after:
4797 if not pats and not after:
4798 raise util.Abort(_('no files specified'))
4798 raise util.Abort(_('no files specified'))
4799
4799
4800 m = scmutil.match(repo[None], pats, opts)
4800 m = scmutil.match(repo[None], pats, opts)
4801 s = repo.status(match=m, clean=True)
4801 s = repo.status(match=m, clean=True)
4802 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4802 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4803
4803
4804 for f in m.files():
4804 for f in m.files():
4805 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
4805 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
4806 if os.path.exists(m.rel(f)):
4806 if os.path.exists(m.rel(f)):
4807 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4807 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4808 ret = 1
4808 ret = 1
4809
4809
4810 if force:
4810 if force:
4811 list = modified + deleted + clean + added
4811 list = modified + deleted + clean + added
4812 elif after:
4812 elif after:
4813 list = deleted
4813 list = deleted
4814 for f in modified + added + clean:
4814 for f in modified + added + clean:
4815 ui.warn(_('not removing %s: file still exists (use -f'
4815 ui.warn(_('not removing %s: file still exists (use -f'
4816 ' to force removal)\n') % m.rel(f))
4816 ' to force removal)\n') % m.rel(f))
4817 ret = 1
4817 ret = 1
4818 else:
4818 else:
4819 list = deleted + clean
4819 list = deleted + clean
4820 for f in modified:
4820 for f in modified:
4821 ui.warn(_('not removing %s: file is modified (use -f'
4821 ui.warn(_('not removing %s: file is modified (use -f'
4822 ' to force removal)\n') % m.rel(f))
4822 ' to force removal)\n') % m.rel(f))
4823 ret = 1
4823 ret = 1
4824 for f in added:
4824 for f in added:
4825 ui.warn(_('not removing %s: file has been marked for add'
4825 ui.warn(_('not removing %s: file has been marked for add'
4826 ' (use forget to undo)\n') % m.rel(f))
4826 ' (use forget to undo)\n') % m.rel(f))
4827 ret = 1
4827 ret = 1
4828
4828
4829 for f in sorted(list):
4829 for f in sorted(list):
4830 if ui.verbose or not m.exact(f):
4830 if ui.verbose or not m.exact(f):
4831 ui.status(_('removing %s\n') % m.rel(f))
4831 ui.status(_('removing %s\n') % m.rel(f))
4832
4832
4833 wlock = repo.wlock()
4833 wlock = repo.wlock()
4834 try:
4834 try:
4835 if not after:
4835 if not after:
4836 for f in list:
4836 for f in list:
4837 if f in added:
4837 if f in added:
4838 continue # we never unlink added files on remove
4838 continue # we never unlink added files on remove
4839 try:
4839 try:
4840 util.unlinkpath(repo.wjoin(f))
4840 util.unlinkpath(repo.wjoin(f))
4841 except OSError, inst:
4841 except OSError, inst:
4842 if inst.errno != errno.ENOENT:
4842 if inst.errno != errno.ENOENT:
4843 raise
4843 raise
4844 repo[None].forget(list)
4844 repo[None].forget(list)
4845 finally:
4845 finally:
4846 wlock.release()
4846 wlock.release()
4847
4847
4848 return ret
4848 return ret
4849
4849
4850 @command('rename|move|mv',
4850 @command('rename|move|mv',
4851 [('A', 'after', None, _('record a rename that has already occurred')),
4851 [('A', 'after', None, _('record a rename that has already occurred')),
4852 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4852 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4853 ] + walkopts + dryrunopts,
4853 ] + walkopts + dryrunopts,
4854 _('[OPTION]... SOURCE... DEST'))
4854 _('[OPTION]... SOURCE... DEST'))
4855 def rename(ui, repo, *pats, **opts):
4855 def rename(ui, repo, *pats, **opts):
4856 """rename files; equivalent of copy + remove
4856 """rename files; equivalent of copy + remove
4857
4857
4858 Mark dest as copies of sources; mark sources for deletion. If dest
4858 Mark dest as copies of sources; mark sources for deletion. If dest
4859 is a directory, copies are put in that directory. If dest is a
4859 is a directory, copies are put in that directory. If dest is a
4860 file, there can only be one source.
4860 file, there can only be one source.
4861
4861
4862 By default, this command copies the contents of files as they
4862 By default, this command copies the contents of files as they
4863 exist in the working directory. If invoked with -A/--after, the
4863 exist in the working directory. If invoked with -A/--after, the
4864 operation is recorded, but no copying is performed.
4864 operation is recorded, but no copying is performed.
4865
4865
4866 This command takes effect at the next commit. To undo a rename
4866 This command takes effect at the next commit. To undo a rename
4867 before that, see :hg:`revert`.
4867 before that, see :hg:`revert`.
4868
4868
4869 Returns 0 on success, 1 if errors are encountered.
4869 Returns 0 on success, 1 if errors are encountered.
4870 """
4870 """
4871 wlock = repo.wlock(False)
4871 wlock = repo.wlock(False)
4872 try:
4872 try:
4873 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4873 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4874 finally:
4874 finally:
4875 wlock.release()
4875 wlock.release()
4876
4876
4877 @command('resolve',
4877 @command('resolve',
4878 [('a', 'all', None, _('select all unresolved files')),
4878 [('a', 'all', None, _('select all unresolved files')),
4879 ('l', 'list', None, _('list state of files needing merge')),
4879 ('l', 'list', None, _('list state of files needing merge')),
4880 ('m', 'mark', None, _('mark files as resolved')),
4880 ('m', 'mark', None, _('mark files as resolved')),
4881 ('u', 'unmark', None, _('mark files as unresolved')),
4881 ('u', 'unmark', None, _('mark files as unresolved')),
4882 ('n', 'no-status', None, _('hide status prefix'))]
4882 ('n', 'no-status', None, _('hide status prefix'))]
4883 + mergetoolopts + walkopts,
4883 + mergetoolopts + walkopts,
4884 _('[OPTION]... [FILE]...'))
4884 _('[OPTION]... [FILE]...'))
4885 def resolve(ui, repo, *pats, **opts):
4885 def resolve(ui, repo, *pats, **opts):
4886 """redo merges or set/view the merge status of files
4886 """redo merges or set/view the merge status of files
4887
4887
4888 Merges with unresolved conflicts are often the result of
4888 Merges with unresolved conflicts are often the result of
4889 non-interactive merging using the ``internal:merge`` configuration
4889 non-interactive merging using the ``internal:merge`` configuration
4890 setting, or a command-line merge tool like ``diff3``. The resolve
4890 setting, or a command-line merge tool like ``diff3``. The resolve
4891 command is used to manage the files involved in a merge, after
4891 command is used to manage the files involved in a merge, after
4892 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4892 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4893 working directory must have two parents). See :hg:`help
4893 working directory must have two parents). See :hg:`help
4894 merge-tools` for information on configuring merge tools.
4894 merge-tools` for information on configuring merge tools.
4895
4895
4896 The resolve command can be used in the following ways:
4896 The resolve command can be used in the following ways:
4897
4897
4898 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4898 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4899 files, discarding any previous merge attempts. Re-merging is not
4899 files, discarding any previous merge attempts. Re-merging is not
4900 performed for files already marked as resolved. Use ``--all/-a``
4900 performed for files already marked as resolved. Use ``--all/-a``
4901 to select all unresolved files. ``--tool`` can be used to specify
4901 to select all unresolved files. ``--tool`` can be used to specify
4902 the merge tool used for the given files. It overrides the HGMERGE
4902 the merge tool used for the given files. It overrides the HGMERGE
4903 environment variable and your configuration files. Previous file
4903 environment variable and your configuration files. Previous file
4904 contents are saved with a ``.orig`` suffix.
4904 contents are saved with a ``.orig`` suffix.
4905
4905
4906 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4906 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4907 (e.g. after having manually fixed-up the files). The default is
4907 (e.g. after having manually fixed-up the files). The default is
4908 to mark all unresolved files.
4908 to mark all unresolved files.
4909
4909
4910 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4910 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4911 default is to mark all resolved files.
4911 default is to mark all resolved files.
4912
4912
4913 - :hg:`resolve -l`: list files which had or still have conflicts.
4913 - :hg:`resolve -l`: list files which had or still have conflicts.
4914 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4914 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4915
4915
4916 Note that Mercurial will not let you commit files with unresolved
4916 Note that Mercurial will not let you commit files with unresolved
4917 merge conflicts. You must use :hg:`resolve -m ...` before you can
4917 merge conflicts. You must use :hg:`resolve -m ...` before you can
4918 commit after a conflicting merge.
4918 commit after a conflicting merge.
4919
4919
4920 Returns 0 on success, 1 if any files fail a resolve attempt.
4920 Returns 0 on success, 1 if any files fail a resolve attempt.
4921 """
4921 """
4922
4922
4923 all, mark, unmark, show, nostatus = \
4923 all, mark, unmark, show, nostatus = \
4924 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4924 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4925
4925
4926 if (show and (mark or unmark)) or (mark and unmark):
4926 if (show and (mark or unmark)) or (mark and unmark):
4927 raise util.Abort(_("too many options specified"))
4927 raise util.Abort(_("too many options specified"))
4928 if pats and all:
4928 if pats and all:
4929 raise util.Abort(_("can't specify --all and patterns"))
4929 raise util.Abort(_("can't specify --all and patterns"))
4930 if not (all or pats or show or mark or unmark):
4930 if not (all or pats or show or mark or unmark):
4931 raise util.Abort(_('no files or directories specified; '
4931 raise util.Abort(_('no files or directories specified; '
4932 'use --all to remerge all files'))
4932 'use --all to remerge all files'))
4933
4933
4934 ms = mergemod.mergestate(repo)
4934 ms = mergemod.mergestate(repo)
4935 m = scmutil.match(repo[None], pats, opts)
4935 m = scmutil.match(repo[None], pats, opts)
4936 ret = 0
4936 ret = 0
4937
4937
4938 for f in ms:
4938 for f in ms:
4939 if m(f):
4939 if m(f):
4940 if show:
4940 if show:
4941 if nostatus:
4941 if nostatus:
4942 ui.write("%s\n" % f)
4942 ui.write("%s\n" % f)
4943 else:
4943 else:
4944 ui.write("%s %s\n" % (ms[f].upper(), f),
4944 ui.write("%s %s\n" % (ms[f].upper(), f),
4945 label='resolve.' +
4945 label='resolve.' +
4946 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4946 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4947 elif mark:
4947 elif mark:
4948 ms.mark(f, "r")
4948 ms.mark(f, "r")
4949 elif unmark:
4949 elif unmark:
4950 ms.mark(f, "u")
4950 ms.mark(f, "u")
4951 else:
4951 else:
4952 wctx = repo[None]
4952 wctx = repo[None]
4953 mctx = wctx.parents()[-1]
4953 mctx = wctx.parents()[-1]
4954
4954
4955 # backup pre-resolve (merge uses .orig for its own purposes)
4955 # backup pre-resolve (merge uses .orig for its own purposes)
4956 a = repo.wjoin(f)
4956 a = repo.wjoin(f)
4957 util.copyfile(a, a + ".resolve")
4957 util.copyfile(a, a + ".resolve")
4958
4958
4959 try:
4959 try:
4960 # resolve file
4960 # resolve file
4961 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4961 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4962 if ms.resolve(f, wctx, mctx):
4962 if ms.resolve(f, wctx, mctx):
4963 ret = 1
4963 ret = 1
4964 finally:
4964 finally:
4965 ui.setconfig('ui', 'forcemerge', '')
4965 ui.setconfig('ui', 'forcemerge', '')
4966
4966
4967 # replace filemerge's .orig file with our resolve file
4967 # replace filemerge's .orig file with our resolve file
4968 util.rename(a + ".resolve", a + ".orig")
4968 util.rename(a + ".resolve", a + ".orig")
4969
4969
4970 ms.commit()
4970 ms.commit()
4971 return ret
4971 return ret
4972
4972
4973 @command('revert',
4973 @command('revert',
4974 [('a', 'all', None, _('revert all changes when no arguments given')),
4974 [('a', 'all', None, _('revert all changes when no arguments given')),
4975 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4975 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4976 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4976 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4977 ('C', 'no-backup', None, _('do not save backup copies of files')),
4977 ('C', 'no-backup', None, _('do not save backup copies of files')),
4978 ] + walkopts + dryrunopts,
4978 ] + walkopts + dryrunopts,
4979 _('[OPTION]... [-r REV] [NAME]...'))
4979 _('[OPTION]... [-r REV] [NAME]...'))
4980 def revert(ui, repo, *pats, **opts):
4980 def revert(ui, repo, *pats, **opts):
4981 """restore files to their checkout state
4981 """restore files to their checkout state
4982
4982
4983 .. note::
4983 .. note::
4984
4984
4985 To check out earlier revisions, you should use :hg:`update REV`.
4985 To check out earlier revisions, you should use :hg:`update REV`.
4986 To cancel an uncommitted merge (and lose your changes), use
4986 To cancel an uncommitted merge (and lose your changes), use
4987 :hg:`update --clean .`.
4987 :hg:`update --clean .`.
4988
4988
4989 With no revision specified, revert the specified files or directories
4989 With no revision specified, revert the specified files or directories
4990 to the contents they had in the parent of the working directory.
4990 to the contents they had in the parent of the working directory.
4991 This restores the contents of files to an unmodified
4991 This restores the contents of files to an unmodified
4992 state and unschedules adds, removes, copies, and renames. If the
4992 state and unschedules adds, removes, copies, and renames. If the
4993 working directory has two parents, you must explicitly specify a
4993 working directory has two parents, you must explicitly specify a
4994 revision.
4994 revision.
4995
4995
4996 Using the -r/--rev or -d/--date options, revert the given files or
4996 Using the -r/--rev or -d/--date options, revert the given files or
4997 directories to their states as of a specific revision. Because
4997 directories to their states as of a specific revision. Because
4998 revert does not change the working directory parents, this will
4998 revert does not change the working directory parents, this will
4999 cause these files to appear modified. This can be helpful to "back
4999 cause these files to appear modified. This can be helpful to "back
5000 out" some or all of an earlier change. See :hg:`backout` for a
5000 out" some or all of an earlier change. See :hg:`backout` for a
5001 related method.
5001 related method.
5002
5002
5003 Modified files are saved with a .orig suffix before reverting.
5003 Modified files are saved with a .orig suffix before reverting.
5004 To disable these backups, use --no-backup.
5004 To disable these backups, use --no-backup.
5005
5005
5006 See :hg:`help dates` for a list of formats valid for -d/--date.
5006 See :hg:`help dates` for a list of formats valid for -d/--date.
5007
5007
5008 Returns 0 on success.
5008 Returns 0 on success.
5009 """
5009 """
5010
5010
5011 if opts.get("date"):
5011 if opts.get("date"):
5012 if opts.get("rev"):
5012 if opts.get("rev"):
5013 raise util.Abort(_("you can't specify a revision and a date"))
5013 raise util.Abort(_("you can't specify a revision and a date"))
5014 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5014 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5015
5015
5016 parent, p2 = repo.dirstate.parents()
5016 parent, p2 = repo.dirstate.parents()
5017 if not opts.get('rev') and p2 != nullid:
5017 if not opts.get('rev') and p2 != nullid:
5018 # revert after merge is a trap for new users (issue2915)
5018 # revert after merge is a trap for new users (issue2915)
5019 raise util.Abort(_('uncommitted merge with no revision specified'),
5019 raise util.Abort(_('uncommitted merge with no revision specified'),
5020 hint=_('use "hg update" or see "hg help revert"'))
5020 hint=_('use "hg update" or see "hg help revert"'))
5021
5021
5022 ctx = scmutil.revsingle(repo, opts.get('rev'))
5022 ctx = scmutil.revsingle(repo, opts.get('rev'))
5023
5023
5024 if not pats and not opts.get('all'):
5024 if not pats and not opts.get('all'):
5025 msg = _("no files or directories specified")
5025 msg = _("no files or directories specified")
5026 if p2 != nullid:
5026 if p2 != nullid:
5027 hint = _("uncommitted merge, use --all to discard all changes,"
5027 hint = _("uncommitted merge, use --all to discard all changes,"
5028 " or 'hg update -C .' to abort the merge")
5028 " or 'hg update -C .' to abort the merge")
5029 raise util.Abort(msg, hint=hint)
5029 raise util.Abort(msg, hint=hint)
5030 dirty = util.any(repo.status())
5030 dirty = util.any(repo.status())
5031 node = ctx.node()
5031 node = ctx.node()
5032 if node != parent:
5032 if node != parent:
5033 if dirty:
5033 if dirty:
5034 hint = _("uncommitted changes, use --all to discard all"
5034 hint = _("uncommitted changes, use --all to discard all"
5035 " changes, or 'hg update %s' to update") % ctx.rev()
5035 " changes, or 'hg update %s' to update") % ctx.rev()
5036 else:
5036 else:
5037 hint = _("use --all to revert all files,"
5037 hint = _("use --all to revert all files,"
5038 " or 'hg update %s' to update") % ctx.rev()
5038 " or 'hg update %s' to update") % ctx.rev()
5039 elif dirty:
5039 elif dirty:
5040 hint = _("uncommitted changes, use --all to discard all changes")
5040 hint = _("uncommitted changes, use --all to discard all changes")
5041 else:
5041 else:
5042 hint = _("use --all to revert all files")
5042 hint = _("use --all to revert all files")
5043 raise util.Abort(msg, hint=hint)
5043 raise util.Abort(msg, hint=hint)
5044
5044
5045 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
5045 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
5046
5046
5047 @command('rollback', dryrunopts +
5047 @command('rollback', dryrunopts +
5048 [('f', 'force', False, _('ignore safety measures'))])
5048 [('f', 'force', False, _('ignore safety measures'))])
5049 def rollback(ui, repo, **opts):
5049 def rollback(ui, repo, **opts):
5050 """roll back the last transaction (dangerous)
5050 """roll back the last transaction (dangerous)
5051
5051
5052 This command should be used with care. There is only one level of
5052 This command should be used with care. There is only one level of
5053 rollback, and there is no way to undo a rollback. It will also
5053 rollback, and there is no way to undo a rollback. It will also
5054 restore the dirstate at the time of the last transaction, losing
5054 restore the dirstate at the time of the last transaction, losing
5055 any dirstate changes since that time. This command does not alter
5055 any dirstate changes since that time. This command does not alter
5056 the working directory.
5056 the working directory.
5057
5057
5058 Transactions are used to encapsulate the effects of all commands
5058 Transactions are used to encapsulate the effects of all commands
5059 that create new changesets or propagate existing changesets into a
5059 that create new changesets or propagate existing changesets into a
5060 repository.
5060 repository.
5061
5061
5062 .. container:: verbose
5062 .. container:: verbose
5063
5063
5064 For example, the following commands are transactional, and their
5064 For example, the following commands are transactional, and their
5065 effects can be rolled back:
5065 effects can be rolled back:
5066
5066
5067 - commit
5067 - commit
5068 - import
5068 - import
5069 - pull
5069 - pull
5070 - push (with this repository as the destination)
5070 - push (with this repository as the destination)
5071 - unbundle
5071 - unbundle
5072
5072
5073 To avoid permanent data loss, rollback will refuse to rollback a
5073 To avoid permanent data loss, rollback will refuse to rollback a
5074 commit transaction if it isn't checked out. Use --force to
5074 commit transaction if it isn't checked out. Use --force to
5075 override this protection.
5075 override this protection.
5076
5076
5077 This command is not intended for use on public repositories. Once
5077 This command is not intended for use on public repositories. Once
5078 changes are visible for pull by other users, rolling a transaction
5078 changes are visible for pull by other users, rolling a transaction
5079 back locally is ineffective (someone else may already have pulled
5079 back locally is ineffective (someone else may already have pulled
5080 the changes). Furthermore, a race is possible with readers of the
5080 the changes). Furthermore, a race is possible with readers of the
5081 repository; for example an in-progress pull from the repository
5081 repository; for example an in-progress pull from the repository
5082 may fail if a rollback is performed.
5082 may fail if a rollback is performed.
5083
5083
5084 Returns 0 on success, 1 if no rollback data is available.
5084 Returns 0 on success, 1 if no rollback data is available.
5085 """
5085 """
5086 return repo.rollback(dryrun=opts.get('dry_run'),
5086 return repo.rollback(dryrun=opts.get('dry_run'),
5087 force=opts.get('force'))
5087 force=opts.get('force'))
5088
5088
5089 @command('root', [])
5089 @command('root', [])
5090 def root(ui, repo):
5090 def root(ui, repo):
5091 """print the root (top) of the current working directory
5091 """print the root (top) of the current working directory
5092
5092
5093 Print the root directory of the current repository.
5093 Print the root directory of the current repository.
5094
5094
5095 Returns 0 on success.
5095 Returns 0 on success.
5096 """
5096 """
5097 ui.write(repo.root + "\n")
5097 ui.write(repo.root + "\n")
5098
5098
5099 @command('^serve',
5099 @command('^serve',
5100 [('A', 'accesslog', '', _('name of access log file to write to'),
5100 [('A', 'accesslog', '', _('name of access log file to write to'),
5101 _('FILE')),
5101 _('FILE')),
5102 ('d', 'daemon', None, _('run server in background')),
5102 ('d', 'daemon', None, _('run server in background')),
5103 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
5103 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
5104 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5104 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5105 # use string type, then we can check if something was passed
5105 # use string type, then we can check if something was passed
5106 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5106 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5107 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5107 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5108 _('ADDR')),
5108 _('ADDR')),
5109 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5109 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5110 _('PREFIX')),
5110 _('PREFIX')),
5111 ('n', 'name', '',
5111 ('n', 'name', '',
5112 _('name to show in web pages (default: working directory)'), _('NAME')),
5112 _('name to show in web pages (default: working directory)'), _('NAME')),
5113 ('', 'web-conf', '',
5113 ('', 'web-conf', '',
5114 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5114 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5115 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5115 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5116 _('FILE')),
5116 _('FILE')),
5117 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5117 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5118 ('', 'stdio', None, _('for remote clients')),
5118 ('', 'stdio', None, _('for remote clients')),
5119 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5119 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5120 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5120 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5121 ('', 'style', '', _('template style to use'), _('STYLE')),
5121 ('', 'style', '', _('template style to use'), _('STYLE')),
5122 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5122 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5123 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5123 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5124 _('[OPTION]...'))
5124 _('[OPTION]...'))
5125 def serve(ui, repo, **opts):
5125 def serve(ui, repo, **opts):
5126 """start stand-alone webserver
5126 """start stand-alone webserver
5127
5127
5128 Start a local HTTP repository browser and pull server. You can use
5128 Start a local HTTP repository browser and pull server. You can use
5129 this for ad-hoc sharing and browsing of repositories. It is
5129 this for ad-hoc sharing and browsing of repositories. It is
5130 recommended to use a real web server to serve a repository for
5130 recommended to use a real web server to serve a repository for
5131 longer periods of time.
5131 longer periods of time.
5132
5132
5133 Please note that the server does not implement access control.
5133 Please note that the server does not implement access control.
5134 This means that, by default, anybody can read from the server and
5134 This means that, by default, anybody can read from the server and
5135 nobody can write to it by default. Set the ``web.allow_push``
5135 nobody can write to it by default. Set the ``web.allow_push``
5136 option to ``*`` to allow everybody to push to the server. You
5136 option to ``*`` to allow everybody to push to the server. You
5137 should use a real web server if you need to authenticate users.
5137 should use a real web server if you need to authenticate users.
5138
5138
5139 By default, the server logs accesses to stdout and errors to
5139 By default, the server logs accesses to stdout and errors to
5140 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5140 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5141 files.
5141 files.
5142
5142
5143 To have the server choose a free port number to listen on, specify
5143 To have the server choose a free port number to listen on, specify
5144 a port number of 0; in this case, the server will print the port
5144 a port number of 0; in this case, the server will print the port
5145 number it uses.
5145 number it uses.
5146
5146
5147 Returns 0 on success.
5147 Returns 0 on success.
5148 """
5148 """
5149
5149
5150 if opts["stdio"] and opts["cmdserver"]:
5150 if opts["stdio"] and opts["cmdserver"]:
5151 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5151 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5152
5152
5153 def checkrepo():
5153 def checkrepo():
5154 if repo is None:
5154 if repo is None:
5155 raise error.RepoError(_("there is no Mercurial repository here"
5155 raise error.RepoError(_("there is no Mercurial repository here"
5156 " (.hg not found)"))
5156 " (.hg not found)"))
5157
5157
5158 if opts["stdio"]:
5158 if opts["stdio"]:
5159 checkrepo()
5159 checkrepo()
5160 s = sshserver.sshserver(ui, repo)
5160 s = sshserver.sshserver(ui, repo)
5161 s.serve_forever()
5161 s.serve_forever()
5162
5162
5163 if opts["cmdserver"]:
5163 if opts["cmdserver"]:
5164 checkrepo()
5164 checkrepo()
5165 s = commandserver.server(ui, repo, opts["cmdserver"])
5165 s = commandserver.server(ui, repo, opts["cmdserver"])
5166 return s.serve()
5166 return s.serve()
5167
5167
5168 # this way we can check if something was given in the command-line
5168 # this way we can check if something was given in the command-line
5169 if opts.get('port'):
5169 if opts.get('port'):
5170 opts['port'] = util.getport(opts.get('port'))
5170 opts['port'] = util.getport(opts.get('port'))
5171
5171
5172 baseui = repo and repo.baseui or ui
5172 baseui = repo and repo.baseui or ui
5173 optlist = ("name templates style address port prefix ipv6"
5173 optlist = ("name templates style address port prefix ipv6"
5174 " accesslog errorlog certificate encoding")
5174 " accesslog errorlog certificate encoding")
5175 for o in optlist.split():
5175 for o in optlist.split():
5176 val = opts.get(o, '')
5176 val = opts.get(o, '')
5177 if val in (None, ''): # should check against default options instead
5177 if val in (None, ''): # should check against default options instead
5178 continue
5178 continue
5179 baseui.setconfig("web", o, val)
5179 baseui.setconfig("web", o, val)
5180 if repo and repo.ui != baseui:
5180 if repo and repo.ui != baseui:
5181 repo.ui.setconfig("web", o, val)
5181 repo.ui.setconfig("web", o, val)
5182
5182
5183 o = opts.get('web_conf') or opts.get('webdir_conf')
5183 o = opts.get('web_conf') or opts.get('webdir_conf')
5184 if not o:
5184 if not o:
5185 if not repo:
5185 if not repo:
5186 raise error.RepoError(_("there is no Mercurial repository"
5186 raise error.RepoError(_("there is no Mercurial repository"
5187 " here (.hg not found)"))
5187 " here (.hg not found)"))
5188 o = repo.root
5188 o = repo.root
5189
5189
5190 app = hgweb.hgweb(o, baseui=ui)
5190 app = hgweb.hgweb(o, baseui=ui)
5191
5191
5192 class service(object):
5192 class service(object):
5193 def init(self):
5193 def init(self):
5194 util.setsignalhandler()
5194 util.setsignalhandler()
5195 self.httpd = hgweb.server.create_server(ui, app)
5195 self.httpd = hgweb.server.create_server(ui, app)
5196
5196
5197 if opts['port'] and not ui.verbose:
5197 if opts['port'] and not ui.verbose:
5198 return
5198 return
5199
5199
5200 if self.httpd.prefix:
5200 if self.httpd.prefix:
5201 prefix = self.httpd.prefix.strip('/') + '/'
5201 prefix = self.httpd.prefix.strip('/') + '/'
5202 else:
5202 else:
5203 prefix = ''
5203 prefix = ''
5204
5204
5205 port = ':%d' % self.httpd.port
5205 port = ':%d' % self.httpd.port
5206 if port == ':80':
5206 if port == ':80':
5207 port = ''
5207 port = ''
5208
5208
5209 bindaddr = self.httpd.addr
5209 bindaddr = self.httpd.addr
5210 if bindaddr == '0.0.0.0':
5210 if bindaddr == '0.0.0.0':
5211 bindaddr = '*'
5211 bindaddr = '*'
5212 elif ':' in bindaddr: # IPv6
5212 elif ':' in bindaddr: # IPv6
5213 bindaddr = '[%s]' % bindaddr
5213 bindaddr = '[%s]' % bindaddr
5214
5214
5215 fqaddr = self.httpd.fqaddr
5215 fqaddr = self.httpd.fqaddr
5216 if ':' in fqaddr:
5216 if ':' in fqaddr:
5217 fqaddr = '[%s]' % fqaddr
5217 fqaddr = '[%s]' % fqaddr
5218 if opts['port']:
5218 if opts['port']:
5219 write = ui.status
5219 write = ui.status
5220 else:
5220 else:
5221 write = ui.write
5221 write = ui.write
5222 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5222 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5223 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5223 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5224
5224
5225 def run(self):
5225 def run(self):
5226 self.httpd.serve_forever()
5226 self.httpd.serve_forever()
5227
5227
5228 service = service()
5228 service = service()
5229
5229
5230 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5230 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5231
5231
5232 @command('showconfig|debugconfig',
5232 @command('showconfig|debugconfig',
5233 [('u', 'untrusted', None, _('show untrusted configuration options'))],
5233 [('u', 'untrusted', None, _('show untrusted configuration options'))],
5234 _('[-u] [NAME]...'))
5234 _('[-u] [NAME]...'))
5235 def showconfig(ui, repo, *values, **opts):
5235 def showconfig(ui, repo, *values, **opts):
5236 """show combined config settings from all hgrc files
5236 """show combined config settings from all hgrc files
5237
5237
5238 With no arguments, print names and values of all config items.
5238 With no arguments, print names and values of all config items.
5239
5239
5240 With one argument of the form section.name, print just the value
5240 With one argument of the form section.name, print just the value
5241 of that config item.
5241 of that config item.
5242
5242
5243 With multiple arguments, print names and values of all config
5243 With multiple arguments, print names and values of all config
5244 items with matching section names.
5244 items with matching section names.
5245
5245
5246 With --debug, the source (filename and line number) is printed
5246 With --debug, the source (filename and line number) is printed
5247 for each config item.
5247 for each config item.
5248
5248
5249 Returns 0 on success.
5249 Returns 0 on success.
5250 """
5250 """
5251
5251
5252 for f in scmutil.rcpath():
5252 for f in scmutil.rcpath():
5253 ui.debug('read config from: %s\n' % f)
5253 ui.debug('read config from: %s\n' % f)
5254 untrusted = bool(opts.get('untrusted'))
5254 untrusted = bool(opts.get('untrusted'))
5255 if values:
5255 if values:
5256 sections = [v for v in values if '.' not in v]
5256 sections = [v for v in values if '.' not in v]
5257 items = [v for v in values if '.' in v]
5257 items = [v for v in values if '.' in v]
5258 if len(items) > 1 or items and sections:
5258 if len(items) > 1 or items and sections:
5259 raise util.Abort(_('only one config item permitted'))
5259 raise util.Abort(_('only one config item permitted'))
5260 for section, name, value in ui.walkconfig(untrusted=untrusted):
5260 for section, name, value in ui.walkconfig(untrusted=untrusted):
5261 value = str(value).replace('\n', '\\n')
5261 value = str(value).replace('\n', '\\n')
5262 sectname = section + '.' + name
5262 sectname = section + '.' + name
5263 if values:
5263 if values:
5264 for v in values:
5264 for v in values:
5265 if v == section:
5265 if v == section:
5266 ui.debug('%s: ' %
5266 ui.debug('%s: ' %
5267 ui.configsource(section, name, untrusted))
5267 ui.configsource(section, name, untrusted))
5268 ui.write('%s=%s\n' % (sectname, value))
5268 ui.write('%s=%s\n' % (sectname, value))
5269 elif v == sectname:
5269 elif v == sectname:
5270 ui.debug('%s: ' %
5270 ui.debug('%s: ' %
5271 ui.configsource(section, name, untrusted))
5271 ui.configsource(section, name, untrusted))
5272 ui.write(value, '\n')
5272 ui.write(value, '\n')
5273 else:
5273 else:
5274 ui.debug('%s: ' %
5274 ui.debug('%s: ' %
5275 ui.configsource(section, name, untrusted))
5275 ui.configsource(section, name, untrusted))
5276 ui.write('%s=%s\n' % (sectname, value))
5276 ui.write('%s=%s\n' % (sectname, value))
5277
5277
5278 @command('^status|st',
5278 @command('^status|st',
5279 [('A', 'all', None, _('show status of all files')),
5279 [('A', 'all', None, _('show status of all files')),
5280 ('m', 'modified', None, _('show only modified files')),
5280 ('m', 'modified', None, _('show only modified files')),
5281 ('a', 'added', None, _('show only added files')),
5281 ('a', 'added', None, _('show only added files')),
5282 ('r', 'removed', None, _('show only removed files')),
5282 ('r', 'removed', None, _('show only removed files')),
5283 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5283 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5284 ('c', 'clean', None, _('show only files without changes')),
5284 ('c', 'clean', None, _('show only files without changes')),
5285 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5285 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5286 ('i', 'ignored', None, _('show only ignored files')),
5286 ('i', 'ignored', None, _('show only ignored files')),
5287 ('n', 'no-status', None, _('hide status prefix')),
5287 ('n', 'no-status', None, _('hide status prefix')),
5288 ('C', 'copies', None, _('show source of copied files')),
5288 ('C', 'copies', None, _('show source of copied files')),
5289 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5289 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5290 ('', 'rev', [], _('show difference from revision'), _('REV')),
5290 ('', 'rev', [], _('show difference from revision'), _('REV')),
5291 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5291 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5292 ] + walkopts + subrepoopts,
5292 ] + walkopts + subrepoopts,
5293 _('[OPTION]... [FILE]...'))
5293 _('[OPTION]... [FILE]...'))
5294 def status(ui, repo, *pats, **opts):
5294 def status(ui, repo, *pats, **opts):
5295 """show changed files in the working directory
5295 """show changed files in the working directory
5296
5296
5297 Show status of files in the repository. If names are given, only
5297 Show status of files in the repository. If names are given, only
5298 files that match are shown. Files that are clean or ignored or
5298 files that match are shown. Files that are clean or ignored or
5299 the source of a copy/move operation, are not listed unless
5299 the source of a copy/move operation, are not listed unless
5300 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5300 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5301 Unless options described with "show only ..." are given, the
5301 Unless options described with "show only ..." are given, the
5302 options -mardu are used.
5302 options -mardu are used.
5303
5303
5304 Option -q/--quiet hides untracked (unknown and ignored) files
5304 Option -q/--quiet hides untracked (unknown and ignored) files
5305 unless explicitly requested with -u/--unknown or -i/--ignored.
5305 unless explicitly requested with -u/--unknown or -i/--ignored.
5306
5306
5307 .. note::
5307 .. note::
5308 status may appear to disagree with diff if permissions have
5308 status may appear to disagree with diff if permissions have
5309 changed or a merge has occurred. The standard diff format does
5309 changed or a merge has occurred. The standard diff format does
5310 not report permission changes and diff only reports changes
5310 not report permission changes and diff only reports changes
5311 relative to one merge parent.
5311 relative to one merge parent.
5312
5312
5313 If one revision is given, it is used as the base revision.
5313 If one revision is given, it is used as the base revision.
5314 If two revisions are given, the differences between them are
5314 If two revisions are given, the differences between them are
5315 shown. The --change option can also be used as a shortcut to list
5315 shown. The --change option can also be used as a shortcut to list
5316 the changed files of a revision from its first parent.
5316 the changed files of a revision from its first parent.
5317
5317
5318 The codes used to show the status of files are::
5318 The codes used to show the status of files are::
5319
5319
5320 M = modified
5320 M = modified
5321 A = added
5321 A = added
5322 R = removed
5322 R = removed
5323 C = clean
5323 C = clean
5324 ! = missing (deleted by non-hg command, but still tracked)
5324 ! = missing (deleted by non-hg command, but still tracked)
5325 ? = not tracked
5325 ? = not tracked
5326 I = ignored
5326 I = ignored
5327 = origin of the previous file listed as A (added)
5327 = origin of the previous file listed as A (added)
5328
5328
5329 .. container:: verbose
5329 .. container:: verbose
5330
5330
5331 Examples:
5331 Examples:
5332
5332
5333 - show changes in the working directory relative to a
5333 - show changes in the working directory relative to a
5334 changeset::
5334 changeset::
5335
5335
5336 hg status --rev 9353
5336 hg status --rev 9353
5337
5337
5338 - show all changes including copies in an existing changeset::
5338 - show all changes including copies in an existing changeset::
5339
5339
5340 hg status --copies --change 9353
5340 hg status --copies --change 9353
5341
5341
5342 - get a NUL separated list of added files, suitable for xargs::
5342 - get a NUL separated list of added files, suitable for xargs::
5343
5343
5344 hg status -an0
5344 hg status -an0
5345
5345
5346 Returns 0 on success.
5346 Returns 0 on success.
5347 """
5347 """
5348
5348
5349 revs = opts.get('rev')
5349 revs = opts.get('rev')
5350 change = opts.get('change')
5350 change = opts.get('change')
5351
5351
5352 if revs and change:
5352 if revs and change:
5353 msg = _('cannot specify --rev and --change at the same time')
5353 msg = _('cannot specify --rev and --change at the same time')
5354 raise util.Abort(msg)
5354 raise util.Abort(msg)
5355 elif change:
5355 elif change:
5356 node2 = scmutil.revsingle(repo, change, None).node()
5356 node2 = scmutil.revsingle(repo, change, None).node()
5357 node1 = repo[node2].p1().node()
5357 node1 = repo[node2].p1().node()
5358 else:
5358 else:
5359 node1, node2 = scmutil.revpair(repo, revs)
5359 node1, node2 = scmutil.revpair(repo, revs)
5360
5360
5361 cwd = (pats and repo.getcwd()) or ''
5361 cwd = (pats and repo.getcwd()) or ''
5362 end = opts.get('print0') and '\0' or '\n'
5362 end = opts.get('print0') and '\0' or '\n'
5363 copy = {}
5363 copy = {}
5364 states = 'modified added removed deleted unknown ignored clean'.split()
5364 states = 'modified added removed deleted unknown ignored clean'.split()
5365 show = [k for k in states if opts.get(k)]
5365 show = [k for k in states if opts.get(k)]
5366 if opts.get('all'):
5366 if opts.get('all'):
5367 show += ui.quiet and (states[:4] + ['clean']) or states
5367 show += ui.quiet and (states[:4] + ['clean']) or states
5368 if not show:
5368 if not show:
5369 show = ui.quiet and states[:4] or states[:5]
5369 show = ui.quiet and states[:4] or states[:5]
5370
5370
5371 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5371 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5372 'ignored' in show, 'clean' in show, 'unknown' in show,
5372 'ignored' in show, 'clean' in show, 'unknown' in show,
5373 opts.get('subrepos'))
5373 opts.get('subrepos'))
5374 changestates = zip(states, 'MAR!?IC', stat)
5374 changestates = zip(states, 'MAR!?IC', stat)
5375
5375
5376 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5376 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5377 copy = copies.pathcopies(repo[node1], repo[node2])
5377 copy = copies.pathcopies(repo[node1], repo[node2])
5378
5378
5379 fm = ui.formatter('status', opts)
5379 fm = ui.formatter('status', opts)
5380 format = '%s %s' + end
5380 format = '%s %s' + end
5381 if opts.get('no_status'):
5381 if opts.get('no_status'):
5382 format = '%.0s%s' + end
5382 format = '%.0s%s' + end
5383
5383
5384 for state, char, files in changestates:
5384 for state, char, files in changestates:
5385 if state in show:
5385 if state in show:
5386 label = 'status.' + state
5386 label = 'status.' + state
5387 for f in files:
5387 for f in files:
5388 fm.startitem()
5388 fm.startitem()
5389 fm.write("status path", format, char,
5389 fm.write("status path", format, char,
5390 repo.pathto(f, cwd), label=label)
5390 repo.pathto(f, cwd), label=label)
5391 if f in copy:
5391 if f in copy:
5392 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5392 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5393 label='status.copied')
5393 label='status.copied')
5394 fm.end()
5394 fm.end()
5395
5395
5396 @command('^summary|sum',
5396 @command('^summary|sum',
5397 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5397 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5398 def summary(ui, repo, **opts):
5398 def summary(ui, repo, **opts):
5399 """summarize working directory state
5399 """summarize working directory state
5400
5400
5401 This generates a brief summary of the working directory state,
5401 This generates a brief summary of the working directory state,
5402 including parents, branch, commit status, and available updates.
5402 including parents, branch, commit status, and available updates.
5403
5403
5404 With the --remote option, this will check the default paths for
5404 With the --remote option, this will check the default paths for
5405 incoming and outgoing changes. This can be time-consuming.
5405 incoming and outgoing changes. This can be time-consuming.
5406
5406
5407 Returns 0 on success.
5407 Returns 0 on success.
5408 """
5408 """
5409
5409
5410 ctx = repo[None]
5410 ctx = repo[None]
5411 parents = ctx.parents()
5411 parents = ctx.parents()
5412 pnode = parents[0].node()
5412 pnode = parents[0].node()
5413 marks = []
5413 marks = []
5414
5414
5415 for p in parents:
5415 for p in parents:
5416 # label with log.changeset (instead of log.parent) since this
5416 # label with log.changeset (instead of log.parent) since this
5417 # shows a working directory parent *changeset*:
5417 # shows a working directory parent *changeset*:
5418 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5418 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5419 label='log.changeset')
5419 label='log.changeset')
5420 ui.write(' '.join(p.tags()), label='log.tag')
5420 ui.write(' '.join(p.tags()), label='log.tag')
5421 if p.bookmarks():
5421 if p.bookmarks():
5422 marks.extend(p.bookmarks())
5422 marks.extend(p.bookmarks())
5423 if p.rev() == -1:
5423 if p.rev() == -1:
5424 if not len(repo):
5424 if not len(repo):
5425 ui.write(_(' (empty repository)'))
5425 ui.write(_(' (empty repository)'))
5426 else:
5426 else:
5427 ui.write(_(' (no revision checked out)'))
5427 ui.write(_(' (no revision checked out)'))
5428 ui.write('\n')
5428 ui.write('\n')
5429 if p.description():
5429 if p.description():
5430 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5430 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5431 label='log.summary')
5431 label='log.summary')
5432
5432
5433 branch = ctx.branch()
5433 branch = ctx.branch()
5434 bheads = repo.branchheads(branch)
5434 bheads = repo.branchheads(branch)
5435 m = _('branch: %s\n') % branch
5435 m = _('branch: %s\n') % branch
5436 if branch != 'default':
5436 if branch != 'default':
5437 ui.write(m, label='log.branch')
5437 ui.write(m, label='log.branch')
5438 else:
5438 else:
5439 ui.status(m, label='log.branch')
5439 ui.status(m, label='log.branch')
5440
5440
5441 if marks:
5441 if marks:
5442 current = repo._bookmarkcurrent
5442 current = repo._bookmarkcurrent
5443 ui.write(_('bookmarks:'), label='log.bookmark')
5443 ui.write(_('bookmarks:'), label='log.bookmark')
5444 if current is not None:
5444 if current is not None:
5445 try:
5445 try:
5446 marks.remove(current)
5446 marks.remove(current)
5447 ui.write(' *' + current, label='bookmarks.current')
5447 ui.write(' *' + current, label='bookmarks.current')
5448 except ValueError:
5448 except ValueError:
5449 # current bookmark not in parent ctx marks
5449 # current bookmark not in parent ctx marks
5450 pass
5450 pass
5451 for m in marks:
5451 for m in marks:
5452 ui.write(' ' + m, label='log.bookmark')
5452 ui.write(' ' + m, label='log.bookmark')
5453 ui.write('\n', label='log.bookmark')
5453 ui.write('\n', label='log.bookmark')
5454
5454
5455 st = list(repo.status(unknown=True))[:6]
5455 st = list(repo.status(unknown=True))[:6]
5456
5456
5457 c = repo.dirstate.copies()
5457 c = repo.dirstate.copies()
5458 copied, renamed = [], []
5458 copied, renamed = [], []
5459 for d, s in c.iteritems():
5459 for d, s in c.iteritems():
5460 if s in st[2]:
5460 if s in st[2]:
5461 st[2].remove(s)
5461 st[2].remove(s)
5462 renamed.append(d)
5462 renamed.append(d)
5463 else:
5463 else:
5464 copied.append(d)
5464 copied.append(d)
5465 if d in st[1]:
5465 if d in st[1]:
5466 st[1].remove(d)
5466 st[1].remove(d)
5467 st.insert(3, renamed)
5467 st.insert(3, renamed)
5468 st.insert(4, copied)
5468 st.insert(4, copied)
5469
5469
5470 ms = mergemod.mergestate(repo)
5470 ms = mergemod.mergestate(repo)
5471 st.append([f for f in ms if ms[f] == 'u'])
5471 st.append([f for f in ms if ms[f] == 'u'])
5472
5472
5473 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5473 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5474 st.append(subs)
5474 st.append(subs)
5475
5475
5476 labels = [ui.label(_('%d modified'), 'status.modified'),
5476 labels = [ui.label(_('%d modified'), 'status.modified'),
5477 ui.label(_('%d added'), 'status.added'),
5477 ui.label(_('%d added'), 'status.added'),
5478 ui.label(_('%d removed'), 'status.removed'),
5478 ui.label(_('%d removed'), 'status.removed'),
5479 ui.label(_('%d renamed'), 'status.copied'),
5479 ui.label(_('%d renamed'), 'status.copied'),
5480 ui.label(_('%d copied'), 'status.copied'),
5480 ui.label(_('%d copied'), 'status.copied'),
5481 ui.label(_('%d deleted'), 'status.deleted'),
5481 ui.label(_('%d deleted'), 'status.deleted'),
5482 ui.label(_('%d unknown'), 'status.unknown'),
5482 ui.label(_('%d unknown'), 'status.unknown'),
5483 ui.label(_('%d ignored'), 'status.ignored'),
5483 ui.label(_('%d ignored'), 'status.ignored'),
5484 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5484 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5485 ui.label(_('%d subrepos'), 'status.modified')]
5485 ui.label(_('%d subrepos'), 'status.modified')]
5486 t = []
5486 t = []
5487 for s, l in zip(st, labels):
5487 for s, l in zip(st, labels):
5488 if s:
5488 if s:
5489 t.append(l % len(s))
5489 t.append(l % len(s))
5490
5490
5491 t = ', '.join(t)
5491 t = ', '.join(t)
5492 cleanworkdir = False
5492 cleanworkdir = False
5493
5493
5494 if len(parents) > 1:
5494 if len(parents) > 1:
5495 t += _(' (merge)')
5495 t += _(' (merge)')
5496 elif branch != parents[0].branch():
5496 elif branch != parents[0].branch():
5497 t += _(' (new branch)')
5497 t += _(' (new branch)')
5498 elif (parents[0].closesbranch() and
5498 elif (parents[0].closesbranch() and
5499 pnode in repo.branchheads(branch, closed=True)):
5499 pnode in repo.branchheads(branch, closed=True)):
5500 t += _(' (head closed)')
5500 t += _(' (head closed)')
5501 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5501 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5502 t += _(' (clean)')
5502 t += _(' (clean)')
5503 cleanworkdir = True
5503 cleanworkdir = True
5504 elif pnode not in bheads:
5504 elif pnode not in bheads:
5505 t += _(' (new branch head)')
5505 t += _(' (new branch head)')
5506
5506
5507 if cleanworkdir:
5507 if cleanworkdir:
5508 ui.status(_('commit: %s\n') % t.strip())
5508 ui.status(_('commit: %s\n') % t.strip())
5509 else:
5509 else:
5510 ui.write(_('commit: %s\n') % t.strip())
5510 ui.write(_('commit: %s\n') % t.strip())
5511
5511
5512 # all ancestors of branch heads - all ancestors of parent = new csets
5512 # all ancestors of branch heads - all ancestors of parent = new csets
5513 new = [0] * len(repo)
5513 new = [0] * len(repo)
5514 cl = repo.changelog
5514 cl = repo.changelog
5515 for a in [cl.rev(n) for n in bheads]:
5515 for a in [cl.rev(n) for n in bheads]:
5516 new[a] = 1
5516 new[a] = 1
5517 for a in cl.ancestors([cl.rev(n) for n in bheads]):
5517 for a in cl.ancestors([cl.rev(n) for n in bheads]):
5518 new[a] = 1
5518 new[a] = 1
5519 for a in [p.rev() for p in parents]:
5519 for a in [p.rev() for p in parents]:
5520 if a >= 0:
5520 if a >= 0:
5521 new[a] = 0
5521 new[a] = 0
5522 for a in cl.ancestors([p.rev() for p in parents]):
5522 for a in cl.ancestors([p.rev() for p in parents]):
5523 new[a] = 0
5523 new[a] = 0
5524 new = sum(new)
5524 new = sum(new)
5525
5525
5526 if new == 0:
5526 if new == 0:
5527 ui.status(_('update: (current)\n'))
5527 ui.status(_('update: (current)\n'))
5528 elif pnode not in bheads:
5528 elif pnode not in bheads:
5529 ui.write(_('update: %d new changesets (update)\n') % new)
5529 ui.write(_('update: %d new changesets (update)\n') % new)
5530 else:
5530 else:
5531 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5531 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5532 (new, len(bheads)))
5532 (new, len(bheads)))
5533
5533
5534 if opts.get('remote'):
5534 if opts.get('remote'):
5535 t = []
5535 t = []
5536 source, branches = hg.parseurl(ui.expandpath('default'))
5536 source, branches = hg.parseurl(ui.expandpath('default'))
5537 other = hg.peer(repo, {}, source)
5537 other = hg.peer(repo, {}, source)
5538 revs, checkout = hg.addbranchrevs(repo, other, branches,
5538 revs, checkout = hg.addbranchrevs(repo, other, branches,
5539 opts.get('rev'))
5539 opts.get('rev'))
5540 ui.debug('comparing with %s\n' % util.hidepassword(source))
5540 ui.debug('comparing with %s\n' % util.hidepassword(source))
5541 repo.ui.pushbuffer()
5541 repo.ui.pushbuffer()
5542 commoninc = discovery.findcommonincoming(repo, other)
5542 commoninc = discovery.findcommonincoming(repo, other)
5543 _common, incoming, _rheads = commoninc
5543 _common, incoming, _rheads = commoninc
5544 repo.ui.popbuffer()
5544 repo.ui.popbuffer()
5545 if incoming:
5545 if incoming:
5546 t.append(_('1 or more incoming'))
5546 t.append(_('1 or more incoming'))
5547
5547
5548 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5548 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5549 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5549 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5550 if source != dest:
5550 if source != dest:
5551 other = hg.peer(repo, {}, dest)
5551 other = hg.peer(repo, {}, dest)
5552 commoninc = None
5552 commoninc = None
5553 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5553 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5554 repo.ui.pushbuffer()
5554 repo.ui.pushbuffer()
5555 outgoing = discovery.findcommonoutgoing(repo, other,
5555 outgoing = discovery.findcommonoutgoing(repo, other,
5556 commoninc=commoninc)
5556 commoninc=commoninc)
5557 repo.ui.popbuffer()
5557 repo.ui.popbuffer()
5558 o = outgoing.missing
5558 o = outgoing.missing
5559 if o:
5559 if o:
5560 t.append(_('%d outgoing') % len(o))
5560 t.append(_('%d outgoing') % len(o))
5561 if 'bookmarks' in other.listkeys('namespaces'):
5561 if 'bookmarks' in other.listkeys('namespaces'):
5562 lmarks = repo.listkeys('bookmarks')
5562 lmarks = repo.listkeys('bookmarks')
5563 rmarks = other.listkeys('bookmarks')
5563 rmarks = other.listkeys('bookmarks')
5564 diff = set(rmarks) - set(lmarks)
5564 diff = set(rmarks) - set(lmarks)
5565 if len(diff) > 0:
5565 if len(diff) > 0:
5566 t.append(_('%d incoming bookmarks') % len(diff))
5566 t.append(_('%d incoming bookmarks') % len(diff))
5567 diff = set(lmarks) - set(rmarks)
5567 diff = set(lmarks) - set(rmarks)
5568 if len(diff) > 0:
5568 if len(diff) > 0:
5569 t.append(_('%d outgoing bookmarks') % len(diff))
5569 t.append(_('%d outgoing bookmarks') % len(diff))
5570
5570
5571 if t:
5571 if t:
5572 ui.write(_('remote: %s\n') % (', '.join(t)))
5572 ui.write(_('remote: %s\n') % (', '.join(t)))
5573 else:
5573 else:
5574 ui.status(_('remote: (synced)\n'))
5574 ui.status(_('remote: (synced)\n'))
5575
5575
5576 @command('tag',
5576 @command('tag',
5577 [('f', 'force', None, _('force tag')),
5577 [('f', 'force', None, _('force tag')),
5578 ('l', 'local', None, _('make the tag local')),
5578 ('l', 'local', None, _('make the tag local')),
5579 ('r', 'rev', '', _('revision to tag'), _('REV')),
5579 ('r', 'rev', '', _('revision to tag'), _('REV')),
5580 ('', 'remove', None, _('remove a tag')),
5580 ('', 'remove', None, _('remove a tag')),
5581 # -l/--local is already there, commitopts cannot be used
5581 # -l/--local is already there, commitopts cannot be used
5582 ('e', 'edit', None, _('edit commit message')),
5582 ('e', 'edit', None, _('edit commit message')),
5583 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5583 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5584 ] + commitopts2,
5584 ] + commitopts2,
5585 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5585 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5586 def tag(ui, repo, name1, *names, **opts):
5586 def tag(ui, repo, name1, *names, **opts):
5587 """add one or more tags for the current or given revision
5587 """add one or more tags for the current or given revision
5588
5588
5589 Name a particular revision using <name>.
5589 Name a particular revision using <name>.
5590
5590
5591 Tags are used to name particular revisions of the repository and are
5591 Tags are used to name particular revisions of the repository and are
5592 very useful to compare different revisions, to go back to significant
5592 very useful to compare different revisions, to go back to significant
5593 earlier versions or to mark branch points as releases, etc. Changing
5593 earlier versions or to mark branch points as releases, etc. Changing
5594 an existing tag is normally disallowed; use -f/--force to override.
5594 an existing tag is normally disallowed; use -f/--force to override.
5595
5595
5596 If no revision is given, the parent of the working directory is
5596 If no revision is given, the parent of the working directory is
5597 used, or tip if no revision is checked out.
5597 used, or tip if no revision is checked out.
5598
5598
5599 To facilitate version control, distribution, and merging of tags,
5599 To facilitate version control, distribution, and merging of tags,
5600 they are stored as a file named ".hgtags" which is managed similarly
5600 they are stored as a file named ".hgtags" which is managed similarly
5601 to other project files and can be hand-edited if necessary. This
5601 to other project files and can be hand-edited if necessary. This
5602 also means that tagging creates a new commit. The file
5602 also means that tagging creates a new commit. The file
5603 ".hg/localtags" is used for local tags (not shared among
5603 ".hg/localtags" is used for local tags (not shared among
5604 repositories).
5604 repositories).
5605
5605
5606 Tag commits are usually made at the head of a branch. If the parent
5606 Tag commits are usually made at the head of a branch. If the parent
5607 of the working directory is not a branch head, :hg:`tag` aborts; use
5607 of the working directory is not a branch head, :hg:`tag` aborts; use
5608 -f/--force to force the tag commit to be based on a non-head
5608 -f/--force to force the tag commit to be based on a non-head
5609 changeset.
5609 changeset.
5610
5610
5611 See :hg:`help dates` for a list of formats valid for -d/--date.
5611 See :hg:`help dates` for a list of formats valid for -d/--date.
5612
5612
5613 Since tag names have priority over branch names during revision
5613 Since tag names have priority over branch names during revision
5614 lookup, using an existing branch name as a tag name is discouraged.
5614 lookup, using an existing branch name as a tag name is discouraged.
5615
5615
5616 Returns 0 on success.
5616 Returns 0 on success.
5617 """
5617 """
5618 wlock = lock = None
5618 wlock = lock = None
5619 try:
5619 try:
5620 wlock = repo.wlock()
5620 wlock = repo.wlock()
5621 lock = repo.lock()
5621 lock = repo.lock()
5622 rev_ = "."
5622 rev_ = "."
5623 names = [t.strip() for t in (name1,) + names]
5623 names = [t.strip() for t in (name1,) + names]
5624 if len(names) != len(set(names)):
5624 if len(names) != len(set(names)):
5625 raise util.Abort(_('tag names must be unique'))
5625 raise util.Abort(_('tag names must be unique'))
5626 for n in names:
5626 for n in names:
5627 if n in ['tip', '.', 'null']:
5627 if n in ['tip', '.', 'null']:
5628 raise util.Abort(_("the name '%s' is reserved") % n)
5628 raise util.Abort(_("the name '%s' is reserved") % n)
5629 if not n:
5629 if not n:
5630 raise util.Abort(_('tag names cannot consist entirely of '
5630 raise util.Abort(_('tag names cannot consist entirely of '
5631 'whitespace'))
5631 'whitespace'))
5632 if opts.get('rev') and opts.get('remove'):
5632 if opts.get('rev') and opts.get('remove'):
5633 raise util.Abort(_("--rev and --remove are incompatible"))
5633 raise util.Abort(_("--rev and --remove are incompatible"))
5634 if opts.get('rev'):
5634 if opts.get('rev'):
5635 rev_ = opts['rev']
5635 rev_ = opts['rev']
5636 message = opts.get('message')
5636 message = opts.get('message')
5637 if opts.get('remove'):
5637 if opts.get('remove'):
5638 expectedtype = opts.get('local') and 'local' or 'global'
5638 expectedtype = opts.get('local') and 'local' or 'global'
5639 for n in names:
5639 for n in names:
5640 if not repo.tagtype(n):
5640 if not repo.tagtype(n):
5641 raise util.Abort(_("tag '%s' does not exist") % n)
5641 raise util.Abort(_("tag '%s' does not exist") % n)
5642 if repo.tagtype(n) != expectedtype:
5642 if repo.tagtype(n) != expectedtype:
5643 if expectedtype == 'global':
5643 if expectedtype == 'global':
5644 raise util.Abort(_("tag '%s' is not a global tag") % n)
5644 raise util.Abort(_("tag '%s' is not a global tag") % n)
5645 else:
5645 else:
5646 raise util.Abort(_("tag '%s' is not a local tag") % n)
5646 raise util.Abort(_("tag '%s' is not a local tag") % n)
5647 rev_ = nullid
5647 rev_ = nullid
5648 if not message:
5648 if not message:
5649 # we don't translate commit messages
5649 # we don't translate commit messages
5650 message = 'Removed tag %s' % ', '.join(names)
5650 message = 'Removed tag %s' % ', '.join(names)
5651 elif not opts.get('force'):
5651 elif not opts.get('force'):
5652 for n in names:
5652 for n in names:
5653 if n in repo.tags():
5653 if n in repo.tags():
5654 raise util.Abort(_("tag '%s' already exists "
5654 raise util.Abort(_("tag '%s' already exists "
5655 "(use -f to force)") % n)
5655 "(use -f to force)") % n)
5656 if not opts.get('local'):
5656 if not opts.get('local'):
5657 p1, p2 = repo.dirstate.parents()
5657 p1, p2 = repo.dirstate.parents()
5658 if p2 != nullid:
5658 if p2 != nullid:
5659 raise util.Abort(_('uncommitted merge'))
5659 raise util.Abort(_('uncommitted merge'))
5660 bheads = repo.branchheads()
5660 bheads = repo.branchheads()
5661 if not opts.get('force') and bheads and p1 not in bheads:
5661 if not opts.get('force') and bheads and p1 not in bheads:
5662 raise util.Abort(_('not at a branch head (use -f to force)'))
5662 raise util.Abort(_('not at a branch head (use -f to force)'))
5663 r = scmutil.revsingle(repo, rev_).node()
5663 r = scmutil.revsingle(repo, rev_).node()
5664
5664
5665 if not message:
5665 if not message:
5666 # we don't translate commit messages
5666 # we don't translate commit messages
5667 message = ('Added tag %s for changeset %s' %
5667 message = ('Added tag %s for changeset %s' %
5668 (', '.join(names), short(r)))
5668 (', '.join(names), short(r)))
5669
5669
5670 date = opts.get('date')
5670 date = opts.get('date')
5671 if date:
5671 if date:
5672 date = util.parsedate(date)
5672 date = util.parsedate(date)
5673
5673
5674 if opts.get('edit'):
5674 if opts.get('edit'):
5675 message = ui.edit(message, ui.username())
5675 message = ui.edit(message, ui.username())
5676
5676
5677 # don't allow tagging the null rev
5677 # don't allow tagging the null rev
5678 if (not opts.get('remove') and
5678 if (not opts.get('remove') and
5679 scmutil.revsingle(repo, rev_).rev() == nullrev):
5679 scmutil.revsingle(repo, rev_).rev() == nullrev):
5680 raise util.Abort(_("null revision specified"))
5680 raise util.Abort(_("null revision specified"))
5681
5681
5682 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
5682 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
5683 finally:
5683 finally:
5684 release(lock, wlock)
5684 release(lock, wlock)
5685
5685
5686 @command('tags', [], '')
5686 @command('tags', [], '')
5687 def tags(ui, repo):
5687 def tags(ui, repo):
5688 """list repository tags
5688 """list repository tags
5689
5689
5690 This lists both regular and local tags. When the -v/--verbose
5690 This lists both regular and local tags. When the -v/--verbose
5691 switch is used, a third column "local" is printed for local tags.
5691 switch is used, a third column "local" is printed for local tags.
5692
5692
5693 Returns 0 on success.
5693 Returns 0 on success.
5694 """
5694 """
5695
5695
5696 hexfunc = ui.debugflag and hex or short
5696 hexfunc = ui.debugflag and hex or short
5697 tagtype = ""
5697 tagtype = ""
5698
5698
5699 for t, n in reversed(repo.tagslist()):
5699 for t, n in reversed(repo.tagslist()):
5700 if ui.quiet:
5700 if ui.quiet:
5701 ui.write("%s\n" % t, label='tags.normal')
5701 ui.write("%s\n" % t, label='tags.normal')
5702 continue
5702 continue
5703
5703
5704 hn = hexfunc(n)
5704 hn = hexfunc(n)
5705 r = "%5d:%s" % (repo.changelog.rev(n), hn)
5705 r = "%5d:%s" % (repo.changelog.rev(n), hn)
5706 rev = ui.label(r, 'log.changeset')
5706 rev = ui.label(r, 'log.changeset')
5707 spaces = " " * (30 - encoding.colwidth(t))
5707 spaces = " " * (30 - encoding.colwidth(t))
5708
5708
5709 tag = ui.label(t, 'tags.normal')
5709 tag = ui.label(t, 'tags.normal')
5710 if ui.verbose:
5710 if ui.verbose:
5711 if repo.tagtype(t) == 'local':
5711 if repo.tagtype(t) == 'local':
5712 tagtype = " local"
5712 tagtype = " local"
5713 tag = ui.label(t, 'tags.local')
5713 tag = ui.label(t, 'tags.local')
5714 else:
5714 else:
5715 tagtype = ""
5715 tagtype = ""
5716 ui.write("%s%s %s%s\n" % (tag, spaces, rev, tagtype))
5716 ui.write("%s%s %s%s\n" % (tag, spaces, rev, tagtype))
5717
5717
5718 @command('tip',
5718 @command('tip',
5719 [('p', 'patch', None, _('show patch')),
5719 [('p', 'patch', None, _('show patch')),
5720 ('g', 'git', None, _('use git extended diff format')),
5720 ('g', 'git', None, _('use git extended diff format')),
5721 ] + templateopts,
5721 ] + templateopts,
5722 _('[-p] [-g]'))
5722 _('[-p] [-g]'))
5723 def tip(ui, repo, **opts):
5723 def tip(ui, repo, **opts):
5724 """show the tip revision
5724 """show the tip revision
5725
5725
5726 The tip revision (usually just called the tip) is the changeset
5726 The tip revision (usually just called the tip) is the changeset
5727 most recently added to the repository (and therefore the most
5727 most recently added to the repository (and therefore the most
5728 recently changed head).
5728 recently changed head).
5729
5729
5730 If you have just made a commit, that commit will be the tip. If
5730 If you have just made a commit, that commit will be the tip. If
5731 you have just pulled changes from another repository, the tip of
5731 you have just pulled changes from another repository, the tip of
5732 that repository becomes the current tip. The "tip" tag is special
5732 that repository becomes the current tip. The "tip" tag is special
5733 and cannot be renamed or assigned to a different changeset.
5733 and cannot be renamed or assigned to a different changeset.
5734
5734
5735 Returns 0 on success.
5735 Returns 0 on success.
5736 """
5736 """
5737 displayer = cmdutil.show_changeset(ui, repo, opts)
5737 displayer = cmdutil.show_changeset(ui, repo, opts)
5738 displayer.show(repo[len(repo) - 1])
5738 displayer.show(repo[len(repo) - 1])
5739 displayer.close()
5739 displayer.close()
5740
5740
5741 @command('unbundle',
5741 @command('unbundle',
5742 [('u', 'update', None,
5742 [('u', 'update', None,
5743 _('update to new branch head if changesets were unbundled'))],
5743 _('update to new branch head if changesets were unbundled'))],
5744 _('[-u] FILE...'))
5744 _('[-u] FILE...'))
5745 def unbundle(ui, repo, fname1, *fnames, **opts):
5745 def unbundle(ui, repo, fname1, *fnames, **opts):
5746 """apply one or more changegroup files
5746 """apply one or more changegroup files
5747
5747
5748 Apply one or more compressed changegroup files generated by the
5748 Apply one or more compressed changegroup files generated by the
5749 bundle command.
5749 bundle command.
5750
5750
5751 Returns 0 on success, 1 if an update has unresolved files.
5751 Returns 0 on success, 1 if an update has unresolved files.
5752 """
5752 """
5753 fnames = (fname1,) + fnames
5753 fnames = (fname1,) + fnames
5754
5754
5755 lock = repo.lock()
5755 lock = repo.lock()
5756 wc = repo['.']
5756 wc = repo['.']
5757 try:
5757 try:
5758 for fname in fnames:
5758 for fname in fnames:
5759 f = url.open(ui, fname)
5759 f = url.open(ui, fname)
5760 gen = changegroup.readbundle(f, fname)
5760 gen = changegroup.readbundle(f, fname)
5761 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
5761 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
5762 finally:
5762 finally:
5763 lock.release()
5763 lock.release()
5764 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5764 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5765 return postincoming(ui, repo, modheads, opts.get('update'), None)
5765 return postincoming(ui, repo, modheads, opts.get('update'), None)
5766
5766
5767 @command('^update|up|checkout|co',
5767 @command('^update|up|checkout|co',
5768 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5768 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5769 ('c', 'check', None,
5769 ('c', 'check', None,
5770 _('update across branches if no uncommitted changes')),
5770 _('update across branches if no uncommitted changes')),
5771 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5771 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5772 ('r', 'rev', '', _('revision'), _('REV'))],
5772 ('r', 'rev', '', _('revision'), _('REV'))],
5773 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5773 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5774 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5774 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5775 """update working directory (or switch revisions)
5775 """update working directory (or switch revisions)
5776
5776
5777 Update the repository's working directory to the specified
5777 Update the repository's working directory to the specified
5778 changeset. If no changeset is specified, update to the tip of the
5778 changeset. If no changeset is specified, update to the tip of the
5779 current named branch and move the current bookmark (see :hg:`help
5779 current named branch and move the current bookmark (see :hg:`help
5780 bookmarks`).
5780 bookmarks`).
5781
5781
5782 Update sets the working directory's parent revision to the specified
5782 Update sets the working directory's parent revision to the specified
5783 changeset (see :hg:`help parents`).
5783 changeset (see :hg:`help parents`).
5784
5784
5785 If the changeset is not a descendant or ancestor of the working
5785 If the changeset is not a descendant or ancestor of the working
5786 directory's parent, the update is aborted. With the -c/--check
5786 directory's parent, the update is aborted. With the -c/--check
5787 option, the working directory is checked for uncommitted changes; if
5787 option, the working directory is checked for uncommitted changes; if
5788 none are found, the working directory is updated to the specified
5788 none are found, the working directory is updated to the specified
5789 changeset.
5789 changeset.
5790
5790
5791 .. container:: verbose
5791 .. container:: verbose
5792
5792
5793 The following rules apply when the working directory contains
5793 The following rules apply when the working directory contains
5794 uncommitted changes:
5794 uncommitted changes:
5795
5795
5796 1. If neither -c/--check nor -C/--clean is specified, and if
5796 1. If neither -c/--check nor -C/--clean is specified, and if
5797 the requested changeset is an ancestor or descendant of
5797 the requested changeset is an ancestor or descendant of
5798 the working directory's parent, the uncommitted changes
5798 the working directory's parent, the uncommitted changes
5799 are merged into the requested changeset and the merged
5799 are merged into the requested changeset and the merged
5800 result is left uncommitted. If the requested changeset is
5800 result is left uncommitted. If the requested changeset is
5801 not an ancestor or descendant (that is, it is on another
5801 not an ancestor or descendant (that is, it is on another
5802 branch), the update is aborted and the uncommitted changes
5802 branch), the update is aborted and the uncommitted changes
5803 are preserved.
5803 are preserved.
5804
5804
5805 2. With the -c/--check option, the update is aborted and the
5805 2. With the -c/--check option, the update is aborted and the
5806 uncommitted changes are preserved.
5806 uncommitted changes are preserved.
5807
5807
5808 3. With the -C/--clean option, uncommitted changes are discarded and
5808 3. With the -C/--clean option, uncommitted changes are discarded and
5809 the working directory is updated to the requested changeset.
5809 the working directory is updated to the requested changeset.
5810
5810
5811 To cancel an uncommitted merge (and lose your changes), use
5811 To cancel an uncommitted merge (and lose your changes), use
5812 :hg:`update --clean .`.
5812 :hg:`update --clean .`.
5813
5813
5814 Use null as the changeset to remove the working directory (like
5814 Use null as the changeset to remove the working directory (like
5815 :hg:`clone -U`).
5815 :hg:`clone -U`).
5816
5816
5817 If you want to revert just one file to an older revision, use
5817 If you want to revert just one file to an older revision, use
5818 :hg:`revert [-r REV] NAME`.
5818 :hg:`revert [-r REV] NAME`.
5819
5819
5820 See :hg:`help dates` for a list of formats valid for -d/--date.
5820 See :hg:`help dates` for a list of formats valid for -d/--date.
5821
5821
5822 Returns 0 on success, 1 if there are unresolved files.
5822 Returns 0 on success, 1 if there are unresolved files.
5823 """
5823 """
5824 if rev and node:
5824 if rev and node:
5825 raise util.Abort(_("please specify just one revision"))
5825 raise util.Abort(_("please specify just one revision"))
5826
5826
5827 if rev is None or rev == '':
5827 if rev is None or rev == '':
5828 rev = node
5828 rev = node
5829
5829
5830 # with no argument, we also move the current bookmark, if any
5830 # with no argument, we also move the current bookmark, if any
5831 movemarkfrom = None
5831 movemarkfrom = None
5832 if rev is None or node == '':
5832 if rev is None or node == '':
5833 movemarkfrom = repo['.'].node()
5833 movemarkfrom = repo['.'].node()
5834
5834
5835 # if we defined a bookmark, we have to remember the original bookmark name
5835 # if we defined a bookmark, we have to remember the original bookmark name
5836 brev = rev
5836 brev = rev
5837 rev = scmutil.revsingle(repo, rev, rev).rev()
5837 rev = scmutil.revsingle(repo, rev, rev).rev()
5838
5838
5839 if check and clean:
5839 if check and clean:
5840 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5840 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5841
5841
5842 if date:
5842 if date:
5843 if rev is not None:
5843 if rev is not None:
5844 raise util.Abort(_("you can't specify a revision and a date"))
5844 raise util.Abort(_("you can't specify a revision and a date"))
5845 rev = cmdutil.finddate(ui, repo, date)
5845 rev = cmdutil.finddate(ui, repo, date)
5846
5846
5847 if check:
5847 if check:
5848 c = repo[None]
5848 c = repo[None]
5849 if c.dirty(merge=False, branch=False):
5849 if c.dirty(merge=False, branch=False):
5850 raise util.Abort(_("uncommitted local changes"))
5850 raise util.Abort(_("uncommitted local changes"))
5851 if rev is None:
5851 if rev is None:
5852 rev = repo[repo[None].branch()].rev()
5852 rev = repo[repo[None].branch()].rev()
5853 mergemod._checkunknown(repo, repo[None], repo[rev])
5853 mergemod._checkunknown(repo, repo[None], repo[rev])
5854
5854
5855 if clean:
5855 if clean:
5856 ret = hg.clean(repo, rev)
5856 ret = hg.clean(repo, rev)
5857 else:
5857 else:
5858 ret = hg.update(repo, rev)
5858 ret = hg.update(repo, rev)
5859
5859
5860 if not ret and movemarkfrom:
5860 if not ret and movemarkfrom:
5861 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
5861 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
5862 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
5862 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
5863 elif brev in repo._bookmarks:
5863 elif brev in repo._bookmarks:
5864 bookmarks.setcurrent(repo, brev)
5864 bookmarks.setcurrent(repo, brev)
5865 elif brev:
5865 elif brev:
5866 bookmarks.unsetcurrent(repo)
5866 bookmarks.unsetcurrent(repo)
5867
5867
5868 return ret
5868 return ret
5869
5869
5870 @command('verify', [])
5870 @command('verify', [])
5871 def verify(ui, repo):
5871 def verify(ui, repo):
5872 """verify the integrity of the repository
5872 """verify the integrity of the repository
5873
5873
5874 Verify the integrity of the current repository.
5874 Verify the integrity of the current repository.
5875
5875
5876 This will perform an extensive check of the repository's
5876 This will perform an extensive check of the repository's
5877 integrity, validating the hashes and checksums of each entry in
5877 integrity, validating the hashes and checksums of each entry in
5878 the changelog, manifest, and tracked files, as well as the
5878 the changelog, manifest, and tracked files, as well as the
5879 integrity of their crosslinks and indices.
5879 integrity of their crosslinks and indices.
5880
5880
5881 Returns 0 on success, 1 if errors are encountered.
5881 Returns 0 on success, 1 if errors are encountered.
5882 """
5882 """
5883 return hg.verify(repo)
5883 return hg.verify(repo)
5884
5884
5885 @command('version', [])
5885 @command('version', [])
5886 def version_(ui):
5886 def version_(ui):
5887 """output version and copyright information"""
5887 """output version and copyright information"""
5888 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5888 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5889 % util.version())
5889 % util.version())
5890 ui.status(_(
5890 ui.status(_(
5891 "(see http://mercurial.selenic.com for more information)\n"
5891 "(see http://mercurial.selenic.com for more information)\n"
5892 "\nCopyright (C) 2005-2012 Matt Mackall and others\n"
5892 "\nCopyright (C) 2005-2012 Matt Mackall and others\n"
5893 "This is free software; see the source for copying conditions. "
5893 "This is free software; see the source for copying conditions. "
5894 "There is NO\nwarranty; "
5894 "There is NO\nwarranty; "
5895 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5895 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5896 ))
5896 ))
5897
5897
5898 norepo = ("clone init version help debugcommands debugcomplete"
5898 norepo = ("clone init version help debugcommands debugcomplete"
5899 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5899 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5900 " debugknown debuggetbundle debugbundle")
5900 " debugknown debuggetbundle debugbundle")
5901 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5901 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5902 " debugdata debugindex debugindexdot debugrevlog")
5902 " debugdata debugindex debugindexdot debugrevlog")
@@ -1,183 +1,183 b''
1 # config.py - configuration parsing for Mercurial
1 # config.py - configuration parsing for Mercurial
2 #
2 #
3 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import error, util
9 import error, util
10 import os, errno
10 import os, errno
11
11
12 class sortdict(dict):
12 class sortdict(dict):
13 'a simple sorted dictionary'
13 'a simple sorted dictionary'
14 def __init__(self, data=None):
14 def __init__(self, data=None):
15 self._list = []
15 self._list = []
16 if data:
16 if data:
17 self.update(data)
17 self.update(data)
18 def copy(self):
18 def copy(self):
19 return sortdict(self)
19 return sortdict(self)
20 def __setitem__(self, key, val):
20 def __setitem__(self, key, val):
21 if key in self:
21 if key in self:
22 self._list.remove(key)
22 self._list.remove(key)
23 self._list.append(key)
23 self._list.append(key)
24 dict.__setitem__(self, key, val)
24 dict.__setitem__(self, key, val)
25 def __iter__(self):
25 def __iter__(self):
26 return self._list.__iter__()
26 return self._list.__iter__()
27 def update(self, src):
27 def update(self, src):
28 for k in src:
28 for k in src:
29 self[k] = src[k]
29 self[k] = src[k]
30 def clear(self):
30 def clear(self):
31 dict.clear(self)
31 dict.clear(self)
32 self._list = []
32 self._list = []
33 def items(self):
33 def items(self):
34 return [(k, self[k]) for k in self._list]
34 return [(k, self[k]) for k in self._list]
35 def __delitem__(self, key):
35 def __delitem__(self, key):
36 dict.__delitem__(self, key)
36 dict.__delitem__(self, key)
37 self._list.remove(key)
37 self._list.remove(key)
38 def keys(self):
38 def keys(self):
39 return self._list
39 return self._list
40 def iterkeys(self):
40 def iterkeys(self):
41 return self._list.__iter__()
41 return self._list.__iter__()
42
42
43 class config(object):
43 class config(object):
44 def __init__(self, data=None):
44 def __init__(self, data=None):
45 self._data = {}
45 self._data = {}
46 self._source = {}
46 self._source = {}
47 if data:
47 if data:
48 for k in data._data:
48 for k in data._data:
49 self._data[k] = data[k].copy()
49 self._data[k] = data[k].copy()
50 self._source = data._source.copy()
50 self._source = data._source.copy()
51 def copy(self):
51 def copy(self):
52 return config(self)
52 return config(self)
53 def __contains__(self, section):
53 def __contains__(self, section):
54 return section in self._data
54 return section in self._data
55 def __getitem__(self, section):
55 def __getitem__(self, section):
56 return self._data.get(section, {})
56 return self._data.get(section, {})
57 def __iter__(self):
57 def __iter__(self):
58 for d in self.sections():
58 for d in self.sections():
59 yield d
59 yield d
60 def update(self, src):
60 def update(self, src):
61 for s in src:
61 for s in src:
62 if s not in self:
62 if s not in self:
63 self._data[s] = sortdict()
63 self._data[s] = sortdict()
64 self._data[s].update(src._data[s])
64 self._data[s].update(src._data[s])
65 self._source.update(src._source)
65 self._source.update(src._source)
66 def get(self, section, item, default=None):
66 def get(self, section, item, default=None):
67 return self._data.get(section, {}).get(item, default)
67 return self._data.get(section, {}).get(item, default)
68
68
69 def backup(self, section, item):
69 def backup(self, section, item):
70 """return a tuple allowing restore to reinstall previous values
70 """return a tuple allowing restore to reinstall a previous value
71
71
72 The main reason we need it is because it handle the "no data" case.
72 The main reason we need it is because it handles the "no data" case.
73 """
73 """
74 try:
74 try:
75 value = self._data[section][item]
75 value = self._data[section][item]
76 source = self.source(section, item)
76 source = self.source(section, item)
77 return (section, item, value, source)
77 return (section, item, value, source)
78 except KeyError:
78 except KeyError:
79 return (section, item)
79 return (section, item)
80
80
81 def source(self, section, item):
81 def source(self, section, item):
82 return self._source.get((section, item), "")
82 return self._source.get((section, item), "")
83 def sections(self):
83 def sections(self):
84 return sorted(self._data.keys())
84 return sorted(self._data.keys())
85 def items(self, section):
85 def items(self, section):
86 return self._data.get(section, {}).items()
86 return self._data.get(section, {}).items()
87 def set(self, section, item, value, source=""):
87 def set(self, section, item, value, source=""):
88 if section not in self:
88 if section not in self:
89 self._data[section] = sortdict()
89 self._data[section] = sortdict()
90 self._data[section][item] = value
90 self._data[section][item] = value
91 self._source[(section, item)] = source
91 self._source[(section, item)] = source
92
92
93 def restore(self, data):
93 def restore(self, data):
94 """restore data returned by self.backup"""
94 """restore data returned by self.backup"""
95 if len(data) == 4:
95 if len(data) == 4:
96 # restore old data
96 # restore old data
97 section, item, value, source = data
97 section, item, value, source = data
98 self._data[section][item] = value
98 self._data[section][item] = value
99 self._source[(section, item)] = source
99 self._source[(section, item)] = source
100 else:
100 else:
101 # no data before, remove everything
101 # no data before, remove everything
102 section, item = data
102 section, item = data
103 if section in self._data:
103 if section in self._data:
104 del self._data[section][item]
104 del self._data[section][item]
105 self._source.pop((section, item), None)
105 self._source.pop((section, item), None)
106
106
107 def parse(self, src, data, sections=None, remap=None, include=None):
107 def parse(self, src, data, sections=None, remap=None, include=None):
108 sectionre = util.compilere(r'\[([^\[]+)\]')
108 sectionre = util.compilere(r'\[([^\[]+)\]')
109 itemre = util.compilere(r'([^=\s][^=]*?)\s*=\s*(.*\S|)')
109 itemre = util.compilere(r'([^=\s][^=]*?)\s*=\s*(.*\S|)')
110 contre = util.compilere(r'\s+(\S|\S.*\S)\s*$')
110 contre = util.compilere(r'\s+(\S|\S.*\S)\s*$')
111 emptyre = util.compilere(r'(;|#|\s*$)')
111 emptyre = util.compilere(r'(;|#|\s*$)')
112 commentre = util.compilere(r'(;|#)')
112 commentre = util.compilere(r'(;|#)')
113 unsetre = util.compilere(r'%unset\s+(\S+)')
113 unsetre = util.compilere(r'%unset\s+(\S+)')
114 includere = util.compilere(r'%include\s+(\S|\S.*\S)\s*$')
114 includere = util.compilere(r'%include\s+(\S|\S.*\S)\s*$')
115 section = ""
115 section = ""
116 item = None
116 item = None
117 line = 0
117 line = 0
118 cont = False
118 cont = False
119
119
120 for l in data.splitlines(True):
120 for l in data.splitlines(True):
121 line += 1
121 line += 1
122 if line == 1 and l.startswith('\xef\xbb\xbf'):
122 if line == 1 and l.startswith('\xef\xbb\xbf'):
123 # Someone set us up the BOM
123 # Someone set us up the BOM
124 l = l[3:]
124 l = l[3:]
125 if cont:
125 if cont:
126 if commentre.match(l):
126 if commentre.match(l):
127 continue
127 continue
128 m = contre.match(l)
128 m = contre.match(l)
129 if m:
129 if m:
130 if sections and section not in sections:
130 if sections and section not in sections:
131 continue
131 continue
132 v = self.get(section, item) + "\n" + m.group(1)
132 v = self.get(section, item) + "\n" + m.group(1)
133 self.set(section, item, v, "%s:%d" % (src, line))
133 self.set(section, item, v, "%s:%d" % (src, line))
134 continue
134 continue
135 item = None
135 item = None
136 cont = False
136 cont = False
137 m = includere.match(l)
137 m = includere.match(l)
138 if m:
138 if m:
139 inc = util.expandpath(m.group(1))
139 inc = util.expandpath(m.group(1))
140 base = os.path.dirname(src)
140 base = os.path.dirname(src)
141 inc = os.path.normpath(os.path.join(base, inc))
141 inc = os.path.normpath(os.path.join(base, inc))
142 if include:
142 if include:
143 try:
143 try:
144 include(inc, remap=remap, sections=sections)
144 include(inc, remap=remap, sections=sections)
145 except IOError, inst:
145 except IOError, inst:
146 if inst.errno != errno.ENOENT:
146 if inst.errno != errno.ENOENT:
147 raise error.ParseError(_("cannot include %s (%s)")
147 raise error.ParseError(_("cannot include %s (%s)")
148 % (inc, inst.strerror),
148 % (inc, inst.strerror),
149 "%s:%s" % (src, line))
149 "%s:%s" % (src, line))
150 continue
150 continue
151 if emptyre.match(l):
151 if emptyre.match(l):
152 continue
152 continue
153 m = sectionre.match(l)
153 m = sectionre.match(l)
154 if m:
154 if m:
155 section = m.group(1)
155 section = m.group(1)
156 if remap:
156 if remap:
157 section = remap.get(section, section)
157 section = remap.get(section, section)
158 if section not in self:
158 if section not in self:
159 self._data[section] = sortdict()
159 self._data[section] = sortdict()
160 continue
160 continue
161 m = itemre.match(l)
161 m = itemre.match(l)
162 if m:
162 if m:
163 item = m.group(1)
163 item = m.group(1)
164 cont = True
164 cont = True
165 if sections and section not in sections:
165 if sections and section not in sections:
166 continue
166 continue
167 self.set(section, item, m.group(2), "%s:%d" % (src, line))
167 self.set(section, item, m.group(2), "%s:%d" % (src, line))
168 continue
168 continue
169 m = unsetre.match(l)
169 m = unsetre.match(l)
170 if m:
170 if m:
171 name = m.group(1)
171 name = m.group(1)
172 if sections and section not in sections:
172 if sections and section not in sections:
173 continue
173 continue
174 if self.get(section, name) is not None:
174 if self.get(section, name) is not None:
175 del self._data[section][name]
175 del self._data[section][name]
176 continue
176 continue
177
177
178 raise error.ParseError(l.rstrip(), ("%s:%s" % (src, line)))
178 raise error.ParseError(l.rstrip(), ("%s:%s" % (src, line)))
179
179
180 def read(self, path, fp=None, sections=None, remap=None):
180 def read(self, path, fp=None, sections=None, remap=None):
181 if not fp:
181 if not fp:
182 fp = util.posixfile(path)
182 fp = util.posixfile(path)
183 self.parse(path, fp.read(), sections, remap, self.read)
183 self.parse(path, fp.read(), sections, remap, self.read)
@@ -1,479 +1,479 b''
1 # dagparser.py - parser and generator for concise description of DAGs
1 # dagparser.py - parser and generator for concise description of DAGs
2 #
2 #
3 # Copyright 2010 Peter Arrenbrecht <peter@arrenbrecht.ch>
3 # Copyright 2010 Peter Arrenbrecht <peter@arrenbrecht.ch>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import re, string
8 import re, string
9 import util
9 import util
10 from i18n import _
10 from i18n import _
11
11
12 def parsedag(desc):
12 def parsedag(desc):
13 '''parses a DAG from a concise textual description; generates events
13 '''parses a DAG from a concise textual description; generates events
14
14
15 "+n" is a linear run of n nodes based on the current default parent
15 "+n" is a linear run of n nodes based on the current default parent
16 "." is a single node based on the current default parent
16 "." is a single node based on the current default parent
17 "$" resets the default parent to -1 (implied at the start);
17 "$" resets the default parent to -1 (implied at the start);
18 otherwise the default parent is always the last node created
18 otherwise the default parent is always the last node created
19 "<p" sets the default parent to the backref p
19 "<p" sets the default parent to the backref p
20 "*p" is a fork at parent p, where p is a backref
20 "*p" is a fork at parent p, where p is a backref
21 "*p1/p2/.../pn" is a merge of parents p1..pn, where the pi are backrefs
21 "*p1/p2/.../pn" is a merge of parents p1..pn, where the pi are backrefs
22 "/p2/.../pn" is a merge of the preceding node and p2..pn
22 "/p2/.../pn" is a merge of the preceding node and p2..pn
23 ":name" defines a label for the preceding node; labels can be redefined
23 ":name" defines a label for the preceding node; labels can be redefined
24 "@text" emits an annotation event for text
24 "@text" emits an annotation event for text
25 "!command" emits an action event for the current node
25 "!command" emits an action event for the current node
26 "!!my command\n" is like "!", but to the end of the line
26 "!!my command\n" is like "!", but to the end of the line
27 "#...\n" is a comment up to the end of the line
27 "#...\n" is a comment up to the end of the line
28
28
29 Whitespace between the above elements is ignored.
29 Whitespace between the above elements is ignored.
30
30
31 A backref is either
31 A backref is either
32 * a number n, which references the node curr-n, where curr is the current
32 * a number n, which references the node curr-n, where curr is the current
33 node, or
33 node, or
34 * the name of a label you placed earlier using ":name", or
34 * the name of a label you placed earlier using ":name", or
35 * empty to denote the default parent.
35 * empty to denote the default parent.
36
36
37 All string valued-elements are either strictly alphanumeric, or must
37 All string valued-elements are either strictly alphanumeric, or must
38 be enclosed in double quotes ("..."), with "\" as escape character.
38 be enclosed in double quotes ("..."), with "\" as escape character.
39
39
40 Generates sequence of
40 Generates sequence of
41
41
42 ('n', (id, [parentids])) for node creation
42 ('n', (id, [parentids])) for node creation
43 ('l', (id, labelname)) for labels on nodes
43 ('l', (id, labelname)) for labels on nodes
44 ('a', text) for annotations
44 ('a', text) for annotations
45 ('c', command) for actions (!)
45 ('c', command) for actions (!)
46 ('C', command) for line actions (!!)
46 ('C', command) for line actions (!!)
47
47
48 Examples
48 Examples
49 --------
49 --------
50
50
51 Example of a complex graph (output not shown for brevity):
51 Example of a complex graph (output not shown for brevity):
52
52
53 >>> len(list(parsedag("""
53 >>> len(list(parsedag("""
54 ...
54 ...
55 ... +3 # 3 nodes in linear run
55 ... +3 # 3 nodes in linear run
56 ... :forkhere # a label for the last of the 3 nodes from above
56 ... :forkhere # a label for the last of the 3 nodes from above
57 ... +5 # 5 more nodes on one branch
57 ... +5 # 5 more nodes on one branch
58 ... :mergethis # label again
58 ... :mergethis # label again
59 ... <forkhere # set default parent to labelled fork node
59 ... <forkhere # set default parent to labeled fork node
60 ... +10 # 10 more nodes on a parallel branch
60 ... +10 # 10 more nodes on a parallel branch
61 ... @stable # following nodes will be annotated as "stable"
61 ... @stable # following nodes will be annotated as "stable"
62 ... +5 # 5 nodes in stable
62 ... +5 # 5 nodes in stable
63 ... !addfile # custom command; could trigger new file in next node
63 ... !addfile # custom command; could trigger new file in next node
64 ... +2 # two more nodes
64 ... +2 # two more nodes
65 ... /mergethis # merge last node with labelled node
65 ... /mergethis # merge last node with labeled node
66 ... +4 # 4 more nodes descending from merge node
66 ... +4 # 4 more nodes descending from merge node
67 ...
67 ...
68 ... """)))
68 ... """)))
69 34
69 34
70
70
71 Empty list:
71 Empty list:
72
72
73 >>> list(parsedag(""))
73 >>> list(parsedag(""))
74 []
74 []
75
75
76 A simple linear run:
76 A simple linear run:
77
77
78 >>> list(parsedag("+3"))
78 >>> list(parsedag("+3"))
79 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
79 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
80
80
81 Some non-standard ways to define such runs:
81 Some non-standard ways to define such runs:
82
82
83 >>> list(parsedag("+1+2"))
83 >>> list(parsedag("+1+2"))
84 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
84 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
85
85
86 >>> list(parsedag("+1*1*"))
86 >>> list(parsedag("+1*1*"))
87 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
87 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
88
88
89 >>> list(parsedag("*"))
89 >>> list(parsedag("*"))
90 [('n', (0, [-1]))]
90 [('n', (0, [-1]))]
91
91
92 >>> list(parsedag("..."))
92 >>> list(parsedag("..."))
93 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
93 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
94
94
95 A fork and a join, using numeric back references:
95 A fork and a join, using numeric back references:
96
96
97 >>> list(parsedag("+2*2*/2"))
97 >>> list(parsedag("+2*2*/2"))
98 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])), ('n', (3, [2, 1]))]
98 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])), ('n', (3, [2, 1]))]
99
99
100 >>> list(parsedag("+2<2+1/2"))
100 >>> list(parsedag("+2<2+1/2"))
101 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])), ('n', (3, [2, 1]))]
101 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])), ('n', (3, [2, 1]))]
102
102
103 Placing a label:
103 Placing a label:
104
104
105 >>> list(parsedag("+1 :mylabel +1"))
105 >>> list(parsedag("+1 :mylabel +1"))
106 [('n', (0, [-1])), ('l', (0, 'mylabel')), ('n', (1, [0]))]
106 [('n', (0, [-1])), ('l', (0, 'mylabel')), ('n', (1, [0]))]
107
107
108 An empty label (silly, really):
108 An empty label (silly, really):
109
109
110 >>> list(parsedag("+1:+1"))
110 >>> list(parsedag("+1:+1"))
111 [('n', (0, [-1])), ('l', (0, '')), ('n', (1, [0]))]
111 [('n', (0, [-1])), ('l', (0, '')), ('n', (1, [0]))]
112
112
113 Fork and join, but with labels instead of numeric back references:
113 Fork and join, but with labels instead of numeric back references:
114
114
115 >>> list(parsedag("+1:f +1:p2 *f */p2"))
115 >>> list(parsedag("+1:f +1:p2 *f */p2"))
116 [('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])), ('l', (1, 'p2')),
116 [('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])), ('l', (1, 'p2')),
117 ('n', (2, [0])), ('n', (3, [2, 1]))]
117 ('n', (2, [0])), ('n', (3, [2, 1]))]
118
118
119 >>> list(parsedag("+1:f +1:p2 <f +1 /p2"))
119 >>> list(parsedag("+1:f +1:p2 <f +1 /p2"))
120 [('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])), ('l', (1, 'p2')),
120 [('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])), ('l', (1, 'p2')),
121 ('n', (2, [0])), ('n', (3, [2, 1]))]
121 ('n', (2, [0])), ('n', (3, [2, 1]))]
122
122
123 Restarting from the root:
123 Restarting from the root:
124
124
125 >>> list(parsedag("+1 $ +1"))
125 >>> list(parsedag("+1 $ +1"))
126 [('n', (0, [-1])), ('n', (1, [-1]))]
126 [('n', (0, [-1])), ('n', (1, [-1]))]
127
127
128 Annotations, which are meant to introduce sticky state for subsequent nodes:
128 Annotations, which are meant to introduce sticky state for subsequent nodes:
129
129
130 >>> list(parsedag("+1 @ann +1"))
130 >>> list(parsedag("+1 @ann +1"))
131 [('n', (0, [-1])), ('a', 'ann'), ('n', (1, [0]))]
131 [('n', (0, [-1])), ('a', 'ann'), ('n', (1, [0]))]
132
132
133 >>> list(parsedag('+1 @"my annotation" +1'))
133 >>> list(parsedag('+1 @"my annotation" +1'))
134 [('n', (0, [-1])), ('a', 'my annotation'), ('n', (1, [0]))]
134 [('n', (0, [-1])), ('a', 'my annotation'), ('n', (1, [0]))]
135
135
136 Commands, which are meant to operate on the most recently created node:
136 Commands, which are meant to operate on the most recently created node:
137
137
138 >>> list(parsedag("+1 !cmd +1"))
138 >>> list(parsedag("+1 !cmd +1"))
139 [('n', (0, [-1])), ('c', 'cmd'), ('n', (1, [0]))]
139 [('n', (0, [-1])), ('c', 'cmd'), ('n', (1, [0]))]
140
140
141 >>> list(parsedag('+1 !"my command" +1'))
141 >>> list(parsedag('+1 !"my command" +1'))
142 [('n', (0, [-1])), ('c', 'my command'), ('n', (1, [0]))]
142 [('n', (0, [-1])), ('c', 'my command'), ('n', (1, [0]))]
143
143
144 >>> list(parsedag('+1 !!my command line\\n +1'))
144 >>> list(parsedag('+1 !!my command line\\n +1'))
145 [('n', (0, [-1])), ('C', 'my command line'), ('n', (1, [0]))]
145 [('n', (0, [-1])), ('C', 'my command line'), ('n', (1, [0]))]
146
146
147 Comments, which extend to the end of the line:
147 Comments, which extend to the end of the line:
148
148
149 >>> list(parsedag('+1 # comment\\n+1'))
149 >>> list(parsedag('+1 # comment\\n+1'))
150 [('n', (0, [-1])), ('n', (1, [0]))]
150 [('n', (0, [-1])), ('n', (1, [0]))]
151
151
152 Error:
152 Error:
153
153
154 >>> try: list(parsedag('+1 bad'))
154 >>> try: list(parsedag('+1 bad'))
155 ... except Exception, e: print e
155 ... except Exception, e: print e
156 invalid character in dag description: bad...
156 invalid character in dag description: bad...
157
157
158 '''
158 '''
159 if not desc:
159 if not desc:
160 return
160 return
161
161
162 wordchars = string.ascii_letters + string.digits
162 wordchars = string.ascii_letters + string.digits
163
163
164 labels = {}
164 labels = {}
165 p1 = -1
165 p1 = -1
166 r = 0
166 r = 0
167
167
168 def resolve(ref):
168 def resolve(ref):
169 if not ref:
169 if not ref:
170 return p1
170 return p1
171 elif ref[0] in string.digits:
171 elif ref[0] in string.digits:
172 return r - int(ref)
172 return r - int(ref)
173 else:
173 else:
174 return labels[ref]
174 return labels[ref]
175
175
176 chiter = (c for c in desc)
176 chiter = (c for c in desc)
177
177
178 def nextch():
178 def nextch():
179 try:
179 try:
180 return chiter.next()
180 return chiter.next()
181 except StopIteration:
181 except StopIteration:
182 return '\0'
182 return '\0'
183
183
184 def nextrun(c, allow):
184 def nextrun(c, allow):
185 s = ''
185 s = ''
186 while c in allow:
186 while c in allow:
187 s += c
187 s += c
188 c = nextch()
188 c = nextch()
189 return c, s
189 return c, s
190
190
191 def nextdelimited(c, limit, escape):
191 def nextdelimited(c, limit, escape):
192 s = ''
192 s = ''
193 while c != limit:
193 while c != limit:
194 if c == escape:
194 if c == escape:
195 c = nextch()
195 c = nextch()
196 s += c
196 s += c
197 c = nextch()
197 c = nextch()
198 return nextch(), s
198 return nextch(), s
199
199
200 def nextstring(c):
200 def nextstring(c):
201 if c == '"':
201 if c == '"':
202 return nextdelimited(nextch(), '"', '\\')
202 return nextdelimited(nextch(), '"', '\\')
203 else:
203 else:
204 return nextrun(c, wordchars)
204 return nextrun(c, wordchars)
205
205
206 c = nextch()
206 c = nextch()
207 while c != '\0':
207 while c != '\0':
208 while c in string.whitespace:
208 while c in string.whitespace:
209 c = nextch()
209 c = nextch()
210 if c == '.':
210 if c == '.':
211 yield 'n', (r, [p1])
211 yield 'n', (r, [p1])
212 p1 = r
212 p1 = r
213 r += 1
213 r += 1
214 c = nextch()
214 c = nextch()
215 elif c == '+':
215 elif c == '+':
216 c, digs = nextrun(nextch(), string.digits)
216 c, digs = nextrun(nextch(), string.digits)
217 n = int(digs)
217 n = int(digs)
218 for i in xrange(0, n):
218 for i in xrange(0, n):
219 yield 'n', (r, [p1])
219 yield 'n', (r, [p1])
220 p1 = r
220 p1 = r
221 r += 1
221 r += 1
222 elif c in '*/':
222 elif c in '*/':
223 if c == '*':
223 if c == '*':
224 c = nextch()
224 c = nextch()
225 c, pref = nextstring(c)
225 c, pref = nextstring(c)
226 prefs = [pref]
226 prefs = [pref]
227 while c == '/':
227 while c == '/':
228 c, pref = nextstring(nextch())
228 c, pref = nextstring(nextch())
229 prefs.append(pref)
229 prefs.append(pref)
230 ps = [resolve(ref) for ref in prefs]
230 ps = [resolve(ref) for ref in prefs]
231 yield 'n', (r, ps)
231 yield 'n', (r, ps)
232 p1 = r
232 p1 = r
233 r += 1
233 r += 1
234 elif c == '<':
234 elif c == '<':
235 c, ref = nextstring(nextch())
235 c, ref = nextstring(nextch())
236 p1 = resolve(ref)
236 p1 = resolve(ref)
237 elif c == ':':
237 elif c == ':':
238 c, name = nextstring(nextch())
238 c, name = nextstring(nextch())
239 labels[name] = p1
239 labels[name] = p1
240 yield 'l', (p1, name)
240 yield 'l', (p1, name)
241 elif c == '@':
241 elif c == '@':
242 c, text = nextstring(nextch())
242 c, text = nextstring(nextch())
243 yield 'a', text
243 yield 'a', text
244 elif c == '!':
244 elif c == '!':
245 c = nextch()
245 c = nextch()
246 if c == '!':
246 if c == '!':
247 cmd = ''
247 cmd = ''
248 c = nextch()
248 c = nextch()
249 while c not in '\n\r\0':
249 while c not in '\n\r\0':
250 cmd += c
250 cmd += c
251 c = nextch()
251 c = nextch()
252 yield 'C', cmd
252 yield 'C', cmd
253 else:
253 else:
254 c, cmd = nextstring(c)
254 c, cmd = nextstring(c)
255 yield 'c', cmd
255 yield 'c', cmd
256 elif c == '#':
256 elif c == '#':
257 while c not in '\n\r\0':
257 while c not in '\n\r\0':
258 c = nextch()
258 c = nextch()
259 elif c == '$':
259 elif c == '$':
260 p1 = -1
260 p1 = -1
261 c = nextch()
261 c = nextch()
262 elif c == '\0':
262 elif c == '\0':
263 return # in case it was preceded by whitespace
263 return # in case it was preceded by whitespace
264 else:
264 else:
265 s = ''
265 s = ''
266 i = 0
266 i = 0
267 while c != '\0' and i < 10:
267 while c != '\0' and i < 10:
268 s += c
268 s += c
269 i += 1
269 i += 1
270 c = nextch()
270 c = nextch()
271 raise util.Abort(_('invalid character in dag description: '
271 raise util.Abort(_('invalid character in dag description: '
272 '%s...') % s)
272 '%s...') % s)
273
273
274 def dagtextlines(events,
274 def dagtextlines(events,
275 addspaces=True,
275 addspaces=True,
276 wraplabels=False,
276 wraplabels=False,
277 wrapannotations=False,
277 wrapannotations=False,
278 wrapcommands=False,
278 wrapcommands=False,
279 wrapnonlinear=False,
279 wrapnonlinear=False,
280 usedots=False,
280 usedots=False,
281 maxlinewidth=70):
281 maxlinewidth=70):
282 '''generates single lines for dagtext()'''
282 '''generates single lines for dagtext()'''
283
283
284 def wrapstring(text):
284 def wrapstring(text):
285 if re.match("^[0-9a-z]*$", text):
285 if re.match("^[0-9a-z]*$", text):
286 return text
286 return text
287 return '"' + text.replace('\\', '\\\\').replace('"', '\"') + '"'
287 return '"' + text.replace('\\', '\\\\').replace('"', '\"') + '"'
288
288
289 def gen():
289 def gen():
290 labels = {}
290 labels = {}
291 run = 0
291 run = 0
292 wantr = 0
292 wantr = 0
293 needroot = False
293 needroot = False
294 for kind, data in events:
294 for kind, data in events:
295 if kind == 'n':
295 if kind == 'n':
296 r, ps = data
296 r, ps = data
297
297
298 # sanity check
298 # sanity check
299 if r != wantr:
299 if r != wantr:
300 raise util.Abort(_("expected id %i, got %i") % (wantr, r))
300 raise util.Abort(_("expected id %i, got %i") % (wantr, r))
301 if not ps:
301 if not ps:
302 ps = [-1]
302 ps = [-1]
303 else:
303 else:
304 for p in ps:
304 for p in ps:
305 if p >= r:
305 if p >= r:
306 raise util.Abort(_("parent id %i is larger than "
306 raise util.Abort(_("parent id %i is larger than "
307 "current id %i") % (p, r))
307 "current id %i") % (p, r))
308 wantr += 1
308 wantr += 1
309
309
310 # new root?
310 # new root?
311 p1 = r - 1
311 p1 = r - 1
312 if len(ps) == 1 and ps[0] == -1:
312 if len(ps) == 1 and ps[0] == -1:
313 if needroot:
313 if needroot:
314 if run:
314 if run:
315 yield '+' + str(run)
315 yield '+' + str(run)
316 run = 0
316 run = 0
317 if wrapnonlinear:
317 if wrapnonlinear:
318 yield '\n'
318 yield '\n'
319 yield '$'
319 yield '$'
320 p1 = -1
320 p1 = -1
321 else:
321 else:
322 needroot = True
322 needroot = True
323 if len(ps) == 1 and ps[0] == p1:
323 if len(ps) == 1 and ps[0] == p1:
324 if usedots:
324 if usedots:
325 yield "."
325 yield "."
326 else:
326 else:
327 run += 1
327 run += 1
328 else:
328 else:
329 if run:
329 if run:
330 yield '+' + str(run)
330 yield '+' + str(run)
331 run = 0
331 run = 0
332 if wrapnonlinear:
332 if wrapnonlinear:
333 yield '\n'
333 yield '\n'
334 prefs = []
334 prefs = []
335 for p in ps:
335 for p in ps:
336 if p == p1:
336 if p == p1:
337 prefs.append('')
337 prefs.append('')
338 elif p in labels:
338 elif p in labels:
339 prefs.append(labels[p])
339 prefs.append(labels[p])
340 else:
340 else:
341 prefs.append(str(r - p))
341 prefs.append(str(r - p))
342 yield '*' + '/'.join(prefs)
342 yield '*' + '/'.join(prefs)
343 else:
343 else:
344 if run:
344 if run:
345 yield '+' + str(run)
345 yield '+' + str(run)
346 run = 0
346 run = 0
347 if kind == 'l':
347 if kind == 'l':
348 rid, name = data
348 rid, name = data
349 labels[rid] = name
349 labels[rid] = name
350 yield ':' + name
350 yield ':' + name
351 if wraplabels:
351 if wraplabels:
352 yield '\n'
352 yield '\n'
353 elif kind == 'c':
353 elif kind == 'c':
354 yield '!' + wrapstring(data)
354 yield '!' + wrapstring(data)
355 if wrapcommands:
355 if wrapcommands:
356 yield '\n'
356 yield '\n'
357 elif kind == 'C':
357 elif kind == 'C':
358 yield '!!' + data
358 yield '!!' + data
359 yield '\n'
359 yield '\n'
360 elif kind == 'a':
360 elif kind == 'a':
361 if wrapannotations:
361 if wrapannotations:
362 yield '\n'
362 yield '\n'
363 yield '@' + wrapstring(data)
363 yield '@' + wrapstring(data)
364 elif kind == '#':
364 elif kind == '#':
365 yield '#' + data
365 yield '#' + data
366 yield '\n'
366 yield '\n'
367 else:
367 else:
368 raise util.Abort(_("invalid event type in dag: %s")
368 raise util.Abort(_("invalid event type in dag: %s")
369 % str((type, data)))
369 % str((type, data)))
370 if run:
370 if run:
371 yield '+' + str(run)
371 yield '+' + str(run)
372
372
373 line = ''
373 line = ''
374 for part in gen():
374 for part in gen():
375 if part == '\n':
375 if part == '\n':
376 if line:
376 if line:
377 yield line
377 yield line
378 line = ''
378 line = ''
379 else:
379 else:
380 if len(line) + len(part) >= maxlinewidth:
380 if len(line) + len(part) >= maxlinewidth:
381 yield line
381 yield line
382 line = ''
382 line = ''
383 elif addspaces and line and part != '.':
383 elif addspaces and line and part != '.':
384 line += ' '
384 line += ' '
385 line += part
385 line += part
386 if line:
386 if line:
387 yield line
387 yield line
388
388
389 def dagtext(dag,
389 def dagtext(dag,
390 addspaces=True,
390 addspaces=True,
391 wraplabels=False,
391 wraplabels=False,
392 wrapannotations=False,
392 wrapannotations=False,
393 wrapcommands=False,
393 wrapcommands=False,
394 wrapnonlinear=False,
394 wrapnonlinear=False,
395 usedots=False,
395 usedots=False,
396 maxlinewidth=70):
396 maxlinewidth=70):
397 '''generates lines of a textual representation for a dag event stream
397 '''generates lines of a textual representation for a dag event stream
398
398
399 events should generate what parsedag() does, so:
399 events should generate what parsedag() does, so:
400
400
401 ('n', (id, [parentids])) for node creation
401 ('n', (id, [parentids])) for node creation
402 ('l', (id, labelname)) for labels on nodes
402 ('l', (id, labelname)) for labels on nodes
403 ('a', text) for annotations
403 ('a', text) for annotations
404 ('c', text) for commands
404 ('c', text) for commands
405 ('C', text) for line commands ('!!')
405 ('C', text) for line commands ('!!')
406 ('#', text) for comment lines
406 ('#', text) for comment lines
407
407
408 Parent nodes must come before child nodes.
408 Parent nodes must come before child nodes.
409
409
410 Examples
410 Examples
411 --------
411 --------
412
412
413 Linear run:
413 Linear run:
414
414
415 >>> dagtext([('n', (0, [-1])), ('n', (1, [0]))])
415 >>> dagtext([('n', (0, [-1])), ('n', (1, [0]))])
416 '+2'
416 '+2'
417
417
418 Two roots:
418 Two roots:
419
419
420 >>> dagtext([('n', (0, [-1])), ('n', (1, [-1]))])
420 >>> dagtext([('n', (0, [-1])), ('n', (1, [-1]))])
421 '+1 $ +1'
421 '+1 $ +1'
422
422
423 Fork and join:
423 Fork and join:
424
424
425 >>> dagtext([('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])),
425 >>> dagtext([('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])),
426 ... ('n', (3, [2, 1]))])
426 ... ('n', (3, [2, 1]))])
427 '+2 *2 */2'
427 '+2 *2 */2'
428
428
429 Fork and join with labels:
429 Fork and join with labels:
430
430
431 >>> dagtext([('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])),
431 >>> dagtext([('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])),
432 ... ('l', (1, 'p2')), ('n', (2, [0])), ('n', (3, [2, 1]))])
432 ... ('l', (1, 'p2')), ('n', (2, [0])), ('n', (3, [2, 1]))])
433 '+1 :f +1 :p2 *f */p2'
433 '+1 :f +1 :p2 *f */p2'
434
434
435 Annotations:
435 Annotations:
436
436
437 >>> dagtext([('n', (0, [-1])), ('a', 'ann'), ('n', (1, [0]))])
437 >>> dagtext([('n', (0, [-1])), ('a', 'ann'), ('n', (1, [0]))])
438 '+1 @ann +1'
438 '+1 @ann +1'
439
439
440 >>> dagtext([('n', (0, [-1])),
440 >>> dagtext([('n', (0, [-1])),
441 ... ('a', 'my annotation'),
441 ... ('a', 'my annotation'),
442 ... ('n', (1, [0]))])
442 ... ('n', (1, [0]))])
443 '+1 @"my annotation" +1'
443 '+1 @"my annotation" +1'
444
444
445 Commands:
445 Commands:
446
446
447 >>> dagtext([('n', (0, [-1])), ('c', 'cmd'), ('n', (1, [0]))])
447 >>> dagtext([('n', (0, [-1])), ('c', 'cmd'), ('n', (1, [0]))])
448 '+1 !cmd +1'
448 '+1 !cmd +1'
449
449
450 >>> dagtext([('n', (0, [-1])), ('c', 'my command'), ('n', (1, [0]))])
450 >>> dagtext([('n', (0, [-1])), ('c', 'my command'), ('n', (1, [0]))])
451 '+1 !"my command" +1'
451 '+1 !"my command" +1'
452
452
453 >>> dagtext([('n', (0, [-1])),
453 >>> dagtext([('n', (0, [-1])),
454 ... ('C', 'my command line'),
454 ... ('C', 'my command line'),
455 ... ('n', (1, [0]))])
455 ... ('n', (1, [0]))])
456 '+1 !!my command line\\n+1'
456 '+1 !!my command line\\n+1'
457
457
458 Comments:
458 Comments:
459
459
460 >>> dagtext([('n', (0, [-1])), ('#', ' comment'), ('n', (1, [0]))])
460 >>> dagtext([('n', (0, [-1])), ('#', ' comment'), ('n', (1, [0]))])
461 '+1 # comment\\n+1'
461 '+1 # comment\\n+1'
462
462
463 >>> dagtext([])
463 >>> dagtext([])
464 ''
464 ''
465
465
466 Combining parsedag and dagtext:
466 Combining parsedag and dagtext:
467
467
468 >>> dagtext(parsedag('+1 :f +1 :p2 *f */p2'))
468 >>> dagtext(parsedag('+1 :f +1 :p2 *f */p2'))
469 '+1 :f +1 :p2 *f */p2'
469 '+1 :f +1 :p2 *f */p2'
470
470
471 '''
471 '''
472 return "\n".join(dagtextlines(dag,
472 return "\n".join(dagtextlines(dag,
473 addspaces,
473 addspaces,
474 wraplabels,
474 wraplabels,
475 wrapannotations,
475 wrapannotations,
476 wrapcommands,
476 wrapcommands,
477 wrapnonlinear,
477 wrapnonlinear,
478 usedots,
478 usedots,
479 maxlinewidth))
479 maxlinewidth))
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now