##// END OF EJS Templates
check-code: detect "missing _() in ui message" more exactly...
FUJIWARA Katsunori -
r29397:844f7288 default
parent child Browse files
Show More
@@ -1,630 +1,643 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # check-code - a style and portability checker for Mercurial
3 # check-code - a style and portability checker for Mercurial
4 #
4 #
5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """style and portability checker for Mercurial
10 """style and portability checker for Mercurial
11
11
12 when a rule triggers wrong, do one of the following (prefer one from top):
12 when a rule triggers wrong, do one of the following (prefer one from top):
13 * do the work-around the rule suggests
13 * do the work-around the rule suggests
14 * doublecheck that it is a false match
14 * doublecheck that it is a false match
15 * improve the rule pattern
15 * improve the rule pattern
16 * add an ignore pattern to the rule (3rd arg) which matches your good line
16 * add an ignore pattern to the rule (3rd arg) which matches your good line
17 (you can append a short comment and match this, like: #re-raises)
17 (you can append a short comment and match this, like: #re-raises)
18 * change the pattern to a warning and list the exception in test-check-code-hg
18 * change the pattern to a warning and list the exception in test-check-code-hg
19 * ONLY use no--check-code for skipping entire files from external sources
19 * ONLY use no--check-code for skipping entire files from external sources
20 """
20 """
21
21
22 from __future__ import absolute_import, print_function
22 from __future__ import absolute_import, print_function
23 import glob
23 import glob
24 import keyword
24 import keyword
25 import optparse
25 import optparse
26 import os
26 import os
27 import re
27 import re
28 import sys
28 import sys
29 if sys.version_info[0] < 3:
29 if sys.version_info[0] < 3:
30 opentext = open
30 opentext = open
31 else:
31 else:
32 def opentext(f):
32 def opentext(f):
33 return open(f, encoding='ascii')
33 return open(f, encoding='ascii')
34 try:
34 try:
35 xrange
35 xrange
36 except NameError:
36 except NameError:
37 xrange = range
37 xrange = range
38 try:
38 try:
39 import re2
39 import re2
40 except ImportError:
40 except ImportError:
41 re2 = None
41 re2 = None
42
42
43 def compilere(pat, multiline=False):
43 def compilere(pat, multiline=False):
44 if multiline:
44 if multiline:
45 pat = '(?m)' + pat
45 pat = '(?m)' + pat
46 if re2:
46 if re2:
47 try:
47 try:
48 return re2.compile(pat)
48 return re2.compile(pat)
49 except re2.error:
49 except re2.error:
50 pass
50 pass
51 return re.compile(pat)
51 return re.compile(pat)
52
52
53 def repquote(m):
53 def repquote(m):
54 # check "rules depending on implementation of repquote()" in each
54 # check "rules depending on implementation of repquote()" in each
55 # patterns (especially pypats), before changing this function
55 # patterns (especially pypats), before changing this function
56 fixedmap = {' ': ' ', '\n': '\n', '.': 'p', ':': 'q',
56 fixedmap = {' ': ' ', '\n': '\n', '.': 'p', ':': 'q',
57 '%': '%', '\\': 'b', '*': 'A', '+': 'P', '-': 'M'}
57 '%': '%', '\\': 'b', '*': 'A', '+': 'P', '-': 'M'}
58 def encodechr(i):
58 def encodechr(i):
59 if i > 255:
59 if i > 255:
60 return 'u'
60 return 'u'
61 c = chr(i)
61 c = chr(i)
62 if c in fixedmap:
62 if c in fixedmap:
63 return fixedmap[c]
63 return fixedmap[c]
64 if c.isalpha():
64 if c.isalpha():
65 return 'x'
65 return 'x'
66 if c.isdigit():
66 if c.isdigit():
67 return 'n'
67 return 'n'
68 return 'o'
68 return 'o'
69 t = m.group('text')
69 t = m.group('text')
70 tt = ''.join(encodechr(i) for i in xrange(256))
70 tt = ''.join(encodechr(i) for i in xrange(256))
71 t = t.translate(tt)
71 t = t.translate(tt)
72 return m.group('quote') + t + m.group('quote')
72 return m.group('quote') + t + m.group('quote')
73
73
74 def reppython(m):
74 def reppython(m):
75 comment = m.group('comment')
75 comment = m.group('comment')
76 if comment:
76 if comment:
77 l = len(comment.rstrip())
77 l = len(comment.rstrip())
78 return "#" * l + comment[l:]
78 return "#" * l + comment[l:]
79 return repquote(m)
79 return repquote(m)
80
80
81 def repcomment(m):
81 def repcomment(m):
82 return m.group(1) + "#" * len(m.group(2))
82 return m.group(1) + "#" * len(m.group(2))
83
83
84 def repccomment(m):
84 def repccomment(m):
85 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
85 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
86 return m.group(1) + t + "*/"
86 return m.group(1) + t + "*/"
87
87
88 def repcallspaces(m):
88 def repcallspaces(m):
89 t = re.sub(r"\n\s+", "\n", m.group(2))
89 t = re.sub(r"\n\s+", "\n", m.group(2))
90 return m.group(1) + t
90 return m.group(1) + t
91
91
92 def repinclude(m):
92 def repinclude(m):
93 return m.group(1) + "<foo>"
93 return m.group(1) + "<foo>"
94
94
95 def rephere(m):
95 def rephere(m):
96 t = re.sub(r"\S", "x", m.group(2))
96 t = re.sub(r"\S", "x", m.group(2))
97 return m.group(1) + t
97 return m.group(1) + t
98
98
99
99
100 testpats = [
100 testpats = [
101 [
101 [
102 (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"),
102 (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"),
103 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
103 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
104 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
104 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
105 (r'(?<!hg )grep.* -a', "don't use 'grep -a', use in-line python"),
105 (r'(?<!hg )grep.* -a', "don't use 'grep -a', use in-line python"),
106 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
106 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
107 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
107 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
108 (r'echo -n', "don't use 'echo -n', use printf"),
108 (r'echo -n', "don't use 'echo -n', use printf"),
109 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
109 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
110 (r'head -c', "don't use 'head -c', use 'dd'"),
110 (r'head -c', "don't use 'head -c', use 'dd'"),
111 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
111 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
112 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
112 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
113 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
113 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
114 (r'printf.*[^\\]\\([1-9]|0\d)', r"don't use 'printf \NNN', use Python"),
114 (r'printf.*[^\\]\\([1-9]|0\d)', r"don't use 'printf \NNN', use Python"),
115 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
115 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
116 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
116 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
117 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
117 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
118 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
118 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
119 "use egrep for extended grep syntax"),
119 "use egrep for extended grep syntax"),
120 (r'/bin/', "don't use explicit paths for tools"),
120 (r'/bin/', "don't use explicit paths for tools"),
121 (r'[^\n]\Z', "no trailing newline"),
121 (r'[^\n]\Z', "no trailing newline"),
122 (r'export .*=', "don't export and assign at once"),
122 (r'export .*=', "don't export and assign at once"),
123 (r'^source\b', "don't use 'source', use '.'"),
123 (r'^source\b', "don't use 'source', use '.'"),
124 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
124 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
125 (r'\bls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
125 (r'\bls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
126 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
126 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
127 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
127 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
128 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
128 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
129 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
129 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
130 (r'^alias\b.*=', "don't use alias, use a function"),
130 (r'^alias\b.*=', "don't use alias, use a function"),
131 (r'if\s*!', "don't use '!' to negate exit status"),
131 (r'if\s*!', "don't use '!' to negate exit status"),
132 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
132 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
133 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
133 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
134 (r'^( *)\t', "don't use tabs to indent"),
134 (r'^( *)\t', "don't use tabs to indent"),
135 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
135 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
136 "put a backslash-escaped newline after sed 'i' command"),
136 "put a backslash-escaped newline after sed 'i' command"),
137 (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"),
137 (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"),
138 (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"),
138 (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"),
139 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"),
139 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"),
140 (r'\butil\.Abort\b', "directly use error.Abort"),
140 (r'\butil\.Abort\b', "directly use error.Abort"),
141 (r'\|&', "don't use |&, use 2>&1"),
141 (r'\|&', "don't use |&, use 2>&1"),
142 (r'\w = +\w', "only one space after = allowed"),
142 (r'\w = +\w', "only one space after = allowed"),
143 (r'\bsed\b.*[^\\]\\n', "don't use 'sed ... \\n', use a \\ and a newline"),
143 (r'\bsed\b.*[^\\]\\n', "don't use 'sed ... \\n', use a \\ and a newline"),
144 (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'")
144 (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'")
145 ],
145 ],
146 # warnings
146 # warnings
147 [
147 [
148 (r'^function', "don't use 'function', use old style"),
148 (r'^function', "don't use 'function', use old style"),
149 (r'^diff.*-\w*N', "don't use 'diff -N'"),
149 (r'^diff.*-\w*N', "don't use 'diff -N'"),
150 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
150 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
151 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
151 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
152 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
152 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
153 ]
153 ]
154 ]
154 ]
155
155
156 testfilters = [
156 testfilters = [
157 (r"( *)(#([^\n]*\S)?)", repcomment),
157 (r"( *)(#([^\n]*\S)?)", repcomment),
158 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
158 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
159 ]
159 ]
160
160
161 winglobmsg = "use (glob) to match Windows paths too"
161 winglobmsg = "use (glob) to match Windows paths too"
162 uprefix = r"^ \$ "
162 uprefix = r"^ \$ "
163 utestpats = [
163 utestpats = [
164 [
164 [
165 (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"),
165 (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"),
166 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
166 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
167 "use regex test output patterns instead of sed"),
167 "use regex test output patterns instead of sed"),
168 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
168 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
169 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
169 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
170 (uprefix + r'.*\|\| echo.*(fail|error)',
170 (uprefix + r'.*\|\| echo.*(fail|error)',
171 "explicit exit code checks unnecessary"),
171 "explicit exit code checks unnecessary"),
172 (uprefix + r'set -e', "don't use set -e"),
172 (uprefix + r'set -e', "don't use set -e"),
173 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
173 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
174 (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite "
174 (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite "
175 "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx
175 "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx
176 '# no-msys'), # in test-pull.t which is skipped on windows
176 '# no-msys'), # in test-pull.t which is skipped on windows
177 (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
177 (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
178 (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
178 (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
179 winglobmsg),
179 winglobmsg),
180 (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg,
180 (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg,
181 '\$TESTTMP/unix-repo$'), # in test-issue1802.t which skipped on windows
181 '\$TESTTMP/unix-repo$'), # in test-issue1802.t which skipped on windows
182 (r'^ reverting (?!subrepo ).*/.*[^)]$', winglobmsg),
182 (r'^ reverting (?!subrepo ).*/.*[^)]$', winglobmsg),
183 (r'^ cloning subrepo \S+/.*[^)]$', winglobmsg),
183 (r'^ cloning subrepo \S+/.*[^)]$', winglobmsg),
184 (r'^ pushing to \$TESTTMP/.*[^)]$', winglobmsg),
184 (r'^ pushing to \$TESTTMP/.*[^)]$', winglobmsg),
185 (r'^ pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg),
185 (r'^ pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg),
186 (r'^ moving \S+/.*[^)]$', winglobmsg),
186 (r'^ moving \S+/.*[^)]$', winglobmsg),
187 (r'^ no changes made to subrepo since.*/.*[^)]$', winglobmsg),
187 (r'^ no changes made to subrepo since.*/.*[^)]$', winglobmsg),
188 (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$', winglobmsg),
188 (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$', winglobmsg),
189 (r'^ .*file://\$TESTTMP',
189 (r'^ .*file://\$TESTTMP',
190 'write "file:/*/$TESTTMP" + (glob) to match on windows too'),
190 'write "file:/*/$TESTTMP" + (glob) to match on windows too'),
191 (r'^ (cat|find): .*: No such file or directory',
191 (r'^ (cat|find): .*: No such file or directory',
192 'use test -f to test for file existence'),
192 'use test -f to test for file existence'),
193 (r'^ diff -[^ -]*p',
193 (r'^ diff -[^ -]*p',
194 "don't use (external) diff with -p for portability"),
194 "don't use (external) diff with -p for portability"),
195 (r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
195 (r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
196 "glob timezone field in diff output for portability"),
196 "glob timezone field in diff output for portability"),
197 (r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
197 (r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
198 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability"),
198 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability"),
199 (r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
199 (r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
200 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability"),
200 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability"),
201 (r'^ @@ -[0-9]+ [+][0-9]+ @@',
201 (r'^ @@ -[0-9]+ [+][0-9]+ @@',
202 "use '@@ -N* +N* @@ (glob)' style chunk header for portability"),
202 "use '@@ -N* +N* @@ (glob)' style chunk header for portability"),
203 (uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
203 (uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
204 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
204 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
205 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)"),
205 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)"),
206 ],
206 ],
207 # warnings
207 # warnings
208 [
208 [
209 (r'^ [^*?/\n]* \(glob\)$',
209 (r'^ [^*?/\n]* \(glob\)$',
210 "glob match with no glob character (?*/)"),
210 "glob match with no glob character (?*/)"),
211 ]
211 ]
212 ]
212 ]
213
213
214 for i in [0, 1]:
214 for i in [0, 1]:
215 for tp in testpats[i]:
215 for tp in testpats[i]:
216 p = tp[0]
216 p = tp[0]
217 m = tp[1]
217 m = tp[1]
218 if p.startswith(r'^'):
218 if p.startswith(r'^'):
219 p = r"^ [$>] (%s)" % p[1:]
219 p = r"^ [$>] (%s)" % p[1:]
220 else:
220 else:
221 p = r"^ [$>] .*(%s)" % p
221 p = r"^ [$>] .*(%s)" % p
222 utestpats[i].append((p, m) + tp[2:])
222 utestpats[i].append((p, m) + tp[2:])
223
223
224 utestfilters = [
224 utestfilters = [
225 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
225 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
226 (r"( *)(#([^\n]*\S)?)", repcomment),
226 (r"( *)(#([^\n]*\S)?)", repcomment),
227 ]
227 ]
228
228
229 pypats = [
229 pypats = [
230 [
230 [
231 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
231 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
232 "tuple parameter unpacking not available in Python 3+"),
232 "tuple parameter unpacking not available in Python 3+"),
233 (r'lambda\s*\(.*,.*\)',
233 (r'lambda\s*\(.*,.*\)',
234 "tuple parameter unpacking not available in Python 3+"),
234 "tuple parameter unpacking not available in Python 3+"),
235 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
235 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
236 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
236 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
237 (r'dict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
237 (r'dict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
238 'dict-from-generator'),
238 'dict-from-generator'),
239 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
239 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
240 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
240 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
241 (r'^\s*\t', "don't use tabs"),
241 (r'^\s*\t', "don't use tabs"),
242 (r'\S;\s*\n', "semicolon"),
242 (r'\S;\s*\n', "semicolon"),
243 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
243 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
244 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
244 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
245 (r'(\w|\)),\w', "missing whitespace after ,"),
245 (r'(\w|\)),\w', "missing whitespace after ,"),
246 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
246 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
247 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
247 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
248 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
248 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
249 (r'.{81}', "line too long"),
249 (r'.{81}', "line too long"),
250 (r'[^\n]\Z', "no trailing newline"),
250 (r'[^\n]\Z', "no trailing newline"),
251 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
251 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
252 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
252 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
253 # "don't use underbars in identifiers"),
253 # "don't use underbars in identifiers"),
254 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
254 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
255 "don't use camelcase in identifiers"),
255 "don't use camelcase in identifiers"),
256 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
256 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
257 "linebreak after :"),
257 "linebreak after :"),
258 (r'class\s[^( \n]+:', "old-style class, use class foo(object)",
258 (r'class\s[^( \n]+:', "old-style class, use class foo(object)",
259 r'#.*old-style'),
259 r'#.*old-style'),
260 (r'class\s[^( \n]+\(\):',
260 (r'class\s[^( \n]+\(\):',
261 "class foo() creates old style object, use class foo(object)",
261 "class foo() creates old style object, use class foo(object)",
262 r'#.*old-style'),
262 r'#.*old-style'),
263 (r'\b(%s)\(' % '|'.join(k for k in keyword.kwlist
263 (r'\b(%s)\(' % '|'.join(k for k in keyword.kwlist
264 if k not in ('print', 'exec')),
264 if k not in ('print', 'exec')),
265 "Python keyword is not a function"),
265 "Python keyword is not a function"),
266 (r',]', "unneeded trailing ',' in list"),
266 (r',]', "unneeded trailing ',' in list"),
267 # (r'class\s[A-Z][^\(]*\((?!Exception)',
267 # (r'class\s[A-Z][^\(]*\((?!Exception)',
268 # "don't capitalize non-exception classes"),
268 # "don't capitalize non-exception classes"),
269 # (r'in range\(', "use xrange"),
269 # (r'in range\(', "use xrange"),
270 # (r'^\s*print\s+', "avoid using print in core and extensions"),
270 # (r'^\s*print\s+', "avoid using print in core and extensions"),
271 (r'[\x80-\xff]', "non-ASCII character literal"),
271 (r'[\x80-\xff]', "non-ASCII character literal"),
272 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
272 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
273 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
273 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
274 "gratuitous whitespace after Python keyword"),
274 "gratuitous whitespace after Python keyword"),
275 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
275 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
276 # (r'\s\s=', "gratuitous whitespace before ="),
276 # (r'\s\s=', "gratuitous whitespace before ="),
277 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
277 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
278 "missing whitespace around operator"),
278 "missing whitespace around operator"),
279 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
279 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
280 "missing whitespace around operator"),
280 "missing whitespace around operator"),
281 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
281 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
282 "missing whitespace around operator"),
282 "missing whitespace around operator"),
283 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
283 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
284 "wrong whitespace around ="),
284 "wrong whitespace around ="),
285 (r'\([^()]*( =[^=]|[^<>!=]= )',
285 (r'\([^()]*( =[^=]|[^<>!=]= )',
286 "no whitespace around = for named parameters"),
286 "no whitespace around = for named parameters"),
287 (r'raise Exception', "don't raise generic exceptions"),
287 (r'raise Exception', "don't raise generic exceptions"),
288 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
288 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
289 "don't use old-style two-argument raise, use Exception(message)"),
289 "don't use old-style two-argument raise, use Exception(message)"),
290 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
290 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
291 (r' [=!]=\s+(True|False|None)',
291 (r' [=!]=\s+(True|False|None)',
292 "comparison with singleton, use 'is' or 'is not' instead"),
292 "comparison with singleton, use 'is' or 'is not' instead"),
293 (r'^\s*(while|if) [01]:',
293 (r'^\s*(while|if) [01]:',
294 "use True/False for constant Boolean expression"),
294 "use True/False for constant Boolean expression"),
295 (r'(?:(?<!def)\s+|\()hasattr',
295 (r'(?:(?<!def)\s+|\()hasattr',
296 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
296 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
297 (r'opener\([^)]*\).read\(',
297 (r'opener\([^)]*\).read\(',
298 "use opener.read() instead"),
298 "use opener.read() instead"),
299 (r'opener\([^)]*\).write\(',
299 (r'opener\([^)]*\).write\(',
300 "use opener.write() instead"),
300 "use opener.write() instead"),
301 (r'[\s\(](open|file)\([^)]*\)\.read\(',
301 (r'[\s\(](open|file)\([^)]*\)\.read\(',
302 "use util.readfile() instead"),
302 "use util.readfile() instead"),
303 (r'[\s\(](open|file)\([^)]*\)\.write\(',
303 (r'[\s\(](open|file)\([^)]*\)\.write\(',
304 "use util.writefile() instead"),
304 "use util.writefile() instead"),
305 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
305 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
306 "always assign an opened file to a variable, and close it afterwards"),
306 "always assign an opened file to a variable, and close it afterwards"),
307 (r'[\s\(](open|file)\([^)]*\)\.',
307 (r'[\s\(](open|file)\([^)]*\)\.',
308 "always assign an opened file to a variable, and close it afterwards"),
308 "always assign an opened file to a variable, and close it afterwards"),
309 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
309 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
310 (r'\.debug\(\_', "don't mark debug messages for translation"),
310 (r'\.debug\(\_', "don't mark debug messages for translation"),
311 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
311 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
312 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
312 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
313 (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
313 (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
314 'legacy exception syntax; use "as" instead of ","'),
314 'legacy exception syntax; use "as" instead of ","'),
315 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
315 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
316 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
316 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
317 (r'\b__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
317 (r'\b__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
318 (r'os\.path\.join\(.*, *(""|\'\')\)',
318 (r'os\.path\.join\(.*, *(""|\'\')\)',
319 "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
319 "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
320 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
320 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
321 # XXX only catch mutable arguments on the first line of the definition
321 # XXX only catch mutable arguments on the first line of the definition
322 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
322 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
323 (r'\butil\.Abort\b', "directly use error.Abort"),
323 (r'\butil\.Abort\b', "directly use error.Abort"),
324 (r'^import Queue', "don't use Queue, use util.queue + util.empty"),
324 (r'^import Queue', "don't use Queue, use util.queue + util.empty"),
325 (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
325 (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
326 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
326 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
327 (r'\.next\(\)', "don't use .next(), use next(...)"),
327 (r'\.next\(\)', "don't use .next(), use next(...)"),
328
328
329 # rules depending on implementation of repquote()
329 # rules depending on implementation of repquote()
330 (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
330 (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
331 'string join across lines with no space'),
331 'string join across lines with no space'),
332 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
332 (r'''(?x)ui\.(status|progress|write|note|warn)\(
333 [ \t\n#]*
334 (?# any strings/comments might precede a string, which
335 # contains translatable message)
336 ((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
337 (?# sequence consisting of below might precede translatable message
338 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
339 # - escaped character: "\\", "\n", "\0" ...
340 # - character other than '%', 'b' as '\', and 'x' as alphabet)
341 (['"]|\'\'\'|""")
342 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
343 (?# this regexp can't use [^...] style,
344 # because _preparepats forcibly adds "\n" into [^...],
345 # even though this regexp wants match it against "\n")''',
333 "missing _() in ui message (use () to hide false-positives)"),
346 "missing _() in ui message (use () to hide false-positives)"),
334 ],
347 ],
335 # warnings
348 # warnings
336 [
349 [
337 # rules depending on implementation of repquote()
350 # rules depending on implementation of repquote()
338 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
351 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
339 ]
352 ]
340 ]
353 ]
341
354
342 pyfilters = [
355 pyfilters = [
343 (r"""(?msx)(?P<comment>\#.*?$)|
356 (r"""(?msx)(?P<comment>\#.*?$)|
344 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
357 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
345 (?P<text>(([^\\]|\\.)*?))
358 (?P<text>(([^\\]|\\.)*?))
346 (?P=quote))""", reppython),
359 (?P=quote))""", reppython),
347 ]
360 ]
348
361
349 txtfilters = []
362 txtfilters = []
350
363
351 txtpats = [
364 txtpats = [
352 [
365 [
353 ('\s$', 'trailing whitespace'),
366 ('\s$', 'trailing whitespace'),
354 ('.. note::[ \n][^\n]', 'add two newlines after note::')
367 ('.. note::[ \n][^\n]', 'add two newlines after note::')
355 ],
368 ],
356 []
369 []
357 ]
370 ]
358
371
359 cpats = [
372 cpats = [
360 [
373 [
361 (r'//', "don't use //-style comments"),
374 (r'//', "don't use //-style comments"),
362 (r'^ ', "don't use spaces to indent"),
375 (r'^ ', "don't use spaces to indent"),
363 (r'\S\t', "don't use tabs except for indent"),
376 (r'\S\t', "don't use tabs except for indent"),
364 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
377 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
365 (r'.{81}', "line too long"),
378 (r'.{81}', "line too long"),
366 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
379 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
367 (r'return\(', "return is not a function"),
380 (r'return\(', "return is not a function"),
368 (r' ;', "no space before ;"),
381 (r' ;', "no space before ;"),
369 (r'[^;] \)', "no space before )"),
382 (r'[^;] \)', "no space before )"),
370 (r'[)][{]', "space between ) and {"),
383 (r'[)][{]', "space between ) and {"),
371 (r'\w+\* \w+', "use int *foo, not int* foo"),
384 (r'\w+\* \w+', "use int *foo, not int* foo"),
372 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
385 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
373 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
386 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
374 (r'\w,\w', "missing whitespace after ,"),
387 (r'\w,\w', "missing whitespace after ,"),
375 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
388 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
376 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
389 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
377 (r'^#\s+\w', "use #foo, not # foo"),
390 (r'^#\s+\w', "use #foo, not # foo"),
378 (r'[^\n]\Z', "no trailing newline"),
391 (r'[^\n]\Z', "no trailing newline"),
379 (r'^\s*#import\b', "use only #include in standard C code"),
392 (r'^\s*#import\b', "use only #include in standard C code"),
380 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
393 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
381 (r'strcat\(', "don't use strcat"),
394 (r'strcat\(', "don't use strcat"),
382
395
383 # rules depending on implementation of repquote()
396 # rules depending on implementation of repquote()
384 ],
397 ],
385 # warnings
398 # warnings
386 [
399 [
387 # rules depending on implementation of repquote()
400 # rules depending on implementation of repquote()
388 ]
401 ]
389 ]
402 ]
390
403
391 cfilters = [
404 cfilters = [
392 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
405 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
393 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
406 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
394 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
407 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
395 (r'(\()([^)]+\))', repcallspaces),
408 (r'(\()([^)]+\))', repcallspaces),
396 ]
409 ]
397
410
398 inutilpats = [
411 inutilpats = [
399 [
412 [
400 (r'\bui\.', "don't use ui in util"),
413 (r'\bui\.', "don't use ui in util"),
401 ],
414 ],
402 # warnings
415 # warnings
403 []
416 []
404 ]
417 ]
405
418
406 inrevlogpats = [
419 inrevlogpats = [
407 [
420 [
408 (r'\brepo\.', "don't use repo in revlog"),
421 (r'\brepo\.', "don't use repo in revlog"),
409 ],
422 ],
410 # warnings
423 # warnings
411 []
424 []
412 ]
425 ]
413
426
414 webtemplatefilters = []
427 webtemplatefilters = []
415
428
416 webtemplatepats = [
429 webtemplatepats = [
417 [],
430 [],
418 [
431 [
419 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
432 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
420 'follow desc keyword with either firstline or websub'),
433 'follow desc keyword with either firstline or websub'),
421 ]
434 ]
422 ]
435 ]
423
436
424 checks = [
437 checks = [
425 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
438 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
426 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
439 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
427 ('c', r'.*\.[ch]$', '', cfilters, cpats),
440 ('c', r'.*\.[ch]$', '', cfilters, cpats),
428 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
441 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
429 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
442 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
430 pyfilters, inrevlogpats),
443 pyfilters, inrevlogpats),
431 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
444 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
432 inutilpats),
445 inutilpats),
433 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
446 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
434 ('web template', r'mercurial/templates/.*\.tmpl', '',
447 ('web template', r'mercurial/templates/.*\.tmpl', '',
435 webtemplatefilters, webtemplatepats),
448 webtemplatefilters, webtemplatepats),
436 ]
449 ]
437
450
438 def _preparepats():
451 def _preparepats():
439 for c in checks:
452 for c in checks:
440 failandwarn = c[-1]
453 failandwarn = c[-1]
441 for pats in failandwarn:
454 for pats in failandwarn:
442 for i, pseq in enumerate(pats):
455 for i, pseq in enumerate(pats):
443 # fix-up regexes for multi-line searches
456 # fix-up regexes for multi-line searches
444 p = pseq[0]
457 p = pseq[0]
445 # \s doesn't match \n
458 # \s doesn't match \n
446 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
459 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
447 # [^...] doesn't match newline
460 # [^...] doesn't match newline
448 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
461 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
449
462
450 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
463 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
451 filters = c[3]
464 filters = c[3]
452 for i, flt in enumerate(filters):
465 for i, flt in enumerate(filters):
453 filters[i] = re.compile(flt[0]), flt[1]
466 filters[i] = re.compile(flt[0]), flt[1]
454 _preparepats()
467 _preparepats()
455
468
456 class norepeatlogger(object):
469 class norepeatlogger(object):
457 def __init__(self):
470 def __init__(self):
458 self._lastseen = None
471 self._lastseen = None
459
472
460 def log(self, fname, lineno, line, msg, blame):
473 def log(self, fname, lineno, line, msg, blame):
461 """print error related a to given line of a given file.
474 """print error related a to given line of a given file.
462
475
463 The faulty line will also be printed but only once in the case
476 The faulty line will also be printed but only once in the case
464 of multiple errors.
477 of multiple errors.
465
478
466 :fname: filename
479 :fname: filename
467 :lineno: line number
480 :lineno: line number
468 :line: actual content of the line
481 :line: actual content of the line
469 :msg: error message
482 :msg: error message
470 """
483 """
471 msgid = fname, lineno, line
484 msgid = fname, lineno, line
472 if msgid != self._lastseen:
485 if msgid != self._lastseen:
473 if blame:
486 if blame:
474 print("%s:%d (%s):" % (fname, lineno, blame))
487 print("%s:%d (%s):" % (fname, lineno, blame))
475 else:
488 else:
476 print("%s:%d:" % (fname, lineno))
489 print("%s:%d:" % (fname, lineno))
477 print(" > %s" % line)
490 print(" > %s" % line)
478 self._lastseen = msgid
491 self._lastseen = msgid
479 print(" " + msg)
492 print(" " + msg)
480
493
481 _defaultlogger = norepeatlogger()
494 _defaultlogger = norepeatlogger()
482
495
483 def getblame(f):
496 def getblame(f):
484 lines = []
497 lines = []
485 for l in os.popen('hg annotate -un %s' % f):
498 for l in os.popen('hg annotate -un %s' % f):
486 start, line = l.split(':', 1)
499 start, line = l.split(':', 1)
487 user, rev = start.split()
500 user, rev = start.split()
488 lines.append((line[1:-1], user, rev))
501 lines.append((line[1:-1], user, rev))
489 return lines
502 return lines
490
503
491 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
504 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
492 blame=False, debug=False, lineno=True):
505 blame=False, debug=False, lineno=True):
493 """checks style and portability of a given file
506 """checks style and portability of a given file
494
507
495 :f: filepath
508 :f: filepath
496 :logfunc: function used to report error
509 :logfunc: function used to report error
497 logfunc(filename, linenumber, linecontent, errormessage)
510 logfunc(filename, linenumber, linecontent, errormessage)
498 :maxerr: number of error to display before aborting.
511 :maxerr: number of error to display before aborting.
499 Set to false (default) to report all errors
512 Set to false (default) to report all errors
500
513
501 return True if no error is found, False otherwise.
514 return True if no error is found, False otherwise.
502 """
515 """
503 blamecache = None
516 blamecache = None
504 result = True
517 result = True
505
518
506 try:
519 try:
507 with opentext(f) as fp:
520 with opentext(f) as fp:
508 try:
521 try:
509 pre = post = fp.read()
522 pre = post = fp.read()
510 except UnicodeDecodeError as e:
523 except UnicodeDecodeError as e:
511 print("%s while reading %s" % (e, f))
524 print("%s while reading %s" % (e, f))
512 return result
525 return result
513 except IOError as e:
526 except IOError as e:
514 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
527 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
515 return result
528 return result
516
529
517 for name, match, magic, filters, pats in checks:
530 for name, match, magic, filters, pats in checks:
518 if debug:
531 if debug:
519 print(name, f)
532 print(name, f)
520 fc = 0
533 fc = 0
521 if not (re.match(match, f) or (magic and re.search(magic, pre))):
534 if not (re.match(match, f) or (magic and re.search(magic, pre))):
522 if debug:
535 if debug:
523 print("Skipping %s for %s it doesn't match %s" % (
536 print("Skipping %s for %s it doesn't match %s" % (
524 name, match, f))
537 name, match, f))
525 continue
538 continue
526 if "no-" "check-code" in pre:
539 if "no-" "check-code" in pre:
527 # If you're looking at this line, it's because a file has:
540 # If you're looking at this line, it's because a file has:
528 # no- check- code
541 # no- check- code
529 # but the reason to output skipping is to make life for
542 # but the reason to output skipping is to make life for
530 # tests easier. So, instead of writing it with a normal
543 # tests easier. So, instead of writing it with a normal
531 # spelling, we write it with the expected spelling from
544 # spelling, we write it with the expected spelling from
532 # tests/test-check-code.t
545 # tests/test-check-code.t
533 print("Skipping %s it has no-che?k-code (glob)" % f)
546 print("Skipping %s it has no-che?k-code (glob)" % f)
534 return "Skip" # skip checking this file
547 return "Skip" # skip checking this file
535 for p, r in filters:
548 for p, r in filters:
536 post = re.sub(p, r, post)
549 post = re.sub(p, r, post)
537 nerrs = len(pats[0]) # nerr elements are errors
550 nerrs = len(pats[0]) # nerr elements are errors
538 if warnings:
551 if warnings:
539 pats = pats[0] + pats[1]
552 pats = pats[0] + pats[1]
540 else:
553 else:
541 pats = pats[0]
554 pats = pats[0]
542 # print post # uncomment to show filtered version
555 # print post # uncomment to show filtered version
543
556
544 if debug:
557 if debug:
545 print("Checking %s for %s" % (name, f))
558 print("Checking %s for %s" % (name, f))
546
559
547 prelines = None
560 prelines = None
548 errors = []
561 errors = []
549 for i, pat in enumerate(pats):
562 for i, pat in enumerate(pats):
550 if len(pat) == 3:
563 if len(pat) == 3:
551 p, msg, ignore = pat
564 p, msg, ignore = pat
552 else:
565 else:
553 p, msg = pat
566 p, msg = pat
554 ignore = None
567 ignore = None
555 if i >= nerrs:
568 if i >= nerrs:
556 msg = "warning: " + msg
569 msg = "warning: " + msg
557
570
558 pos = 0
571 pos = 0
559 n = 0
572 n = 0
560 for m in p.finditer(post):
573 for m in p.finditer(post):
561 if prelines is None:
574 if prelines is None:
562 prelines = pre.splitlines()
575 prelines = pre.splitlines()
563 postlines = post.splitlines(True)
576 postlines = post.splitlines(True)
564
577
565 start = m.start()
578 start = m.start()
566 while n < len(postlines):
579 while n < len(postlines):
567 step = len(postlines[n])
580 step = len(postlines[n])
568 if pos + step > start:
581 if pos + step > start:
569 break
582 break
570 pos += step
583 pos += step
571 n += 1
584 n += 1
572 l = prelines[n]
585 l = prelines[n]
573
586
574 if ignore and re.search(ignore, l, re.MULTILINE):
587 if ignore and re.search(ignore, l, re.MULTILINE):
575 if debug:
588 if debug:
576 print("Skipping %s for %s:%s (ignore pattern)" % (
589 print("Skipping %s for %s:%s (ignore pattern)" % (
577 name, f, n))
590 name, f, n))
578 continue
591 continue
579 bd = ""
592 bd = ""
580 if blame:
593 if blame:
581 bd = 'working directory'
594 bd = 'working directory'
582 if not blamecache:
595 if not blamecache:
583 blamecache = getblame(f)
596 blamecache = getblame(f)
584 if n < len(blamecache):
597 if n < len(blamecache):
585 bl, bu, br = blamecache[n]
598 bl, bu, br = blamecache[n]
586 if bl == l:
599 if bl == l:
587 bd = '%s@%s' % (bu, br)
600 bd = '%s@%s' % (bu, br)
588
601
589 errors.append((f, lineno and n + 1, l, msg, bd))
602 errors.append((f, lineno and n + 1, l, msg, bd))
590 result = False
603 result = False
591
604
592 errors.sort()
605 errors.sort()
593 for e in errors:
606 for e in errors:
594 logfunc(*e)
607 logfunc(*e)
595 fc += 1
608 fc += 1
596 if maxerr and fc >= maxerr:
609 if maxerr and fc >= maxerr:
597 print(" (too many errors, giving up)")
610 print(" (too many errors, giving up)")
598 break
611 break
599
612
600 return result
613 return result
601
614
602 if __name__ == "__main__":
615 if __name__ == "__main__":
603 parser = optparse.OptionParser("%prog [options] [files]")
616 parser = optparse.OptionParser("%prog [options] [files]")
604 parser.add_option("-w", "--warnings", action="store_true",
617 parser.add_option("-w", "--warnings", action="store_true",
605 help="include warning-level checks")
618 help="include warning-level checks")
606 parser.add_option("-p", "--per-file", type="int",
619 parser.add_option("-p", "--per-file", type="int",
607 help="max warnings per file")
620 help="max warnings per file")
608 parser.add_option("-b", "--blame", action="store_true",
621 parser.add_option("-b", "--blame", action="store_true",
609 help="use annotate to generate blame info")
622 help="use annotate to generate blame info")
610 parser.add_option("", "--debug", action="store_true",
623 parser.add_option("", "--debug", action="store_true",
611 help="show debug information")
624 help="show debug information")
612 parser.add_option("", "--nolineno", action="store_false",
625 parser.add_option("", "--nolineno", action="store_false",
613 dest='lineno', help="don't show line numbers")
626 dest='lineno', help="don't show line numbers")
614
627
615 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
628 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
616 lineno=True)
629 lineno=True)
617 (options, args) = parser.parse_args()
630 (options, args) = parser.parse_args()
618
631
619 if len(args) == 0:
632 if len(args) == 0:
620 check = glob.glob("*")
633 check = glob.glob("*")
621 else:
634 else:
622 check = args
635 check = args
623
636
624 ret = 0
637 ret = 0
625 for f in check:
638 for f in check:
626 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
639 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
627 blame=options.blame, debug=options.debug,
640 blame=options.blame, debug=options.debug,
628 lineno=options.lineno):
641 lineno=options.lineno):
629 ret = 1
642 ret = 1
630 sys.exit(ret)
643 sys.exit(ret)
@@ -1,59 +1,59 b''
1 # debugshell extension
1 # debugshell extension
2 """a python shell with repo, changelog & manifest objects"""
2 """a python shell with repo, changelog & manifest objects"""
3
3
4 from __future__ import absolute_import
4 from __future__ import absolute_import
5 import code
5 import code
6 import mercurial
6 import mercurial
7 import sys
7 import sys
8 from mercurial import (
8 from mercurial import (
9 cmdutil,
9 cmdutil,
10 demandimport,
10 demandimport,
11 )
11 )
12
12
13 cmdtable = {}
13 cmdtable = {}
14 command = cmdutil.command(cmdtable)
14 command = cmdutil.command(cmdtable)
15
15
16 def pdb(ui, repo, msg, **opts):
16 def pdb(ui, repo, msg, **opts):
17 objects = {
17 objects = {
18 'mercurial': mercurial,
18 'mercurial': mercurial,
19 'repo': repo,
19 'repo': repo,
20 'cl': repo.changelog,
20 'cl': repo.changelog,
21 'mf': repo.manifest,
21 'mf': repo.manifest,
22 }
22 }
23
23
24 code.interact(msg, local=objects)
24 code.interact(msg, local=objects)
25
25
26 def ipdb(ui, repo, msg, **opts):
26 def ipdb(ui, repo, msg, **opts):
27 import IPython
27 import IPython
28
28
29 cl = repo.changelog
29 cl = repo.changelog
30 mf = repo.manifest
30 mf = repo.manifest
31 cl, mf # use variables to appease pyflakes
31 cl, mf # use variables to appease pyflakes
32
32
33 IPython.embed()
33 IPython.embed()
34
34
35 @command('debugshell|dbsh', [])
35 @command('debugshell|dbsh', [])
36 def debugshell(ui, repo, **opts):
36 def debugshell(ui, repo, **opts):
37 bannermsg = "loaded repo : %s\n" \
37 bannermsg = "loaded repo : %s\n" \
38 "using source: %s" % (repo.root,
38 "using source: %s" % (repo.root,
39 mercurial.__path__[0])
39 mercurial.__path__[0])
40
40
41 pdbmap = {
41 pdbmap = {
42 'pdb' : 'code',
42 'pdb' : 'code',
43 'ipdb' : 'IPython'
43 'ipdb' : 'IPython'
44 }
44 }
45
45
46 debugger = ui.config("ui", "debugger")
46 debugger = ui.config("ui", "debugger")
47 if not debugger:
47 if not debugger:
48 debugger = 'pdb'
48 debugger = 'pdb'
49
49
50 # if IPython doesn't exist, fallback to code.interact
50 # if IPython doesn't exist, fallback to code.interact
51 try:
51 try:
52 with demandimport.deactivated():
52 with demandimport.deactivated():
53 __import__(pdbmap[debugger])
53 __import__(pdbmap[debugger])
54 except ImportError:
54 except ImportError:
55 ui.warn("%s debugger specified but %s module was not found\n"
55 ui.warn(("%s debugger specified but %s module was not found\n")
56 % (debugger, pdbmap[debugger]))
56 % (debugger, pdbmap[debugger]))
57 debugger = 'pdb'
57 debugger = 'pdb'
58
58
59 getattr(sys.modules[__name__], debugger)(ui, repo, bannermsg, **opts)
59 getattr(sys.modules[__name__], debugger)(ui, repo, bannermsg, **opts)
@@ -1,224 +1,224 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """usage: %s DOC ...
2 """usage: %s DOC ...
3
3
4 where DOC is the name of a document
4 where DOC is the name of a document
5 """
5 """
6
6
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import os
9 import os
10 import sys
10 import sys
11 import textwrap
11 import textwrap
12
12
13 # This script is executed during installs and may not have C extensions
13 # This script is executed during installs and may not have C extensions
14 # available. Relax C module requirements.
14 # available. Relax C module requirements.
15 os.environ['HGMODULEPOLICY'] = 'allow'
15 os.environ['HGMODULEPOLICY'] = 'allow'
16 # import from the live mercurial repo
16 # import from the live mercurial repo
17 sys.path.insert(0, "..")
17 sys.path.insert(0, "..")
18 from mercurial import demandimport; demandimport.enable()
18 from mercurial import demandimport; demandimport.enable()
19 from mercurial import (
19 from mercurial import (
20 commands,
20 commands,
21 extensions,
21 extensions,
22 help,
22 help,
23 minirst,
23 minirst,
24 ui as uimod,
24 ui as uimod,
25 )
25 )
26 from mercurial.i18n import (
26 from mercurial.i18n import (
27 gettext,
27 gettext,
28 _,
28 _,
29 )
29 )
30
30
31 table = commands.table
31 table = commands.table
32 globalopts = commands.globalopts
32 globalopts = commands.globalopts
33 helptable = help.helptable
33 helptable = help.helptable
34 loaddoc = help.loaddoc
34 loaddoc = help.loaddoc
35
35
36 def get_desc(docstr):
36 def get_desc(docstr):
37 if not docstr:
37 if not docstr:
38 return "", ""
38 return "", ""
39 # sanitize
39 # sanitize
40 docstr = docstr.strip("\n")
40 docstr = docstr.strip("\n")
41 docstr = docstr.rstrip()
41 docstr = docstr.rstrip()
42 shortdesc = docstr.splitlines()[0].strip()
42 shortdesc = docstr.splitlines()[0].strip()
43
43
44 i = docstr.find("\n")
44 i = docstr.find("\n")
45 if i != -1:
45 if i != -1:
46 desc = docstr[i + 2:]
46 desc = docstr[i + 2:]
47 else:
47 else:
48 desc = shortdesc
48 desc = shortdesc
49
49
50 desc = textwrap.dedent(desc)
50 desc = textwrap.dedent(desc)
51
51
52 return (shortdesc, desc)
52 return (shortdesc, desc)
53
53
54 def get_opts(opts):
54 def get_opts(opts):
55 for opt in opts:
55 for opt in opts:
56 if len(opt) == 5:
56 if len(opt) == 5:
57 shortopt, longopt, default, desc, optlabel = opt
57 shortopt, longopt, default, desc, optlabel = opt
58 else:
58 else:
59 shortopt, longopt, default, desc = opt
59 shortopt, longopt, default, desc = opt
60 optlabel = _("VALUE")
60 optlabel = _("VALUE")
61 allopts = []
61 allopts = []
62 if shortopt:
62 if shortopt:
63 allopts.append("-%s" % shortopt)
63 allopts.append("-%s" % shortopt)
64 if longopt:
64 if longopt:
65 allopts.append("--%s" % longopt)
65 allopts.append("--%s" % longopt)
66 if isinstance(default, list):
66 if isinstance(default, list):
67 allopts[-1] += " <%s[+]>" % optlabel
67 allopts[-1] += " <%s[+]>" % optlabel
68 elif (default is not None) and not isinstance(default, bool):
68 elif (default is not None) and not isinstance(default, bool):
69 allopts[-1] += " <%s>" % optlabel
69 allopts[-1] += " <%s>" % optlabel
70 if '\n' in desc:
70 if '\n' in desc:
71 # only remove line breaks and indentation
71 # only remove line breaks and indentation
72 desc = ' '.join(l.lstrip() for l in desc.split('\n'))
72 desc = ' '.join(l.lstrip() for l in desc.split('\n'))
73 desc += default and _(" (default: %s)") % default or ""
73 desc += default and _(" (default: %s)") % default or ""
74 yield (", ".join(allopts), desc)
74 yield (", ".join(allopts), desc)
75
75
76 def get_cmd(cmd, cmdtable):
76 def get_cmd(cmd, cmdtable):
77 d = {}
77 d = {}
78 attr = cmdtable[cmd]
78 attr = cmdtable[cmd]
79 cmds = cmd.lstrip("^").split("|")
79 cmds = cmd.lstrip("^").split("|")
80
80
81 d['cmd'] = cmds[0]
81 d['cmd'] = cmds[0]
82 d['aliases'] = cmd.split("|")[1:]
82 d['aliases'] = cmd.split("|")[1:]
83 d['desc'] = get_desc(gettext(attr[0].__doc__))
83 d['desc'] = get_desc(gettext(attr[0].__doc__))
84 d['opts'] = list(get_opts(attr[1]))
84 d['opts'] = list(get_opts(attr[1]))
85
85
86 s = 'hg ' + cmds[0]
86 s = 'hg ' + cmds[0]
87 if len(attr) > 2:
87 if len(attr) > 2:
88 if not attr[2].startswith('hg'):
88 if not attr[2].startswith('hg'):
89 s += ' ' + attr[2]
89 s += ' ' + attr[2]
90 else:
90 else:
91 s = attr[2]
91 s = attr[2]
92 d['synopsis'] = s.strip()
92 d['synopsis'] = s.strip()
93
93
94 return d
94 return d
95
95
96 def showdoc(ui):
96 def showdoc(ui):
97 # print options
97 # print options
98 ui.write(minirst.section(_("Options")))
98 ui.write(minirst.section(_("Options")))
99 multioccur = False
99 multioccur = False
100 for optstr, desc in get_opts(globalopts):
100 for optstr, desc in get_opts(globalopts):
101 ui.write("%s\n %s\n\n" % (optstr, desc))
101 ui.write("%s\n %s\n\n" % (optstr, desc))
102 if optstr.endswith("[+]>"):
102 if optstr.endswith("[+]>"):
103 multioccur = True
103 multioccur = True
104 if multioccur:
104 if multioccur:
105 ui.write(_("\n[+] marked option can be specified multiple times\n"))
105 ui.write(_("\n[+] marked option can be specified multiple times\n"))
106 ui.write("\n")
106 ui.write("\n")
107
107
108 # print cmds
108 # print cmds
109 ui.write(minirst.section(_("Commands")))
109 ui.write(minirst.section(_("Commands")))
110 commandprinter(ui, table, minirst.subsection)
110 commandprinter(ui, table, minirst.subsection)
111
111
112 # print help topics
112 # print help topics
113 # The config help topic is included in the hgrc.5 man page.
113 # The config help topic is included in the hgrc.5 man page.
114 helpprinter(ui, helptable, minirst.section, exclude=['config'])
114 helpprinter(ui, helptable, minirst.section, exclude=['config'])
115
115
116 ui.write(minirst.section(_("Extensions")))
116 ui.write(minirst.section(_("Extensions")))
117 ui.write(_("This section contains help for extensions that are "
117 ui.write(_("This section contains help for extensions that are "
118 "distributed together with Mercurial. Help for other "
118 "distributed together with Mercurial. Help for other "
119 "extensions is available in the help system."))
119 "extensions is available in the help system."))
120 ui.write("\n\n"
120 ui.write(("\n\n"
121 ".. contents::\n"
121 ".. contents::\n"
122 " :class: htmlonly\n"
122 " :class: htmlonly\n"
123 " :local:\n"
123 " :local:\n"
124 " :depth: 1\n\n")
124 " :depth: 1\n\n"))
125
125
126 for extensionname in sorted(allextensionnames()):
126 for extensionname in sorted(allextensionnames()):
127 mod = extensions.load(ui, extensionname, None)
127 mod = extensions.load(ui, extensionname, None)
128 ui.write(minirst.subsection(extensionname))
128 ui.write(minirst.subsection(extensionname))
129 ui.write("%s\n\n" % gettext(mod.__doc__))
129 ui.write("%s\n\n" % gettext(mod.__doc__))
130 cmdtable = getattr(mod, 'cmdtable', None)
130 cmdtable = getattr(mod, 'cmdtable', None)
131 if cmdtable:
131 if cmdtable:
132 ui.write(minirst.subsubsection(_('Commands')))
132 ui.write(minirst.subsubsection(_('Commands')))
133 commandprinter(ui, cmdtable, minirst.subsubsubsection)
133 commandprinter(ui, cmdtable, minirst.subsubsubsection)
134
134
135 def showtopic(ui, topic):
135 def showtopic(ui, topic):
136 extrahelptable = [
136 extrahelptable = [
137 (["common"], '', loaddoc('common')),
137 (["common"], '', loaddoc('common')),
138 (["hg.1"], '', loaddoc('hg.1')),
138 (["hg.1"], '', loaddoc('hg.1')),
139 (["hg-ssh.8"], '', loaddoc('hg-ssh.8')),
139 (["hg-ssh.8"], '', loaddoc('hg-ssh.8')),
140 (["hgignore.5"], '', loaddoc('hgignore.5')),
140 (["hgignore.5"], '', loaddoc('hgignore.5')),
141 (["hgrc.5"], '', loaddoc('hgrc.5')),
141 (["hgrc.5"], '', loaddoc('hgrc.5')),
142 (["hgignore.5.gendoc"], '', loaddoc('hgignore')),
142 (["hgignore.5.gendoc"], '', loaddoc('hgignore')),
143 (["hgrc.5.gendoc"], '', loaddoc('config')),
143 (["hgrc.5.gendoc"], '', loaddoc('config')),
144 ]
144 ]
145 helpprinter(ui, helptable + extrahelptable, None, include=[topic])
145 helpprinter(ui, helptable + extrahelptable, None, include=[topic])
146
146
147 def helpprinter(ui, helptable, sectionfunc, include=[], exclude=[]):
147 def helpprinter(ui, helptable, sectionfunc, include=[], exclude=[]):
148 for names, sec, doc in helptable:
148 for names, sec, doc in helptable:
149 if exclude and names[0] in exclude:
149 if exclude and names[0] in exclude:
150 continue
150 continue
151 if include and names[0] not in include:
151 if include and names[0] not in include:
152 continue
152 continue
153 for name in names:
153 for name in names:
154 ui.write(".. _%s:\n" % name)
154 ui.write(".. _%s:\n" % name)
155 ui.write("\n")
155 ui.write("\n")
156 if sectionfunc:
156 if sectionfunc:
157 ui.write(sectionfunc(sec))
157 ui.write(sectionfunc(sec))
158 if callable(doc):
158 if callable(doc):
159 doc = doc(ui)
159 doc = doc(ui)
160 ui.write(doc)
160 ui.write(doc)
161 ui.write("\n")
161 ui.write("\n")
162
162
163 def commandprinter(ui, cmdtable, sectionfunc):
163 def commandprinter(ui, cmdtable, sectionfunc):
164 h = {}
164 h = {}
165 for c, attr in cmdtable.items():
165 for c, attr in cmdtable.items():
166 f = c.split("|")[0]
166 f = c.split("|")[0]
167 f = f.lstrip("^")
167 f = f.lstrip("^")
168 h[f] = c
168 h[f] = c
169 cmds = h.keys()
169 cmds = h.keys()
170 cmds.sort()
170 cmds.sort()
171
171
172 for f in cmds:
172 for f in cmds:
173 if f.startswith("debug"):
173 if f.startswith("debug"):
174 continue
174 continue
175 d = get_cmd(h[f], cmdtable)
175 d = get_cmd(h[f], cmdtable)
176 ui.write(sectionfunc(d['cmd']))
176 ui.write(sectionfunc(d['cmd']))
177 # short description
177 # short description
178 ui.write(d['desc'][0])
178 ui.write(d['desc'][0])
179 # synopsis
179 # synopsis
180 ui.write("::\n\n")
180 ui.write("::\n\n")
181 synopsislines = d['synopsis'].splitlines()
181 synopsislines = d['synopsis'].splitlines()
182 for line in synopsislines:
182 for line in synopsislines:
183 # some commands (such as rebase) have a multi-line
183 # some commands (such as rebase) have a multi-line
184 # synopsis
184 # synopsis
185 ui.write(" %s\n" % line)
185 ui.write(" %s\n" % line)
186 ui.write('\n')
186 ui.write('\n')
187 # description
187 # description
188 ui.write("%s\n\n" % d['desc'][1])
188 ui.write("%s\n\n" % d['desc'][1])
189 # options
189 # options
190 opt_output = list(d['opts'])
190 opt_output = list(d['opts'])
191 if opt_output:
191 if opt_output:
192 opts_len = max([len(line[0]) for line in opt_output])
192 opts_len = max([len(line[0]) for line in opt_output])
193 ui.write(_("Options:\n\n"))
193 ui.write(_("Options:\n\n"))
194 multioccur = False
194 multioccur = False
195 for optstr, desc in opt_output:
195 for optstr, desc in opt_output:
196 if desc:
196 if desc:
197 s = "%-*s %s" % (opts_len, optstr, desc)
197 s = "%-*s %s" % (opts_len, optstr, desc)
198 else:
198 else:
199 s = optstr
199 s = optstr
200 ui.write("%s\n" % s)
200 ui.write("%s\n" % s)
201 if optstr.endswith("[+]>"):
201 if optstr.endswith("[+]>"):
202 multioccur = True
202 multioccur = True
203 if multioccur:
203 if multioccur:
204 ui.write(_("\n[+] marked option can be specified"
204 ui.write(_("\n[+] marked option can be specified"
205 " multiple times\n"))
205 " multiple times\n"))
206 ui.write("\n")
206 ui.write("\n")
207 # aliases
207 # aliases
208 if d['aliases']:
208 if d['aliases']:
209 ui.write(_(" aliases: %s\n\n") % " ".join(d['aliases']))
209 ui.write(_(" aliases: %s\n\n") % " ".join(d['aliases']))
210
210
211
211
212 def allextensionnames():
212 def allextensionnames():
213 return extensions.enabled().keys() + extensions.disabled().keys()
213 return extensions.enabled().keys() + extensions.disabled().keys()
214
214
215 if __name__ == "__main__":
215 if __name__ == "__main__":
216 doc = 'hg.1.gendoc'
216 doc = 'hg.1.gendoc'
217 if len(sys.argv) > 1:
217 if len(sys.argv) > 1:
218 doc = sys.argv[1]
218 doc = sys.argv[1]
219
219
220 ui = uimod.ui()
220 ui = uimod.ui()
221 if doc == 'hg.1.gendoc':
221 if doc == 'hg.1.gendoc':
222 showdoc(ui)
222 showdoc(ui)
223 else:
223 else:
224 showtopic(ui, sys.argv[1])
224 showtopic(ui, sys.argv[1])
@@ -1,348 +1,348 b''
1 # Minimal support for git commands on an hg repository
1 # Minimal support for git commands on an hg repository
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.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 '''browse the repository in a graphical way
8 '''browse the repository in a graphical way
9
9
10 The hgk extension allows browsing the history of a repository in a
10 The hgk extension allows browsing the history of a repository in a
11 graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is not
11 graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is not
12 distributed with Mercurial.)
12 distributed with Mercurial.)
13
13
14 hgk consists of two parts: a Tcl script that does the displaying and
14 hgk consists of two parts: a Tcl script that does the displaying and
15 querying of information, and an extension to Mercurial named hgk.py,
15 querying of information, and an extension to Mercurial named hgk.py,
16 which provides hooks for hgk to get information. hgk can be found in
16 which provides hooks for hgk to get information. hgk can be found in
17 the contrib directory, and the extension is shipped in the hgext
17 the contrib directory, and the extension is shipped in the hgext
18 repository, and needs to be enabled.
18 repository, and needs to be enabled.
19
19
20 The :hg:`view` command will launch the hgk Tcl script. For this command
20 The :hg:`view` command will launch the hgk Tcl script. For this command
21 to work, hgk must be in your search path. Alternately, you can specify
21 to work, hgk must be in your search path. Alternately, you can specify
22 the path to hgk in your configuration file::
22 the path to hgk in your configuration file::
23
23
24 [hgk]
24 [hgk]
25 path = /location/of/hgk
25 path = /location/of/hgk
26
26
27 hgk can make use of the extdiff extension to visualize revisions.
27 hgk can make use of the extdiff extension to visualize revisions.
28 Assuming you had already configured extdiff vdiff command, just add::
28 Assuming you had already configured extdiff vdiff command, just add::
29
29
30 [hgk]
30 [hgk]
31 vdiff=vdiff
31 vdiff=vdiff
32
32
33 Revisions context menu will now display additional entries to fire
33 Revisions context menu will now display additional entries to fire
34 vdiff on hovered and selected revisions.
34 vdiff on hovered and selected revisions.
35 '''
35 '''
36
36
37 from __future__ import absolute_import
37 from __future__ import absolute_import
38
38
39 import os
39 import os
40
40
41 from mercurial.i18n import _
41 from mercurial.i18n import _
42 from mercurial.node import (
42 from mercurial.node import (
43 nullid,
43 nullid,
44 nullrev,
44 nullrev,
45 short,
45 short,
46 )
46 )
47 from mercurial import (
47 from mercurial import (
48 cmdutil,
48 cmdutil,
49 commands,
49 commands,
50 obsolete,
50 obsolete,
51 patch,
51 patch,
52 scmutil,
52 scmutil,
53 )
53 )
54
54
55 cmdtable = {}
55 cmdtable = {}
56 command = cmdutil.command(cmdtable)
56 command = cmdutil.command(cmdtable)
57 # Note for extension authors: ONLY specify testedwith = 'internal' for
57 # Note for extension authors: ONLY specify testedwith = 'internal' for
58 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
58 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
59 # be specifying the version(s) of Mercurial they are tested with, or
59 # be specifying the version(s) of Mercurial they are tested with, or
60 # leave the attribute unspecified.
60 # leave the attribute unspecified.
61 testedwith = 'internal'
61 testedwith = 'internal'
62
62
63 @command('debug-diff-tree',
63 @command('debug-diff-tree',
64 [('p', 'patch', None, _('generate patch')),
64 [('p', 'patch', None, _('generate patch')),
65 ('r', 'recursive', None, _('recursive')),
65 ('r', 'recursive', None, _('recursive')),
66 ('P', 'pretty', None, _('pretty')),
66 ('P', 'pretty', None, _('pretty')),
67 ('s', 'stdin', None, _('stdin')),
67 ('s', 'stdin', None, _('stdin')),
68 ('C', 'copy', None, _('detect copies')),
68 ('C', 'copy', None, _('detect copies')),
69 ('S', 'search', "", _('search'))],
69 ('S', 'search', "", _('search'))],
70 ('[OPTION]... NODE1 NODE2 [FILE]...'),
70 ('[OPTION]... NODE1 NODE2 [FILE]...'),
71 inferrepo=True)
71 inferrepo=True)
72 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
72 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
73 """diff trees from two commits"""
73 """diff trees from two commits"""
74 def __difftree(repo, node1, node2, files=[]):
74 def __difftree(repo, node1, node2, files=[]):
75 assert node2 is not None
75 assert node2 is not None
76 mmap = repo[node1].manifest()
76 mmap = repo[node1].manifest()
77 mmap2 = repo[node2].manifest()
77 mmap2 = repo[node2].manifest()
78 m = scmutil.match(repo[node1], files)
78 m = scmutil.match(repo[node1], files)
79 modified, added, removed = repo.status(node1, node2, m)[:3]
79 modified, added, removed = repo.status(node1, node2, m)[:3]
80 empty = short(nullid)
80 empty = short(nullid)
81
81
82 for f in modified:
82 for f in modified:
83 # TODO get file permissions
83 # TODO get file permissions
84 ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
84 ui.write((":100664 100664 %s %s M\t%s\t%s\n") %
85 (short(mmap[f]), short(mmap2[f]), f, f))
85 (short(mmap[f]), short(mmap2[f]), f, f))
86 for f in added:
86 for f in added:
87 ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
87 ui.write((":000000 100664 %s %s N\t%s\t%s\n") %
88 (empty, short(mmap2[f]), f, f))
88 (empty, short(mmap2[f]), f, f))
89 for f in removed:
89 for f in removed:
90 ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
90 ui.write((":100664 000000 %s %s D\t%s\t%s\n") %
91 (short(mmap[f]), empty, f, f))
91 (short(mmap[f]), empty, f, f))
92 ##
92 ##
93
93
94 while True:
94 while True:
95 if opts['stdin']:
95 if opts['stdin']:
96 try:
96 try:
97 line = raw_input().split(' ')
97 line = raw_input().split(' ')
98 node1 = line[0]
98 node1 = line[0]
99 if len(line) > 1:
99 if len(line) > 1:
100 node2 = line[1]
100 node2 = line[1]
101 else:
101 else:
102 node2 = None
102 node2 = None
103 except EOFError:
103 except EOFError:
104 break
104 break
105 node1 = repo.lookup(node1)
105 node1 = repo.lookup(node1)
106 if node2:
106 if node2:
107 node2 = repo.lookup(node2)
107 node2 = repo.lookup(node2)
108 else:
108 else:
109 node2 = node1
109 node2 = node1
110 node1 = repo.changelog.parents(node1)[0]
110 node1 = repo.changelog.parents(node1)[0]
111 if opts['patch']:
111 if opts['patch']:
112 if opts['pretty']:
112 if opts['pretty']:
113 catcommit(ui, repo, node2, "")
113 catcommit(ui, repo, node2, "")
114 m = scmutil.match(repo[node1], files)
114 m = scmutil.match(repo[node1], files)
115 diffopts = patch.difffeatureopts(ui)
115 diffopts = patch.difffeatureopts(ui)
116 diffopts.git = True
116 diffopts.git = True
117 chunks = patch.diff(repo, node1, node2, match=m,
117 chunks = patch.diff(repo, node1, node2, match=m,
118 opts=diffopts)
118 opts=diffopts)
119 for chunk in chunks:
119 for chunk in chunks:
120 ui.write(chunk)
120 ui.write(chunk)
121 else:
121 else:
122 __difftree(repo, node1, node2, files=files)
122 __difftree(repo, node1, node2, files=files)
123 if not opts['stdin']:
123 if not opts['stdin']:
124 break
124 break
125
125
126 def catcommit(ui, repo, n, prefix, ctx=None):
126 def catcommit(ui, repo, n, prefix, ctx=None):
127 nlprefix = '\n' + prefix
127 nlprefix = '\n' + prefix
128 if ctx is None:
128 if ctx is None:
129 ctx = repo[n]
129 ctx = repo[n]
130 # use ctx.node() instead ??
130 # use ctx.node() instead ??
131 ui.write(("tree %s\n" % short(ctx.changeset()[0])))
131 ui.write(("tree %s\n" % short(ctx.changeset()[0])))
132 for p in ctx.parents():
132 for p in ctx.parents():
133 ui.write(("parent %s\n" % p))
133 ui.write(("parent %s\n" % p))
134
134
135 date = ctx.date()
135 date = ctx.date()
136 description = ctx.description().replace("\0", "")
136 description = ctx.description().replace("\0", "")
137 ui.write(("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1])))
137 ui.write(("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1])))
138
138
139 if 'committer' in ctx.extra():
139 if 'committer' in ctx.extra():
140 ui.write(("committer %s\n" % ctx.extra()['committer']))
140 ui.write(("committer %s\n" % ctx.extra()['committer']))
141
141
142 ui.write(("revision %d\n" % ctx.rev()))
142 ui.write(("revision %d\n" % ctx.rev()))
143 ui.write(("branch %s\n" % ctx.branch()))
143 ui.write(("branch %s\n" % ctx.branch()))
144 if obsolete.isenabled(repo, obsolete.createmarkersopt):
144 if obsolete.isenabled(repo, obsolete.createmarkersopt):
145 if ctx.obsolete():
145 if ctx.obsolete():
146 ui.write(("obsolete\n"))
146 ui.write(("obsolete\n"))
147 ui.write(("phase %s\n\n" % ctx.phasestr()))
147 ui.write(("phase %s\n\n" % ctx.phasestr()))
148
148
149 if prefix != "":
149 if prefix != "":
150 ui.write("%s%s\n" % (prefix,
150 ui.write("%s%s\n" % (prefix,
151 description.replace('\n', nlprefix).strip()))
151 description.replace('\n', nlprefix).strip()))
152 else:
152 else:
153 ui.write(description + "\n")
153 ui.write(description + "\n")
154 if prefix:
154 if prefix:
155 ui.write('\0')
155 ui.write('\0')
156
156
157 @command('debug-merge-base', [], _('REV REV'))
157 @command('debug-merge-base', [], _('REV REV'))
158 def base(ui, repo, node1, node2):
158 def base(ui, repo, node1, node2):
159 """output common ancestor information"""
159 """output common ancestor information"""
160 node1 = repo.lookup(node1)
160 node1 = repo.lookup(node1)
161 node2 = repo.lookup(node2)
161 node2 = repo.lookup(node2)
162 n = repo.changelog.ancestor(node1, node2)
162 n = repo.changelog.ancestor(node1, node2)
163 ui.write(short(n) + "\n")
163 ui.write(short(n) + "\n")
164
164
165 @command('debug-cat-file',
165 @command('debug-cat-file',
166 [('s', 'stdin', None, _('stdin'))],
166 [('s', 'stdin', None, _('stdin'))],
167 _('[OPTION]... TYPE FILE'),
167 _('[OPTION]... TYPE FILE'),
168 inferrepo=True)
168 inferrepo=True)
169 def catfile(ui, repo, type=None, r=None, **opts):
169 def catfile(ui, repo, type=None, r=None, **opts):
170 """cat a specific revision"""
170 """cat a specific revision"""
171 # in stdin mode, every line except the commit is prefixed with two
171 # in stdin mode, every line except the commit is prefixed with two
172 # spaces. This way the our caller can find the commit without magic
172 # spaces. This way the our caller can find the commit without magic
173 # strings
173 # strings
174 #
174 #
175 prefix = ""
175 prefix = ""
176 if opts['stdin']:
176 if opts['stdin']:
177 try:
177 try:
178 (type, r) = raw_input().split(' ')
178 (type, r) = raw_input().split(' ')
179 prefix = " "
179 prefix = " "
180 except EOFError:
180 except EOFError:
181 return
181 return
182
182
183 else:
183 else:
184 if not type or not r:
184 if not type or not r:
185 ui.warn(_("cat-file: type or revision not supplied\n"))
185 ui.warn(_("cat-file: type or revision not supplied\n"))
186 commands.help_(ui, 'cat-file')
186 commands.help_(ui, 'cat-file')
187
187
188 while r:
188 while r:
189 if type != "commit":
189 if type != "commit":
190 ui.warn(_("aborting hg cat-file only understands commits\n"))
190 ui.warn(_("aborting hg cat-file only understands commits\n"))
191 return 1
191 return 1
192 n = repo.lookup(r)
192 n = repo.lookup(r)
193 catcommit(ui, repo, n, prefix)
193 catcommit(ui, repo, n, prefix)
194 if opts['stdin']:
194 if opts['stdin']:
195 try:
195 try:
196 (type, r) = raw_input().split(' ')
196 (type, r) = raw_input().split(' ')
197 except EOFError:
197 except EOFError:
198 break
198 break
199 else:
199 else:
200 break
200 break
201
201
202 # git rev-tree is a confusing thing. You can supply a number of
202 # git rev-tree is a confusing thing. You can supply a number of
203 # commit sha1s on the command line, and it walks the commit history
203 # commit sha1s on the command line, and it walks the commit history
204 # telling you which commits are reachable from the supplied ones via
204 # telling you which commits are reachable from the supplied ones via
205 # a bitmask based on arg position.
205 # a bitmask based on arg position.
206 # you can specify a commit to stop at by starting the sha1 with ^
206 # you can specify a commit to stop at by starting the sha1 with ^
207 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
207 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
208 def chlogwalk():
208 def chlogwalk():
209 count = len(repo)
209 count = len(repo)
210 i = count
210 i = count
211 l = [0] * 100
211 l = [0] * 100
212 chunk = 100
212 chunk = 100
213 while True:
213 while True:
214 if chunk > i:
214 if chunk > i:
215 chunk = i
215 chunk = i
216 i = 0
216 i = 0
217 else:
217 else:
218 i -= chunk
218 i -= chunk
219
219
220 for x in xrange(chunk):
220 for x in xrange(chunk):
221 if i + x >= count:
221 if i + x >= count:
222 l[chunk - x:] = [0] * (chunk - x)
222 l[chunk - x:] = [0] * (chunk - x)
223 break
223 break
224 if full is not None:
224 if full is not None:
225 if (i + x) in repo:
225 if (i + x) in repo:
226 l[x] = repo[i + x]
226 l[x] = repo[i + x]
227 l[x].changeset() # force reading
227 l[x].changeset() # force reading
228 else:
228 else:
229 if (i + x) in repo:
229 if (i + x) in repo:
230 l[x] = 1
230 l[x] = 1
231 for x in xrange(chunk - 1, -1, -1):
231 for x in xrange(chunk - 1, -1, -1):
232 if l[x] != 0:
232 if l[x] != 0:
233 yield (i + x, full is not None and l[x] or None)
233 yield (i + x, full is not None and l[x] or None)
234 if i == 0:
234 if i == 0:
235 break
235 break
236
236
237 # calculate and return the reachability bitmask for sha
237 # calculate and return the reachability bitmask for sha
238 def is_reachable(ar, reachable, sha):
238 def is_reachable(ar, reachable, sha):
239 if len(ar) == 0:
239 if len(ar) == 0:
240 return 1
240 return 1
241 mask = 0
241 mask = 0
242 for i in xrange(len(ar)):
242 for i in xrange(len(ar)):
243 if sha in reachable[i]:
243 if sha in reachable[i]:
244 mask |= 1 << i
244 mask |= 1 << i
245
245
246 return mask
246 return mask
247
247
248 reachable = []
248 reachable = []
249 stop_sha1 = []
249 stop_sha1 = []
250 want_sha1 = []
250 want_sha1 = []
251 count = 0
251 count = 0
252
252
253 # figure out which commits they are asking for and which ones they
253 # figure out which commits they are asking for and which ones they
254 # want us to stop on
254 # want us to stop on
255 for i, arg in enumerate(args):
255 for i, arg in enumerate(args):
256 if arg.startswith('^'):
256 if arg.startswith('^'):
257 s = repo.lookup(arg[1:])
257 s = repo.lookup(arg[1:])
258 stop_sha1.append(s)
258 stop_sha1.append(s)
259 want_sha1.append(s)
259 want_sha1.append(s)
260 elif arg != 'HEAD':
260 elif arg != 'HEAD':
261 want_sha1.append(repo.lookup(arg))
261 want_sha1.append(repo.lookup(arg))
262
262
263 # calculate the graph for the supplied commits
263 # calculate the graph for the supplied commits
264 for i, n in enumerate(want_sha1):
264 for i, n in enumerate(want_sha1):
265 reachable.append(set())
265 reachable.append(set())
266 visit = [n]
266 visit = [n]
267 reachable[i].add(n)
267 reachable[i].add(n)
268 while visit:
268 while visit:
269 n = visit.pop(0)
269 n = visit.pop(0)
270 if n in stop_sha1:
270 if n in stop_sha1:
271 continue
271 continue
272 for p in repo.changelog.parents(n):
272 for p in repo.changelog.parents(n):
273 if p not in reachable[i]:
273 if p not in reachable[i]:
274 reachable[i].add(p)
274 reachable[i].add(p)
275 visit.append(p)
275 visit.append(p)
276 if p in stop_sha1:
276 if p in stop_sha1:
277 continue
277 continue
278
278
279 # walk the repository looking for commits that are in our
279 # walk the repository looking for commits that are in our
280 # reachability graph
280 # reachability graph
281 for i, ctx in chlogwalk():
281 for i, ctx in chlogwalk():
282 if i not in repo:
282 if i not in repo:
283 continue
283 continue
284 n = repo.changelog.node(i)
284 n = repo.changelog.node(i)
285 mask = is_reachable(want_sha1, reachable, n)
285 mask = is_reachable(want_sha1, reachable, n)
286 if mask:
286 if mask:
287 parentstr = ""
287 parentstr = ""
288 if parents:
288 if parents:
289 pp = repo.changelog.parents(n)
289 pp = repo.changelog.parents(n)
290 if pp[0] != nullid:
290 if pp[0] != nullid:
291 parentstr += " " + short(pp[0])
291 parentstr += " " + short(pp[0])
292 if pp[1] != nullid:
292 if pp[1] != nullid:
293 parentstr += " " + short(pp[1])
293 parentstr += " " + short(pp[1])
294 if not full:
294 if not full:
295 ui.write("%s%s\n" % (short(n), parentstr))
295 ui.write("%s%s\n" % (short(n), parentstr))
296 elif full == "commit":
296 elif full == "commit":
297 ui.write("%s%s\n" % (short(n), parentstr))
297 ui.write("%s%s\n" % (short(n), parentstr))
298 catcommit(ui, repo, n, ' ', ctx)
298 catcommit(ui, repo, n, ' ', ctx)
299 else:
299 else:
300 (p1, p2) = repo.changelog.parents(n)
300 (p1, p2) = repo.changelog.parents(n)
301 (h, h1, h2) = map(short, (n, p1, p2))
301 (h, h1, h2) = map(short, (n, p1, p2))
302 (i1, i2) = map(repo.changelog.rev, (p1, p2))
302 (i1, i2) = map(repo.changelog.rev, (p1, p2))
303
303
304 date = ctx.date()[0]
304 date = ctx.date()[0]
305 ui.write("%s %s:%s" % (date, h, mask))
305 ui.write("%s %s:%s" % (date, h, mask))
306 mask = is_reachable(want_sha1, reachable, p1)
306 mask = is_reachable(want_sha1, reachable, p1)
307 if i1 != nullrev and mask > 0:
307 if i1 != nullrev and mask > 0:
308 ui.write("%s:%s " % (h1, mask)),
308 ui.write("%s:%s " % (h1, mask)),
309 mask = is_reachable(want_sha1, reachable, p2)
309 mask = is_reachable(want_sha1, reachable, p2)
310 if i2 != nullrev and mask > 0:
310 if i2 != nullrev and mask > 0:
311 ui.write("%s:%s " % (h2, mask))
311 ui.write("%s:%s " % (h2, mask))
312 ui.write("\n")
312 ui.write("\n")
313 if maxnr and count >= maxnr:
313 if maxnr and count >= maxnr:
314 break
314 break
315 count += 1
315 count += 1
316
316
317 # git rev-list tries to order things by date, and has the ability to stop
317 # git rev-list tries to order things by date, and has the ability to stop
318 # at a given commit without walking the whole repo. TODO add the stop
318 # at a given commit without walking the whole repo. TODO add the stop
319 # parameter
319 # parameter
320 @command('debug-rev-list',
320 @command('debug-rev-list',
321 [('H', 'header', None, _('header')),
321 [('H', 'header', None, _('header')),
322 ('t', 'topo-order', None, _('topo-order')),
322 ('t', 'topo-order', None, _('topo-order')),
323 ('p', 'parents', None, _('parents')),
323 ('p', 'parents', None, _('parents')),
324 ('n', 'max-count', 0, _('max-count'))],
324 ('n', 'max-count', 0, _('max-count'))],
325 ('[OPTION]... REV...'))
325 ('[OPTION]... REV...'))
326 def revlist(ui, repo, *revs, **opts):
326 def revlist(ui, repo, *revs, **opts):
327 """print revisions"""
327 """print revisions"""
328 if opts['header']:
328 if opts['header']:
329 full = "commit"
329 full = "commit"
330 else:
330 else:
331 full = None
331 full = None
332 copy = [x for x in revs]
332 copy = [x for x in revs]
333 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
333 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
334
334
335 @command('view',
335 @command('view',
336 [('l', 'limit', '',
336 [('l', 'limit', '',
337 _('limit number of changes displayed'), _('NUM'))],
337 _('limit number of changes displayed'), _('NUM'))],
338 _('[-l LIMIT] [REVRANGE]'))
338 _('[-l LIMIT] [REVRANGE]'))
339 def view(ui, repo, *etc, **opts):
339 def view(ui, repo, *etc, **opts):
340 "start interactive history viewer"
340 "start interactive history viewer"
341 os.chdir(repo.root)
341 os.chdir(repo.root)
342 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
342 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
343 if repo.filtername is None:
343 if repo.filtername is None:
344 optstr += '--hidden'
344 optstr += '--hidden'
345
345
346 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
346 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
347 ui.debug("running %s\n" % cmd)
347 ui.debug("running %s\n" % cmd)
348 ui.system(cmd)
348 ui.system(cmd)
@@ -1,754 +1,754 b''
1 # keyword.py - $Keyword$ expansion for Mercurial
1 # keyword.py - $Keyword$ expansion for Mercurial
2 #
2 #
3 # Copyright 2007-2015 Christian Ebert <blacktrash@gmx.net>
3 # Copyright 2007-2015 Christian Ebert <blacktrash@gmx.net>
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 # $Id$
8 # $Id$
9 #
9 #
10 # Keyword expansion hack against the grain of a Distributed SCM
10 # Keyword expansion hack against the grain of a Distributed SCM
11 #
11 #
12 # There are many good reasons why this is not needed in a distributed
12 # There are many good reasons why this is not needed in a distributed
13 # SCM, still it may be useful in very small projects based on single
13 # SCM, still it may be useful in very small projects based on single
14 # files (like LaTeX packages), that are mostly addressed to an
14 # files (like LaTeX packages), that are mostly addressed to an
15 # audience not running a version control system.
15 # audience not running a version control system.
16 #
16 #
17 # For in-depth discussion refer to
17 # For in-depth discussion refer to
18 # <https://mercurial-scm.org/wiki/KeywordPlan>.
18 # <https://mercurial-scm.org/wiki/KeywordPlan>.
19 #
19 #
20 # Keyword expansion is based on Mercurial's changeset template mappings.
20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 #
21 #
22 # Binary files are not touched.
22 # Binary files are not touched.
23 #
23 #
24 # Files to act upon/ignore are specified in the [keyword] section.
24 # Files to act upon/ignore are specified in the [keyword] section.
25 # Customized keyword template mappings in the [keywordmaps] section.
25 # Customized keyword template mappings in the [keywordmaps] section.
26 #
26 #
27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
28
28
29 '''expand keywords in tracked files
29 '''expand keywords in tracked files
30
30
31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
32 tracked text files selected by your configuration.
32 tracked text files selected by your configuration.
33
33
34 Keywords are only expanded in local repositories and not stored in the
34 Keywords are only expanded in local repositories and not stored in the
35 change history. The mechanism can be regarded as a convenience for the
35 change history. The mechanism can be regarded as a convenience for the
36 current user or for archive distribution.
36 current user or for archive distribution.
37
37
38 Keywords expand to the changeset data pertaining to the latest change
38 Keywords expand to the changeset data pertaining to the latest change
39 relative to the working directory parent of each file.
39 relative to the working directory parent of each file.
40
40
41 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
41 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
42 sections of hgrc files.
42 sections of hgrc files.
43
43
44 Example::
44 Example::
45
45
46 [keyword]
46 [keyword]
47 # expand keywords in every python file except those matching "x*"
47 # expand keywords in every python file except those matching "x*"
48 **.py =
48 **.py =
49 x* = ignore
49 x* = ignore
50
50
51 [keywordset]
51 [keywordset]
52 # prefer svn- over cvs-like default keywordmaps
52 # prefer svn- over cvs-like default keywordmaps
53 svn = True
53 svn = True
54
54
55 .. note::
55 .. note::
56
56
57 The more specific you are in your filename patterns the less you
57 The more specific you are in your filename patterns the less you
58 lose speed in huge repositories.
58 lose speed in huge repositories.
59
59
60 For [keywordmaps] template mapping and expansion demonstration and
60 For [keywordmaps] template mapping and expansion demonstration and
61 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
61 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
62 available templates and filters.
62 available templates and filters.
63
63
64 Three additional date template filters are provided:
64 Three additional date template filters are provided:
65
65
66 :``utcdate``: "2006/09/18 15:13:13"
66 :``utcdate``: "2006/09/18 15:13:13"
67 :``svnutcdate``: "2006-09-18 15:13:13Z"
67 :``svnutcdate``: "2006-09-18 15:13:13Z"
68 :``svnisodate``: "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
68 :``svnisodate``: "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
69
69
70 The default template mappings (view with :hg:`kwdemo -d`) can be
70 The default template mappings (view with :hg:`kwdemo -d`) can be
71 replaced with customized keywords and templates. Again, run
71 replaced with customized keywords and templates. Again, run
72 :hg:`kwdemo` to control the results of your configuration changes.
72 :hg:`kwdemo` to control the results of your configuration changes.
73
73
74 Before changing/disabling active keywords, you must run :hg:`kwshrink`
74 Before changing/disabling active keywords, you must run :hg:`kwshrink`
75 to avoid storing expanded keywords in the change history.
75 to avoid storing expanded keywords in the change history.
76
76
77 To force expansion after enabling it, or a configuration change, run
77 To force expansion after enabling it, or a configuration change, run
78 :hg:`kwexpand`.
78 :hg:`kwexpand`.
79
79
80 Expansions spanning more than one line and incremental expansions,
80 Expansions spanning more than one line and incremental expansions,
81 like CVS' $Log$, are not supported. A keyword template map "Log =
81 like CVS' $Log$, are not supported. A keyword template map "Log =
82 {desc}" expands to the first line of the changeset description.
82 {desc}" expands to the first line of the changeset description.
83 '''
83 '''
84
84
85
85
86 from __future__ import absolute_import
86 from __future__ import absolute_import
87
87
88 import os
88 import os
89 import re
89 import re
90 import tempfile
90 import tempfile
91
91
92 from mercurial.i18n import _
92 from mercurial.i18n import _
93 from mercurial.hgweb import webcommands
93 from mercurial.hgweb import webcommands
94
94
95 from mercurial import (
95 from mercurial import (
96 cmdutil,
96 cmdutil,
97 commands,
97 commands,
98 context,
98 context,
99 dispatch,
99 dispatch,
100 error,
100 error,
101 extensions,
101 extensions,
102 filelog,
102 filelog,
103 localrepo,
103 localrepo,
104 match,
104 match,
105 patch,
105 patch,
106 pathutil,
106 pathutil,
107 registrar,
107 registrar,
108 scmutil,
108 scmutil,
109 templatefilters,
109 templatefilters,
110 util,
110 util,
111 )
111 )
112
112
113 cmdtable = {}
113 cmdtable = {}
114 command = cmdutil.command(cmdtable)
114 command = cmdutil.command(cmdtable)
115 # Note for extension authors: ONLY specify testedwith = 'internal' for
115 # Note for extension authors: ONLY specify testedwith = 'internal' for
116 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
116 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
117 # be specifying the version(s) of Mercurial they are tested with, or
117 # be specifying the version(s) of Mercurial they are tested with, or
118 # leave the attribute unspecified.
118 # leave the attribute unspecified.
119 testedwith = 'internal'
119 testedwith = 'internal'
120
120
121 # hg commands that do not act on keywords
121 # hg commands that do not act on keywords
122 nokwcommands = ('add addremove annotate bundle export grep incoming init log'
122 nokwcommands = ('add addremove annotate bundle export grep incoming init log'
123 ' outgoing push tip verify convert email glog')
123 ' outgoing push tip verify convert email glog')
124
124
125 # hg commands that trigger expansion only when writing to working dir,
125 # hg commands that trigger expansion only when writing to working dir,
126 # not when reading filelog, and unexpand when reading from working dir
126 # not when reading filelog, and unexpand when reading from working dir
127 restricted = ('merge kwexpand kwshrink record qrecord resolve transplant'
127 restricted = ('merge kwexpand kwshrink record qrecord resolve transplant'
128 ' unshelve rebase graft backout histedit fetch')
128 ' unshelve rebase graft backout histedit fetch')
129
129
130 # names of extensions using dorecord
130 # names of extensions using dorecord
131 recordextensions = 'record'
131 recordextensions = 'record'
132
132
133 colortable = {
133 colortable = {
134 'kwfiles.enabled': 'green bold',
134 'kwfiles.enabled': 'green bold',
135 'kwfiles.deleted': 'cyan bold underline',
135 'kwfiles.deleted': 'cyan bold underline',
136 'kwfiles.enabledunknown': 'green',
136 'kwfiles.enabledunknown': 'green',
137 'kwfiles.ignored': 'bold',
137 'kwfiles.ignored': 'bold',
138 'kwfiles.ignoredunknown': 'none'
138 'kwfiles.ignoredunknown': 'none'
139 }
139 }
140
140
141 templatefilter = registrar.templatefilter()
141 templatefilter = registrar.templatefilter()
142
142
143 # date like in cvs' $Date
143 # date like in cvs' $Date
144 @templatefilter('utcdate')
144 @templatefilter('utcdate')
145 def utcdate(text):
145 def utcdate(text):
146 '''Date. Returns a UTC-date in this format: "2009/08/18 11:00:13".
146 '''Date. Returns a UTC-date in this format: "2009/08/18 11:00:13".
147 '''
147 '''
148 return util.datestr((util.parsedate(text)[0], 0), '%Y/%m/%d %H:%M:%S')
148 return util.datestr((util.parsedate(text)[0], 0), '%Y/%m/%d %H:%M:%S')
149 # date like in svn's $Date
149 # date like in svn's $Date
150 @templatefilter('svnisodate')
150 @templatefilter('svnisodate')
151 def svnisodate(text):
151 def svnisodate(text):
152 '''Date. Returns a date in this format: "2009-08-18 13:00:13
152 '''Date. Returns a date in this format: "2009-08-18 13:00:13
153 +0200 (Tue, 18 Aug 2009)".
153 +0200 (Tue, 18 Aug 2009)".
154 '''
154 '''
155 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
155 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
156 # date like in svn's $Id
156 # date like in svn's $Id
157 @templatefilter('svnutcdate')
157 @templatefilter('svnutcdate')
158 def svnutcdate(text):
158 def svnutcdate(text):
159 '''Date. Returns a UTC-date in this format: "2009-08-18
159 '''Date. Returns a UTC-date in this format: "2009-08-18
160 11:00:13Z".
160 11:00:13Z".
161 '''
161 '''
162 return util.datestr((util.parsedate(text)[0], 0), '%Y-%m-%d %H:%M:%SZ')
162 return util.datestr((util.parsedate(text)[0], 0), '%Y-%m-%d %H:%M:%SZ')
163
163
164 # make keyword tools accessible
164 # make keyword tools accessible
165 kwtools = {'templater': None, 'hgcmd': ''}
165 kwtools = {'templater': None, 'hgcmd': ''}
166
166
167 def _defaultkwmaps(ui):
167 def _defaultkwmaps(ui):
168 '''Returns default keywordmaps according to keywordset configuration.'''
168 '''Returns default keywordmaps according to keywordset configuration.'''
169 templates = {
169 templates = {
170 'Revision': '{node|short}',
170 'Revision': '{node|short}',
171 'Author': '{author|user}',
171 'Author': '{author|user}',
172 }
172 }
173 kwsets = ({
173 kwsets = ({
174 'Date': '{date|utcdate}',
174 'Date': '{date|utcdate}',
175 'RCSfile': '{file|basename},v',
175 'RCSfile': '{file|basename},v',
176 'RCSFile': '{file|basename},v', # kept for backwards compatibility
176 'RCSFile': '{file|basename},v', # kept for backwards compatibility
177 # with hg-keyword
177 # with hg-keyword
178 'Source': '{root}/{file},v',
178 'Source': '{root}/{file},v',
179 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
179 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
180 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
180 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
181 }, {
181 }, {
182 'Date': '{date|svnisodate}',
182 'Date': '{date|svnisodate}',
183 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
183 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
184 'LastChangedRevision': '{node|short}',
184 'LastChangedRevision': '{node|short}',
185 'LastChangedBy': '{author|user}',
185 'LastChangedBy': '{author|user}',
186 'LastChangedDate': '{date|svnisodate}',
186 'LastChangedDate': '{date|svnisodate}',
187 })
187 })
188 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
188 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
189 return templates
189 return templates
190
190
191 def _shrinktext(text, subfunc):
191 def _shrinktext(text, subfunc):
192 '''Helper for keyword expansion removal in text.
192 '''Helper for keyword expansion removal in text.
193 Depending on subfunc also returns number of substitutions.'''
193 Depending on subfunc also returns number of substitutions.'''
194 return subfunc(r'$\1$', text)
194 return subfunc(r'$\1$', text)
195
195
196 def _preselect(wstatus, changed):
196 def _preselect(wstatus, changed):
197 '''Retrieves modified and added files from a working directory state
197 '''Retrieves modified and added files from a working directory state
198 and returns the subset of each contained in given changed files
198 and returns the subset of each contained in given changed files
199 retrieved from a change context.'''
199 retrieved from a change context.'''
200 modified = [f for f in wstatus.modified if f in changed]
200 modified = [f for f in wstatus.modified if f in changed]
201 added = [f for f in wstatus.added if f in changed]
201 added = [f for f in wstatus.added if f in changed]
202 return modified, added
202 return modified, added
203
203
204
204
205 class kwtemplater(object):
205 class kwtemplater(object):
206 '''
206 '''
207 Sets up keyword templates, corresponding keyword regex, and
207 Sets up keyword templates, corresponding keyword regex, and
208 provides keyword substitution functions.
208 provides keyword substitution functions.
209 '''
209 '''
210
210
211 def __init__(self, ui, repo, inc, exc):
211 def __init__(self, ui, repo, inc, exc):
212 self.ui = ui
212 self.ui = ui
213 self.repo = repo
213 self.repo = repo
214 self.match = match.match(repo.root, '', [], inc, exc)
214 self.match = match.match(repo.root, '', [], inc, exc)
215 self.restrict = kwtools['hgcmd'] in restricted.split()
215 self.restrict = kwtools['hgcmd'] in restricted.split()
216 self.postcommit = False
216 self.postcommit = False
217
217
218 kwmaps = self.ui.configitems('keywordmaps')
218 kwmaps = self.ui.configitems('keywordmaps')
219 if kwmaps: # override default templates
219 if kwmaps: # override default templates
220 self.templates = dict(kwmaps)
220 self.templates = dict(kwmaps)
221 else:
221 else:
222 self.templates = _defaultkwmaps(self.ui)
222 self.templates = _defaultkwmaps(self.ui)
223
223
224 @util.propertycache
224 @util.propertycache
225 def escape(self):
225 def escape(self):
226 '''Returns bar-separated and escaped keywords.'''
226 '''Returns bar-separated and escaped keywords.'''
227 return '|'.join(map(re.escape, self.templates.keys()))
227 return '|'.join(map(re.escape, self.templates.keys()))
228
228
229 @util.propertycache
229 @util.propertycache
230 def rekw(self):
230 def rekw(self):
231 '''Returns regex for unexpanded keywords.'''
231 '''Returns regex for unexpanded keywords.'''
232 return re.compile(r'\$(%s)\$' % self.escape)
232 return re.compile(r'\$(%s)\$' % self.escape)
233
233
234 @util.propertycache
234 @util.propertycache
235 def rekwexp(self):
235 def rekwexp(self):
236 '''Returns regex for expanded keywords.'''
236 '''Returns regex for expanded keywords.'''
237 return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
237 return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
238
238
239 def substitute(self, data, path, ctx, subfunc):
239 def substitute(self, data, path, ctx, subfunc):
240 '''Replaces keywords in data with expanded template.'''
240 '''Replaces keywords in data with expanded template.'''
241 def kwsub(mobj):
241 def kwsub(mobj):
242 kw = mobj.group(1)
242 kw = mobj.group(1)
243 ct = cmdutil.changeset_templater(self.ui, self.repo, False, None,
243 ct = cmdutil.changeset_templater(self.ui, self.repo, False, None,
244 self.templates[kw], '', False)
244 self.templates[kw], '', False)
245 self.ui.pushbuffer()
245 self.ui.pushbuffer()
246 ct.show(ctx, root=self.repo.root, file=path)
246 ct.show(ctx, root=self.repo.root, file=path)
247 ekw = templatefilters.firstline(self.ui.popbuffer())
247 ekw = templatefilters.firstline(self.ui.popbuffer())
248 return '$%s: %s $' % (kw, ekw)
248 return '$%s: %s $' % (kw, ekw)
249 return subfunc(kwsub, data)
249 return subfunc(kwsub, data)
250
250
251 def linkctx(self, path, fileid):
251 def linkctx(self, path, fileid):
252 '''Similar to filelog.linkrev, but returns a changectx.'''
252 '''Similar to filelog.linkrev, but returns a changectx.'''
253 return self.repo.filectx(path, fileid=fileid).changectx()
253 return self.repo.filectx(path, fileid=fileid).changectx()
254
254
255 def expand(self, path, node, data):
255 def expand(self, path, node, data):
256 '''Returns data with keywords expanded.'''
256 '''Returns data with keywords expanded.'''
257 if not self.restrict and self.match(path) and not util.binary(data):
257 if not self.restrict and self.match(path) and not util.binary(data):
258 ctx = self.linkctx(path, node)
258 ctx = self.linkctx(path, node)
259 return self.substitute(data, path, ctx, self.rekw.sub)
259 return self.substitute(data, path, ctx, self.rekw.sub)
260 return data
260 return data
261
261
262 def iskwfile(self, cand, ctx):
262 def iskwfile(self, cand, ctx):
263 '''Returns subset of candidates which are configured for keyword
263 '''Returns subset of candidates which are configured for keyword
264 expansion but are not symbolic links.'''
264 expansion but are not symbolic links.'''
265 return [f for f in cand if self.match(f) and 'l' not in ctx.flags(f)]
265 return [f for f in cand if self.match(f) and 'l' not in ctx.flags(f)]
266
266
267 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
267 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
268 '''Overwrites selected files expanding/shrinking keywords.'''
268 '''Overwrites selected files expanding/shrinking keywords.'''
269 if self.restrict or lookup or self.postcommit: # exclude kw_copy
269 if self.restrict or lookup or self.postcommit: # exclude kw_copy
270 candidates = self.iskwfile(candidates, ctx)
270 candidates = self.iskwfile(candidates, ctx)
271 if not candidates:
271 if not candidates:
272 return
272 return
273 kwcmd = self.restrict and lookup # kwexpand/kwshrink
273 kwcmd = self.restrict and lookup # kwexpand/kwshrink
274 if self.restrict or expand and lookup:
274 if self.restrict or expand and lookup:
275 mf = ctx.manifest()
275 mf = ctx.manifest()
276 if self.restrict or rekw:
276 if self.restrict or rekw:
277 re_kw = self.rekw
277 re_kw = self.rekw
278 else:
278 else:
279 re_kw = self.rekwexp
279 re_kw = self.rekwexp
280 if expand:
280 if expand:
281 msg = _('overwriting %s expanding keywords\n')
281 msg = _('overwriting %s expanding keywords\n')
282 else:
282 else:
283 msg = _('overwriting %s shrinking keywords\n')
283 msg = _('overwriting %s shrinking keywords\n')
284 for f in candidates:
284 for f in candidates:
285 if self.restrict:
285 if self.restrict:
286 data = self.repo.file(f).read(mf[f])
286 data = self.repo.file(f).read(mf[f])
287 else:
287 else:
288 data = self.repo.wread(f)
288 data = self.repo.wread(f)
289 if util.binary(data):
289 if util.binary(data):
290 continue
290 continue
291 if expand:
291 if expand:
292 parents = ctx.parents()
292 parents = ctx.parents()
293 if lookup:
293 if lookup:
294 ctx = self.linkctx(f, mf[f])
294 ctx = self.linkctx(f, mf[f])
295 elif self.restrict and len(parents) > 1:
295 elif self.restrict and len(parents) > 1:
296 # merge commit
296 # merge commit
297 # in case of conflict f is in modified state during
297 # in case of conflict f is in modified state during
298 # merge, even if f does not differ from f in parent
298 # merge, even if f does not differ from f in parent
299 for p in parents:
299 for p in parents:
300 if f in p and not p[f].cmp(ctx[f]):
300 if f in p and not p[f].cmp(ctx[f]):
301 ctx = p[f].changectx()
301 ctx = p[f].changectx()
302 break
302 break
303 data, found = self.substitute(data, f, ctx, re_kw.subn)
303 data, found = self.substitute(data, f, ctx, re_kw.subn)
304 elif self.restrict:
304 elif self.restrict:
305 found = re_kw.search(data)
305 found = re_kw.search(data)
306 else:
306 else:
307 data, found = _shrinktext(data, re_kw.subn)
307 data, found = _shrinktext(data, re_kw.subn)
308 if found:
308 if found:
309 self.ui.note(msg % f)
309 self.ui.note(msg % f)
310 fp = self.repo.wvfs(f, "wb", atomictemp=True)
310 fp = self.repo.wvfs(f, "wb", atomictemp=True)
311 fp.write(data)
311 fp.write(data)
312 fp.close()
312 fp.close()
313 if kwcmd:
313 if kwcmd:
314 self.repo.dirstate.normal(f)
314 self.repo.dirstate.normal(f)
315 elif self.postcommit:
315 elif self.postcommit:
316 self.repo.dirstate.normallookup(f)
316 self.repo.dirstate.normallookup(f)
317
317
318 def shrink(self, fname, text):
318 def shrink(self, fname, text):
319 '''Returns text with all keyword substitutions removed.'''
319 '''Returns text with all keyword substitutions removed.'''
320 if self.match(fname) and not util.binary(text):
320 if self.match(fname) and not util.binary(text):
321 return _shrinktext(text, self.rekwexp.sub)
321 return _shrinktext(text, self.rekwexp.sub)
322 return text
322 return text
323
323
324 def shrinklines(self, fname, lines):
324 def shrinklines(self, fname, lines):
325 '''Returns lines with keyword substitutions removed.'''
325 '''Returns lines with keyword substitutions removed.'''
326 if self.match(fname):
326 if self.match(fname):
327 text = ''.join(lines)
327 text = ''.join(lines)
328 if not util.binary(text):
328 if not util.binary(text):
329 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
329 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
330 return lines
330 return lines
331
331
332 def wread(self, fname, data):
332 def wread(self, fname, data):
333 '''If in restricted mode returns data read from wdir with
333 '''If in restricted mode returns data read from wdir with
334 keyword substitutions removed.'''
334 keyword substitutions removed.'''
335 if self.restrict:
335 if self.restrict:
336 return self.shrink(fname, data)
336 return self.shrink(fname, data)
337 return data
337 return data
338
338
339 class kwfilelog(filelog.filelog):
339 class kwfilelog(filelog.filelog):
340 '''
340 '''
341 Subclass of filelog to hook into its read, add, cmp methods.
341 Subclass of filelog to hook into its read, add, cmp methods.
342 Keywords are "stored" unexpanded, and processed on reading.
342 Keywords are "stored" unexpanded, and processed on reading.
343 '''
343 '''
344 def __init__(self, opener, kwt, path):
344 def __init__(self, opener, kwt, path):
345 super(kwfilelog, self).__init__(opener, path)
345 super(kwfilelog, self).__init__(opener, path)
346 self.kwt = kwt
346 self.kwt = kwt
347 self.path = path
347 self.path = path
348
348
349 def read(self, node):
349 def read(self, node):
350 '''Expands keywords when reading filelog.'''
350 '''Expands keywords when reading filelog.'''
351 data = super(kwfilelog, self).read(node)
351 data = super(kwfilelog, self).read(node)
352 if self.renamed(node):
352 if self.renamed(node):
353 return data
353 return data
354 return self.kwt.expand(self.path, node, data)
354 return self.kwt.expand(self.path, node, data)
355
355
356 def add(self, text, meta, tr, link, p1=None, p2=None):
356 def add(self, text, meta, tr, link, p1=None, p2=None):
357 '''Removes keyword substitutions when adding to filelog.'''
357 '''Removes keyword substitutions when adding to filelog.'''
358 text = self.kwt.shrink(self.path, text)
358 text = self.kwt.shrink(self.path, text)
359 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
359 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
360
360
361 def cmp(self, node, text):
361 def cmp(self, node, text):
362 '''Removes keyword substitutions for comparison.'''
362 '''Removes keyword substitutions for comparison.'''
363 text = self.kwt.shrink(self.path, text)
363 text = self.kwt.shrink(self.path, text)
364 return super(kwfilelog, self).cmp(node, text)
364 return super(kwfilelog, self).cmp(node, text)
365
365
366 def _status(ui, repo, wctx, kwt, *pats, **opts):
366 def _status(ui, repo, wctx, kwt, *pats, **opts):
367 '''Bails out if [keyword] configuration is not active.
367 '''Bails out if [keyword] configuration is not active.
368 Returns status of working directory.'''
368 Returns status of working directory.'''
369 if kwt:
369 if kwt:
370 return repo.status(match=scmutil.match(wctx, pats, opts), clean=True,
370 return repo.status(match=scmutil.match(wctx, pats, opts), clean=True,
371 unknown=opts.get('unknown') or opts.get('all'))
371 unknown=opts.get('unknown') or opts.get('all'))
372 if ui.configitems('keyword'):
372 if ui.configitems('keyword'):
373 raise error.Abort(_('[keyword] patterns cannot match'))
373 raise error.Abort(_('[keyword] patterns cannot match'))
374 raise error.Abort(_('no [keyword] patterns configured'))
374 raise error.Abort(_('no [keyword] patterns configured'))
375
375
376 def _kwfwrite(ui, repo, expand, *pats, **opts):
376 def _kwfwrite(ui, repo, expand, *pats, **opts):
377 '''Selects files and passes them to kwtemplater.overwrite.'''
377 '''Selects files and passes them to kwtemplater.overwrite.'''
378 wctx = repo[None]
378 wctx = repo[None]
379 if len(wctx.parents()) > 1:
379 if len(wctx.parents()) > 1:
380 raise error.Abort(_('outstanding uncommitted merge'))
380 raise error.Abort(_('outstanding uncommitted merge'))
381 kwt = kwtools['templater']
381 kwt = kwtools['templater']
382 with repo.wlock():
382 with repo.wlock():
383 status = _status(ui, repo, wctx, kwt, *pats, **opts)
383 status = _status(ui, repo, wctx, kwt, *pats, **opts)
384 if status.modified or status.added or status.removed or status.deleted:
384 if status.modified or status.added or status.removed or status.deleted:
385 raise error.Abort(_('outstanding uncommitted changes'))
385 raise error.Abort(_('outstanding uncommitted changes'))
386 kwt.overwrite(wctx, status.clean, True, expand)
386 kwt.overwrite(wctx, status.clean, True, expand)
387
387
388 @command('kwdemo',
388 @command('kwdemo',
389 [('d', 'default', None, _('show default keyword template maps')),
389 [('d', 'default', None, _('show default keyword template maps')),
390 ('f', 'rcfile', '',
390 ('f', 'rcfile', '',
391 _('read maps from rcfile'), _('FILE'))],
391 _('read maps from rcfile'), _('FILE'))],
392 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'),
392 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'),
393 optionalrepo=True)
393 optionalrepo=True)
394 def demo(ui, repo, *args, **opts):
394 def demo(ui, repo, *args, **opts):
395 '''print [keywordmaps] configuration and an expansion example
395 '''print [keywordmaps] configuration and an expansion example
396
396
397 Show current, custom, or default keyword template maps and their
397 Show current, custom, or default keyword template maps and their
398 expansions.
398 expansions.
399
399
400 Extend the current configuration by specifying maps as arguments
400 Extend the current configuration by specifying maps as arguments
401 and using -f/--rcfile to source an external hgrc file.
401 and using -f/--rcfile to source an external hgrc file.
402
402
403 Use -d/--default to disable current configuration.
403 Use -d/--default to disable current configuration.
404
404
405 See :hg:`help templates` for information on templates and filters.
405 See :hg:`help templates` for information on templates and filters.
406 '''
406 '''
407 def demoitems(section, items):
407 def demoitems(section, items):
408 ui.write('[%s]\n' % section)
408 ui.write('[%s]\n' % section)
409 for k, v in sorted(items):
409 for k, v in sorted(items):
410 ui.write('%s = %s\n' % (k, v))
410 ui.write('%s = %s\n' % (k, v))
411
411
412 fn = 'demo.txt'
412 fn = 'demo.txt'
413 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
413 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
414 ui.note(_('creating temporary repository at %s\n') % tmpdir)
414 ui.note(_('creating temporary repository at %s\n') % tmpdir)
415 repo = localrepo.localrepository(repo.baseui, tmpdir, True)
415 repo = localrepo.localrepository(repo.baseui, tmpdir, True)
416 ui.setconfig('keyword', fn, '', 'keyword')
416 ui.setconfig('keyword', fn, '', 'keyword')
417 svn = ui.configbool('keywordset', 'svn')
417 svn = ui.configbool('keywordset', 'svn')
418 # explicitly set keywordset for demo output
418 # explicitly set keywordset for demo output
419 ui.setconfig('keywordset', 'svn', svn, 'keyword')
419 ui.setconfig('keywordset', 'svn', svn, 'keyword')
420
420
421 uikwmaps = ui.configitems('keywordmaps')
421 uikwmaps = ui.configitems('keywordmaps')
422 if args or opts.get('rcfile'):
422 if args or opts.get('rcfile'):
423 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
423 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
424 if uikwmaps:
424 if uikwmaps:
425 ui.status(_('\textending current template maps\n'))
425 ui.status(_('\textending current template maps\n'))
426 if opts.get('default') or not uikwmaps:
426 if opts.get('default') or not uikwmaps:
427 if svn:
427 if svn:
428 ui.status(_('\toverriding default svn keywordset\n'))
428 ui.status(_('\toverriding default svn keywordset\n'))
429 else:
429 else:
430 ui.status(_('\toverriding default cvs keywordset\n'))
430 ui.status(_('\toverriding default cvs keywordset\n'))
431 if opts.get('rcfile'):
431 if opts.get('rcfile'):
432 ui.readconfig(opts.get('rcfile'))
432 ui.readconfig(opts.get('rcfile'))
433 if args:
433 if args:
434 # simulate hgrc parsing
434 # simulate hgrc parsing
435 rcmaps = '[keywordmaps]\n%s\n' % '\n'.join(args)
435 rcmaps = '[keywordmaps]\n%s\n' % '\n'.join(args)
436 repo.vfs.write('hgrc', rcmaps)
436 repo.vfs.write('hgrc', rcmaps)
437 ui.readconfig(repo.join('hgrc'))
437 ui.readconfig(repo.join('hgrc'))
438 kwmaps = dict(ui.configitems('keywordmaps'))
438 kwmaps = dict(ui.configitems('keywordmaps'))
439 elif opts.get('default'):
439 elif opts.get('default'):
440 if svn:
440 if svn:
441 ui.status(_('\n\tconfiguration using default svn keywordset\n'))
441 ui.status(_('\n\tconfiguration using default svn keywordset\n'))
442 else:
442 else:
443 ui.status(_('\n\tconfiguration using default cvs keywordset\n'))
443 ui.status(_('\n\tconfiguration using default cvs keywordset\n'))
444 kwmaps = _defaultkwmaps(ui)
444 kwmaps = _defaultkwmaps(ui)
445 if uikwmaps:
445 if uikwmaps:
446 ui.status(_('\tdisabling current template maps\n'))
446 ui.status(_('\tdisabling current template maps\n'))
447 for k, v in kwmaps.iteritems():
447 for k, v in kwmaps.iteritems():
448 ui.setconfig('keywordmaps', k, v, 'keyword')
448 ui.setconfig('keywordmaps', k, v, 'keyword')
449 else:
449 else:
450 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
450 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
451 if uikwmaps:
451 if uikwmaps:
452 kwmaps = dict(uikwmaps)
452 kwmaps = dict(uikwmaps)
453 else:
453 else:
454 kwmaps = _defaultkwmaps(ui)
454 kwmaps = _defaultkwmaps(ui)
455
455
456 uisetup(ui)
456 uisetup(ui)
457 reposetup(ui, repo)
457 reposetup(ui, repo)
458 ui.write('[extensions]\nkeyword =\n')
458 ui.write(('[extensions]\nkeyword =\n'))
459 demoitems('keyword', ui.configitems('keyword'))
459 demoitems('keyword', ui.configitems('keyword'))
460 demoitems('keywordset', ui.configitems('keywordset'))
460 demoitems('keywordset', ui.configitems('keywordset'))
461 demoitems('keywordmaps', kwmaps.iteritems())
461 demoitems('keywordmaps', kwmaps.iteritems())
462 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
462 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
463 repo.wvfs.write(fn, keywords)
463 repo.wvfs.write(fn, keywords)
464 repo[None].add([fn])
464 repo[None].add([fn])
465 ui.note(_('\nkeywords written to %s:\n') % fn)
465 ui.note(_('\nkeywords written to %s:\n') % fn)
466 ui.note(keywords)
466 ui.note(keywords)
467 with repo.wlock():
467 with repo.wlock():
468 repo.dirstate.setbranch('demobranch')
468 repo.dirstate.setbranch('demobranch')
469 for name, cmd in ui.configitems('hooks'):
469 for name, cmd in ui.configitems('hooks'):
470 if name.split('.', 1)[0].find('commit') > -1:
470 if name.split('.', 1)[0].find('commit') > -1:
471 repo.ui.setconfig('hooks', name, '', 'keyword')
471 repo.ui.setconfig('hooks', name, '', 'keyword')
472 msg = _('hg keyword configuration and expansion example')
472 msg = _('hg keyword configuration and expansion example')
473 ui.note(("hg ci -m '%s'\n" % msg))
473 ui.note(("hg ci -m '%s'\n" % msg))
474 repo.commit(text=msg)
474 repo.commit(text=msg)
475 ui.status(_('\n\tkeywords expanded\n'))
475 ui.status(_('\n\tkeywords expanded\n'))
476 ui.write(repo.wread(fn))
476 ui.write(repo.wread(fn))
477 repo.wvfs.rmtree(repo.root)
477 repo.wvfs.rmtree(repo.root)
478
478
479 @command('kwexpand',
479 @command('kwexpand',
480 commands.walkopts,
480 commands.walkopts,
481 _('hg kwexpand [OPTION]... [FILE]...'),
481 _('hg kwexpand [OPTION]... [FILE]...'),
482 inferrepo=True)
482 inferrepo=True)
483 def expand(ui, repo, *pats, **opts):
483 def expand(ui, repo, *pats, **opts):
484 '''expand keywords in the working directory
484 '''expand keywords in the working directory
485
485
486 Run after (re)enabling keyword expansion.
486 Run after (re)enabling keyword expansion.
487
487
488 kwexpand refuses to run if given files contain local changes.
488 kwexpand refuses to run if given files contain local changes.
489 '''
489 '''
490 # 3rd argument sets expansion to True
490 # 3rd argument sets expansion to True
491 _kwfwrite(ui, repo, True, *pats, **opts)
491 _kwfwrite(ui, repo, True, *pats, **opts)
492
492
493 @command('kwfiles',
493 @command('kwfiles',
494 [('A', 'all', None, _('show keyword status flags of all files')),
494 [('A', 'all', None, _('show keyword status flags of all files')),
495 ('i', 'ignore', None, _('show files excluded from expansion')),
495 ('i', 'ignore', None, _('show files excluded from expansion')),
496 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
496 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
497 ] + commands.walkopts,
497 ] + commands.walkopts,
498 _('hg kwfiles [OPTION]... [FILE]...'),
498 _('hg kwfiles [OPTION]... [FILE]...'),
499 inferrepo=True)
499 inferrepo=True)
500 def files(ui, repo, *pats, **opts):
500 def files(ui, repo, *pats, **opts):
501 '''show files configured for keyword expansion
501 '''show files configured for keyword expansion
502
502
503 List which files in the working directory are matched by the
503 List which files in the working directory are matched by the
504 [keyword] configuration patterns.
504 [keyword] configuration patterns.
505
505
506 Useful to prevent inadvertent keyword expansion and to speed up
506 Useful to prevent inadvertent keyword expansion and to speed up
507 execution by including only files that are actual candidates for
507 execution by including only files that are actual candidates for
508 expansion.
508 expansion.
509
509
510 See :hg:`help keyword` on how to construct patterns both for
510 See :hg:`help keyword` on how to construct patterns both for
511 inclusion and exclusion of files.
511 inclusion and exclusion of files.
512
512
513 With -A/--all and -v/--verbose the codes used to show the status
513 With -A/--all and -v/--verbose the codes used to show the status
514 of files are::
514 of files are::
515
515
516 K = keyword expansion candidate
516 K = keyword expansion candidate
517 k = keyword expansion candidate (not tracked)
517 k = keyword expansion candidate (not tracked)
518 I = ignored
518 I = ignored
519 i = ignored (not tracked)
519 i = ignored (not tracked)
520 '''
520 '''
521 kwt = kwtools['templater']
521 kwt = kwtools['templater']
522 wctx = repo[None]
522 wctx = repo[None]
523 status = _status(ui, repo, wctx, kwt, *pats, **opts)
523 status = _status(ui, repo, wctx, kwt, *pats, **opts)
524 if pats:
524 if pats:
525 cwd = repo.getcwd()
525 cwd = repo.getcwd()
526 else:
526 else:
527 cwd = ''
527 cwd = ''
528 files = []
528 files = []
529 if not opts.get('unknown') or opts.get('all'):
529 if not opts.get('unknown') or opts.get('all'):
530 files = sorted(status.modified + status.added + status.clean)
530 files = sorted(status.modified + status.added + status.clean)
531 kwfiles = kwt.iskwfile(files, wctx)
531 kwfiles = kwt.iskwfile(files, wctx)
532 kwdeleted = kwt.iskwfile(status.deleted, wctx)
532 kwdeleted = kwt.iskwfile(status.deleted, wctx)
533 kwunknown = kwt.iskwfile(status.unknown, wctx)
533 kwunknown = kwt.iskwfile(status.unknown, wctx)
534 if not opts.get('ignore') or opts.get('all'):
534 if not opts.get('ignore') or opts.get('all'):
535 showfiles = kwfiles, kwdeleted, kwunknown
535 showfiles = kwfiles, kwdeleted, kwunknown
536 else:
536 else:
537 showfiles = [], [], []
537 showfiles = [], [], []
538 if opts.get('all') or opts.get('ignore'):
538 if opts.get('all') or opts.get('ignore'):
539 showfiles += ([f for f in files if f not in kwfiles],
539 showfiles += ([f for f in files if f not in kwfiles],
540 [f for f in status.unknown if f not in kwunknown])
540 [f for f in status.unknown if f not in kwunknown])
541 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
541 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
542 kwstates = zip(kwlabels, 'K!kIi', showfiles)
542 kwstates = zip(kwlabels, 'K!kIi', showfiles)
543 fm = ui.formatter('kwfiles', opts)
543 fm = ui.formatter('kwfiles', opts)
544 fmt = '%.0s%s\n'
544 fmt = '%.0s%s\n'
545 if opts.get('all') or ui.verbose:
545 if opts.get('all') or ui.verbose:
546 fmt = '%s %s\n'
546 fmt = '%s %s\n'
547 for kwstate, char, filenames in kwstates:
547 for kwstate, char, filenames in kwstates:
548 label = 'kwfiles.' + kwstate
548 label = 'kwfiles.' + kwstate
549 for f in filenames:
549 for f in filenames:
550 fm.startitem()
550 fm.startitem()
551 fm.write('kwstatus path', fmt, char,
551 fm.write('kwstatus path', fmt, char,
552 repo.pathto(f, cwd), label=label)
552 repo.pathto(f, cwd), label=label)
553 fm.end()
553 fm.end()
554
554
555 @command('kwshrink',
555 @command('kwshrink',
556 commands.walkopts,
556 commands.walkopts,
557 _('hg kwshrink [OPTION]... [FILE]...'),
557 _('hg kwshrink [OPTION]... [FILE]...'),
558 inferrepo=True)
558 inferrepo=True)
559 def shrink(ui, repo, *pats, **opts):
559 def shrink(ui, repo, *pats, **opts):
560 '''revert expanded keywords in the working directory
560 '''revert expanded keywords in the working directory
561
561
562 Must be run before changing/disabling active keywords.
562 Must be run before changing/disabling active keywords.
563
563
564 kwshrink refuses to run if given files contain local changes.
564 kwshrink refuses to run if given files contain local changes.
565 '''
565 '''
566 # 3rd argument sets expansion to False
566 # 3rd argument sets expansion to False
567 _kwfwrite(ui, repo, False, *pats, **opts)
567 _kwfwrite(ui, repo, False, *pats, **opts)
568
568
569
569
570 def uisetup(ui):
570 def uisetup(ui):
571 ''' Monkeypatches dispatch._parse to retrieve user command.'''
571 ''' Monkeypatches dispatch._parse to retrieve user command.'''
572
572
573 def kwdispatch_parse(orig, ui, args):
573 def kwdispatch_parse(orig, ui, args):
574 '''Monkeypatch dispatch._parse to obtain running hg command.'''
574 '''Monkeypatch dispatch._parse to obtain running hg command.'''
575 cmd, func, args, options, cmdoptions = orig(ui, args)
575 cmd, func, args, options, cmdoptions = orig(ui, args)
576 kwtools['hgcmd'] = cmd
576 kwtools['hgcmd'] = cmd
577 return cmd, func, args, options, cmdoptions
577 return cmd, func, args, options, cmdoptions
578
578
579 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
579 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
580
580
581 def reposetup(ui, repo):
581 def reposetup(ui, repo):
582 '''Sets up repo as kwrepo for keyword substitution.
582 '''Sets up repo as kwrepo for keyword substitution.
583 Overrides file method to return kwfilelog instead of filelog
583 Overrides file method to return kwfilelog instead of filelog
584 if file matches user configuration.
584 if file matches user configuration.
585 Wraps commit to overwrite configured files with updated
585 Wraps commit to overwrite configured files with updated
586 keyword substitutions.
586 keyword substitutions.
587 Monkeypatches patch and webcommands.'''
587 Monkeypatches patch and webcommands.'''
588
588
589 try:
589 try:
590 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
590 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
591 or '.hg' in util.splitpath(repo.root)
591 or '.hg' in util.splitpath(repo.root)
592 or repo._url.startswith('bundle:')):
592 or repo._url.startswith('bundle:')):
593 return
593 return
594 except AttributeError:
594 except AttributeError:
595 pass
595 pass
596
596
597 inc, exc = [], ['.hg*']
597 inc, exc = [], ['.hg*']
598 for pat, opt in ui.configitems('keyword'):
598 for pat, opt in ui.configitems('keyword'):
599 if opt != 'ignore':
599 if opt != 'ignore':
600 inc.append(pat)
600 inc.append(pat)
601 else:
601 else:
602 exc.append(pat)
602 exc.append(pat)
603 if not inc:
603 if not inc:
604 return
604 return
605
605
606 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
606 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
607
607
608 class kwrepo(repo.__class__):
608 class kwrepo(repo.__class__):
609 def file(self, f):
609 def file(self, f):
610 if f[0] == '/':
610 if f[0] == '/':
611 f = f[1:]
611 f = f[1:]
612 return kwfilelog(self.svfs, kwt, f)
612 return kwfilelog(self.svfs, kwt, f)
613
613
614 def wread(self, filename):
614 def wread(self, filename):
615 data = super(kwrepo, self).wread(filename)
615 data = super(kwrepo, self).wread(filename)
616 return kwt.wread(filename, data)
616 return kwt.wread(filename, data)
617
617
618 def commit(self, *args, **opts):
618 def commit(self, *args, **opts):
619 # use custom commitctx for user commands
619 # use custom commitctx for user commands
620 # other extensions can still wrap repo.commitctx directly
620 # other extensions can still wrap repo.commitctx directly
621 self.commitctx = self.kwcommitctx
621 self.commitctx = self.kwcommitctx
622 try:
622 try:
623 return super(kwrepo, self).commit(*args, **opts)
623 return super(kwrepo, self).commit(*args, **opts)
624 finally:
624 finally:
625 del self.commitctx
625 del self.commitctx
626
626
627 def kwcommitctx(self, ctx, error=False):
627 def kwcommitctx(self, ctx, error=False):
628 n = super(kwrepo, self).commitctx(ctx, error)
628 n = super(kwrepo, self).commitctx(ctx, error)
629 # no lock needed, only called from repo.commit() which already locks
629 # no lock needed, only called from repo.commit() which already locks
630 if not kwt.postcommit:
630 if not kwt.postcommit:
631 restrict = kwt.restrict
631 restrict = kwt.restrict
632 kwt.restrict = True
632 kwt.restrict = True
633 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
633 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
634 False, True)
634 False, True)
635 kwt.restrict = restrict
635 kwt.restrict = restrict
636 return n
636 return n
637
637
638 def rollback(self, dryrun=False, force=False):
638 def rollback(self, dryrun=False, force=False):
639 wlock = self.wlock()
639 wlock = self.wlock()
640 origrestrict = kwt.restrict
640 origrestrict = kwt.restrict
641 try:
641 try:
642 if not dryrun:
642 if not dryrun:
643 changed = self['.'].files()
643 changed = self['.'].files()
644 ret = super(kwrepo, self).rollback(dryrun, force)
644 ret = super(kwrepo, self).rollback(dryrun, force)
645 if not dryrun:
645 if not dryrun:
646 ctx = self['.']
646 ctx = self['.']
647 modified, added = _preselect(ctx.status(), changed)
647 modified, added = _preselect(ctx.status(), changed)
648 kwt.restrict = False
648 kwt.restrict = False
649 kwt.overwrite(ctx, modified, True, True)
649 kwt.overwrite(ctx, modified, True, True)
650 kwt.overwrite(ctx, added, True, False)
650 kwt.overwrite(ctx, added, True, False)
651 return ret
651 return ret
652 finally:
652 finally:
653 kwt.restrict = origrestrict
653 kwt.restrict = origrestrict
654 wlock.release()
654 wlock.release()
655
655
656 # monkeypatches
656 # monkeypatches
657 def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None):
657 def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None):
658 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
658 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
659 rejects or conflicts due to expanded keywords in working dir.'''
659 rejects or conflicts due to expanded keywords in working dir.'''
660 orig(self, ui, gp, backend, store, eolmode)
660 orig(self, ui, gp, backend, store, eolmode)
661 # shrink keywords read from working dir
661 # shrink keywords read from working dir
662 self.lines = kwt.shrinklines(self.fname, self.lines)
662 self.lines = kwt.shrinklines(self.fname, self.lines)
663
663
664 def kwdiff(orig, *args, **kwargs):
664 def kwdiff(orig, *args, **kwargs):
665 '''Monkeypatch patch.diff to avoid expansion.'''
665 '''Monkeypatch patch.diff to avoid expansion.'''
666 kwt.restrict = True
666 kwt.restrict = True
667 return orig(*args, **kwargs)
667 return orig(*args, **kwargs)
668
668
669 def kwweb_skip(orig, web, req, tmpl):
669 def kwweb_skip(orig, web, req, tmpl):
670 '''Wraps webcommands.x turning off keyword expansion.'''
670 '''Wraps webcommands.x turning off keyword expansion.'''
671 kwt.match = util.never
671 kwt.match = util.never
672 return orig(web, req, tmpl)
672 return orig(web, req, tmpl)
673
673
674 def kw_amend(orig, ui, repo, commitfunc, old, extra, pats, opts):
674 def kw_amend(orig, ui, repo, commitfunc, old, extra, pats, opts):
675 '''Wraps cmdutil.amend expanding keywords after amend.'''
675 '''Wraps cmdutil.amend expanding keywords after amend.'''
676 with repo.wlock():
676 with repo.wlock():
677 kwt.postcommit = True
677 kwt.postcommit = True
678 newid = orig(ui, repo, commitfunc, old, extra, pats, opts)
678 newid = orig(ui, repo, commitfunc, old, extra, pats, opts)
679 if newid != old.node():
679 if newid != old.node():
680 ctx = repo[newid]
680 ctx = repo[newid]
681 kwt.restrict = True
681 kwt.restrict = True
682 kwt.overwrite(ctx, ctx.files(), False, True)
682 kwt.overwrite(ctx, ctx.files(), False, True)
683 kwt.restrict = False
683 kwt.restrict = False
684 return newid
684 return newid
685
685
686 def kw_copy(orig, ui, repo, pats, opts, rename=False):
686 def kw_copy(orig, ui, repo, pats, opts, rename=False):
687 '''Wraps cmdutil.copy so that copy/rename destinations do not
687 '''Wraps cmdutil.copy so that copy/rename destinations do not
688 contain expanded keywords.
688 contain expanded keywords.
689 Note that the source of a regular file destination may also be a
689 Note that the source of a regular file destination may also be a
690 symlink:
690 symlink:
691 hg cp sym x -> x is symlink
691 hg cp sym x -> x is symlink
692 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
692 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
693 For the latter we have to follow the symlink to find out whether its
693 For the latter we have to follow the symlink to find out whether its
694 target is configured for expansion and we therefore must unexpand the
694 target is configured for expansion and we therefore must unexpand the
695 keywords in the destination.'''
695 keywords in the destination.'''
696 with repo.wlock():
696 with repo.wlock():
697 orig(ui, repo, pats, opts, rename)
697 orig(ui, repo, pats, opts, rename)
698 if opts.get('dry_run'):
698 if opts.get('dry_run'):
699 return
699 return
700 wctx = repo[None]
700 wctx = repo[None]
701 cwd = repo.getcwd()
701 cwd = repo.getcwd()
702
702
703 def haskwsource(dest):
703 def haskwsource(dest):
704 '''Returns true if dest is a regular file and configured for
704 '''Returns true if dest is a regular file and configured for
705 expansion or a symlink which points to a file configured for
705 expansion or a symlink which points to a file configured for
706 expansion. '''
706 expansion. '''
707 source = repo.dirstate.copied(dest)
707 source = repo.dirstate.copied(dest)
708 if 'l' in wctx.flags(source):
708 if 'l' in wctx.flags(source):
709 source = pathutil.canonpath(repo.root, cwd,
709 source = pathutil.canonpath(repo.root, cwd,
710 os.path.realpath(source))
710 os.path.realpath(source))
711 return kwt.match(source)
711 return kwt.match(source)
712
712
713 candidates = [f for f in repo.dirstate.copies() if
713 candidates = [f for f in repo.dirstate.copies() if
714 'l' not in wctx.flags(f) and haskwsource(f)]
714 'l' not in wctx.flags(f) and haskwsource(f)]
715 kwt.overwrite(wctx, candidates, False, False)
715 kwt.overwrite(wctx, candidates, False, False)
716
716
717 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
717 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
718 '''Wraps record.dorecord expanding keywords after recording.'''
718 '''Wraps record.dorecord expanding keywords after recording.'''
719 with repo.wlock():
719 with repo.wlock():
720 # record returns 0 even when nothing has changed
720 # record returns 0 even when nothing has changed
721 # therefore compare nodes before and after
721 # therefore compare nodes before and after
722 kwt.postcommit = True
722 kwt.postcommit = True
723 ctx = repo['.']
723 ctx = repo['.']
724 wstatus = ctx.status()
724 wstatus = ctx.status()
725 ret = orig(ui, repo, commitfunc, *pats, **opts)
725 ret = orig(ui, repo, commitfunc, *pats, **opts)
726 recctx = repo['.']
726 recctx = repo['.']
727 if ctx != recctx:
727 if ctx != recctx:
728 modified, added = _preselect(wstatus, recctx.files())
728 modified, added = _preselect(wstatus, recctx.files())
729 kwt.restrict = False
729 kwt.restrict = False
730 kwt.overwrite(recctx, modified, False, True)
730 kwt.overwrite(recctx, modified, False, True)
731 kwt.overwrite(recctx, added, False, True, True)
731 kwt.overwrite(recctx, added, False, True, True)
732 kwt.restrict = True
732 kwt.restrict = True
733 return ret
733 return ret
734
734
735 def kwfilectx_cmp(orig, self, fctx):
735 def kwfilectx_cmp(orig, self, fctx):
736 # keyword affects data size, comparing wdir and filelog size does
736 # keyword affects data size, comparing wdir and filelog size does
737 # not make sense
737 # not make sense
738 if (fctx._filenode is None and
738 if (fctx._filenode is None and
739 (self._repo._encodefilterpats or
739 (self._repo._encodefilterpats or
740 kwt.match(fctx.path()) and 'l' not in fctx.flags() or
740 kwt.match(fctx.path()) and 'l' not in fctx.flags() or
741 self.size() - 4 == fctx.size()) or
741 self.size() - 4 == fctx.size()) or
742 self.size() == fctx.size()):
742 self.size() == fctx.size()):
743 return self._filelog.cmp(self._filenode, fctx.data())
743 return self._filelog.cmp(self._filenode, fctx.data())
744 return True
744 return True
745
745
746 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
746 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
747 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
747 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
748 extensions.wrapfunction(patch, 'diff', kwdiff)
748 extensions.wrapfunction(patch, 'diff', kwdiff)
749 extensions.wrapfunction(cmdutil, 'amend', kw_amend)
749 extensions.wrapfunction(cmdutil, 'amend', kw_amend)
750 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
750 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
751 extensions.wrapfunction(cmdutil, 'dorecord', kw_dorecord)
751 extensions.wrapfunction(cmdutil, 'dorecord', kw_dorecord)
752 for c in 'annotate changeset rev filediff diff'.split():
752 for c in 'annotate changeset rev filediff diff'.split():
753 extensions.wrapfunction(webcommands, c, kwweb_skip)
753 extensions.wrapfunction(webcommands, c, kwweb_skip)
754 repo.__class__ = kwrepo
754 repo.__class__ = kwrepo
@@ -1,196 +1,196 b''
1 # win32mbcs.py -- MBCS filename support for Mercurial
1 # win32mbcs.py -- MBCS filename support for Mercurial
2 #
2 #
3 # Copyright (c) 2008 Shun-ichi Goto <shunichi.goto@gmail.com>
3 # Copyright (c) 2008 Shun-ichi Goto <shunichi.goto@gmail.com>
4 #
4 #
5 # Version: 0.3
5 # Version: 0.3
6 # Author: Shun-ichi Goto <shunichi.goto@gmail.com>
6 # Author: Shun-ichi Goto <shunichi.goto@gmail.com>
7 #
7 #
8 # This software may be used and distributed according to the terms of the
8 # This software may be used and distributed according to the terms of the
9 # GNU General Public License version 2 or any later version.
9 # GNU General Public License version 2 or any later version.
10 #
10 #
11
11
12 '''allow the use of MBCS paths with problematic encodings
12 '''allow the use of MBCS paths with problematic encodings
13
13
14 Some MBCS encodings are not good for some path operations (i.e.
14 Some MBCS encodings are not good for some path operations (i.e.
15 splitting path, case conversion, etc.) with its encoded bytes. We call
15 splitting path, case conversion, etc.) with its encoded bytes. We call
16 such a encoding (i.e. shift_jis and big5) as "problematic encoding".
16 such a encoding (i.e. shift_jis and big5) as "problematic encoding".
17 This extension can be used to fix the issue with those encodings by
17 This extension can be used to fix the issue with those encodings by
18 wrapping some functions to convert to Unicode string before path
18 wrapping some functions to convert to Unicode string before path
19 operation.
19 operation.
20
20
21 This extension is useful for:
21 This extension is useful for:
22
22
23 - Japanese Windows users using shift_jis encoding.
23 - Japanese Windows users using shift_jis encoding.
24 - Chinese Windows users using big5 encoding.
24 - Chinese Windows users using big5 encoding.
25 - All users who use a repository with one of problematic encodings on
25 - All users who use a repository with one of problematic encodings on
26 case-insensitive file system.
26 case-insensitive file system.
27
27
28 This extension is not needed for:
28 This extension is not needed for:
29
29
30 - Any user who use only ASCII chars in path.
30 - Any user who use only ASCII chars in path.
31 - Any user who do not use any of problematic encodings.
31 - Any user who do not use any of problematic encodings.
32
32
33 Note that there are some limitations on using this extension:
33 Note that there are some limitations on using this extension:
34
34
35 - You should use single encoding in one repository.
35 - You should use single encoding in one repository.
36 - If the repository path ends with 0x5c, .hg/hgrc cannot be read.
36 - If the repository path ends with 0x5c, .hg/hgrc cannot be read.
37 - win32mbcs is not compatible with fixutf8 extension.
37 - win32mbcs is not compatible with fixutf8 extension.
38
38
39 By default, win32mbcs uses encoding.encoding decided by Mercurial.
39 By default, win32mbcs uses encoding.encoding decided by Mercurial.
40 You can specify the encoding by config option::
40 You can specify the encoding by config option::
41
41
42 [win32mbcs]
42 [win32mbcs]
43 encoding = sjis
43 encoding = sjis
44
44
45 It is useful for the users who want to commit with UTF-8 log message.
45 It is useful for the users who want to commit with UTF-8 log message.
46 '''
46 '''
47 from __future__ import absolute_import
47 from __future__ import absolute_import
48
48
49 import os
49 import os
50 import sys
50 import sys
51
51
52 from mercurial.i18n import _
52 from mercurial.i18n import _
53 from mercurial import (
53 from mercurial import (
54 encoding,
54 encoding,
55 error,
55 error,
56 )
56 )
57
57
58 # Note for extension authors: ONLY specify testedwith = 'internal' for
58 # Note for extension authors: ONLY specify testedwith = 'internal' for
59 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
59 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
60 # be specifying the version(s) of Mercurial they are tested with, or
60 # be specifying the version(s) of Mercurial they are tested with, or
61 # leave the attribute unspecified.
61 # leave the attribute unspecified.
62 testedwith = 'internal'
62 testedwith = 'internal'
63
63
64 _encoding = None # see extsetup
64 _encoding = None # see extsetup
65
65
66 def decode(arg):
66 def decode(arg):
67 if isinstance(arg, str):
67 if isinstance(arg, str):
68 uarg = arg.decode(_encoding)
68 uarg = arg.decode(_encoding)
69 if arg == uarg.encode(_encoding):
69 if arg == uarg.encode(_encoding):
70 return uarg
70 return uarg
71 raise UnicodeError("Not local encoding")
71 raise UnicodeError("Not local encoding")
72 elif isinstance(arg, tuple):
72 elif isinstance(arg, tuple):
73 return tuple(map(decode, arg))
73 return tuple(map(decode, arg))
74 elif isinstance(arg, list):
74 elif isinstance(arg, list):
75 return map(decode, arg)
75 return map(decode, arg)
76 elif isinstance(arg, dict):
76 elif isinstance(arg, dict):
77 for k, v in arg.items():
77 for k, v in arg.items():
78 arg[k] = decode(v)
78 arg[k] = decode(v)
79 return arg
79 return arg
80
80
81 def encode(arg):
81 def encode(arg):
82 if isinstance(arg, unicode):
82 if isinstance(arg, unicode):
83 return arg.encode(_encoding)
83 return arg.encode(_encoding)
84 elif isinstance(arg, tuple):
84 elif isinstance(arg, tuple):
85 return tuple(map(encode, arg))
85 return tuple(map(encode, arg))
86 elif isinstance(arg, list):
86 elif isinstance(arg, list):
87 return map(encode, arg)
87 return map(encode, arg)
88 elif isinstance(arg, dict):
88 elif isinstance(arg, dict):
89 for k, v in arg.items():
89 for k, v in arg.items():
90 arg[k] = encode(v)
90 arg[k] = encode(v)
91 return arg
91 return arg
92
92
93 def appendsep(s):
93 def appendsep(s):
94 # ensure the path ends with os.sep, appending it if necessary.
94 # ensure the path ends with os.sep, appending it if necessary.
95 try:
95 try:
96 us = decode(s)
96 us = decode(s)
97 except UnicodeError:
97 except UnicodeError:
98 us = s
98 us = s
99 if us and us[-1] not in ':/\\':
99 if us and us[-1] not in ':/\\':
100 s += os.sep
100 s += os.sep
101 return s
101 return s
102
102
103
103
104 def basewrapper(func, argtype, enc, dec, args, kwds):
104 def basewrapper(func, argtype, enc, dec, args, kwds):
105 # check check already converted, then call original
105 # check check already converted, then call original
106 for arg in args:
106 for arg in args:
107 if isinstance(arg, argtype):
107 if isinstance(arg, argtype):
108 return func(*args, **kwds)
108 return func(*args, **kwds)
109
109
110 try:
110 try:
111 # convert string arguments, call func, then convert back the
111 # convert string arguments, call func, then convert back the
112 # return value.
112 # return value.
113 return enc(func(*dec(args), **dec(kwds)))
113 return enc(func(*dec(args), **dec(kwds)))
114 except UnicodeError:
114 except UnicodeError:
115 raise error.Abort(_("[win32mbcs] filename conversion failed with"
115 raise error.Abort(_("[win32mbcs] filename conversion failed with"
116 " %s encoding\n") % (_encoding))
116 " %s encoding\n") % (_encoding))
117
117
118 def wrapper(func, args, kwds):
118 def wrapper(func, args, kwds):
119 return basewrapper(func, unicode, encode, decode, args, kwds)
119 return basewrapper(func, unicode, encode, decode, args, kwds)
120
120
121
121
122 def reversewrapper(func, args, kwds):
122 def reversewrapper(func, args, kwds):
123 return basewrapper(func, str, decode, encode, args, kwds)
123 return basewrapper(func, str, decode, encode, args, kwds)
124
124
125 def wrapperforlistdir(func, args, kwds):
125 def wrapperforlistdir(func, args, kwds):
126 # Ensure 'path' argument ends with os.sep to avoids
126 # Ensure 'path' argument ends with os.sep to avoids
127 # misinterpreting last 0x5c of MBCS 2nd byte as path separator.
127 # misinterpreting last 0x5c of MBCS 2nd byte as path separator.
128 if args:
128 if args:
129 args = list(args)
129 args = list(args)
130 args[0] = appendsep(args[0])
130 args[0] = appendsep(args[0])
131 if 'path' in kwds:
131 if 'path' in kwds:
132 kwds['path'] = appendsep(kwds['path'])
132 kwds['path'] = appendsep(kwds['path'])
133 return func(*args, **kwds)
133 return func(*args, **kwds)
134
134
135 def wrapname(name, wrapper):
135 def wrapname(name, wrapper):
136 module, name = name.rsplit('.', 1)
136 module, name = name.rsplit('.', 1)
137 module = sys.modules[module]
137 module = sys.modules[module]
138 func = getattr(module, name)
138 func = getattr(module, name)
139 def f(*args, **kwds):
139 def f(*args, **kwds):
140 return wrapper(func, args, kwds)
140 return wrapper(func, args, kwds)
141 try:
141 try:
142 f.__name__ = func.__name__ # fails with Python 2.3
142 f.__name__ = func.__name__ # fails with Python 2.3
143 except Exception:
143 except Exception:
144 pass
144 pass
145 setattr(module, name, f)
145 setattr(module, name, f)
146
146
147 # List of functions to be wrapped.
147 # List of functions to be wrapped.
148 # NOTE: os.path.dirname() and os.path.basename() are safe because
148 # NOTE: os.path.dirname() and os.path.basename() are safe because
149 # they use result of os.path.split()
149 # they use result of os.path.split()
150 funcs = '''os.path.join os.path.split os.path.splitext
150 funcs = '''os.path.join os.path.split os.path.splitext
151 os.path.normpath os.makedirs
151 os.path.normpath os.makedirs
152 mercurial.util.endswithsep mercurial.util.splitpath mercurial.util.checkcase
152 mercurial.util.endswithsep mercurial.util.splitpath mercurial.util.checkcase
153 mercurial.util.fspath mercurial.util.pconvert mercurial.util.normpath
153 mercurial.util.fspath mercurial.util.pconvert mercurial.util.normpath
154 mercurial.util.checkwinfilename mercurial.util.checkosfilename
154 mercurial.util.checkwinfilename mercurial.util.checkosfilename
155 mercurial.util.split'''
155 mercurial.util.split'''
156
156
157 # These functions are required to be called with local encoded string
157 # These functions are required to be called with local encoded string
158 # because they expects argument is local encoded string and cause
158 # because they expects argument is local encoded string and cause
159 # problem with unicode string.
159 # problem with unicode string.
160 rfuncs = '''mercurial.encoding.upper mercurial.encoding.lower'''
160 rfuncs = '''mercurial.encoding.upper mercurial.encoding.lower'''
161
161
162 # List of Windows specific functions to be wrapped.
162 # List of Windows specific functions to be wrapped.
163 winfuncs = '''os.path.splitunc'''
163 winfuncs = '''os.path.splitunc'''
164
164
165 # codec and alias names of sjis and big5 to be faked.
165 # codec and alias names of sjis and big5 to be faked.
166 problematic_encodings = '''big5 big5-tw csbig5 big5hkscs big5-hkscs
166 problematic_encodings = '''big5 big5-tw csbig5 big5hkscs big5-hkscs
167 hkscs cp932 932 ms932 mskanji ms-kanji shift_jis csshiftjis shiftjis
167 hkscs cp932 932 ms932 mskanji ms-kanji shift_jis csshiftjis shiftjis
168 sjis s_jis shift_jis_2004 shiftjis2004 sjis_2004 sjis2004
168 sjis s_jis shift_jis_2004 shiftjis2004 sjis_2004 sjis2004
169 shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213 950 cp950 ms950 '''
169 shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213 950 cp950 ms950 '''
170
170
171 def extsetup(ui):
171 def extsetup(ui):
172 # TODO: decide use of config section for this extension
172 # TODO: decide use of config section for this extension
173 if ((not os.path.supports_unicode_filenames) and
173 if ((not os.path.supports_unicode_filenames) and
174 (sys.platform != 'cygwin')):
174 (sys.platform != 'cygwin')):
175 ui.warn(_("[win32mbcs] cannot activate on this platform.\n"))
175 ui.warn(_("[win32mbcs] cannot activate on this platform.\n"))
176 return
176 return
177 # determine encoding for filename
177 # determine encoding for filename
178 global _encoding
178 global _encoding
179 _encoding = ui.config('win32mbcs', 'encoding', encoding.encoding)
179 _encoding = ui.config('win32mbcs', 'encoding', encoding.encoding)
180 # fake is only for relevant environment.
180 # fake is only for relevant environment.
181 if _encoding.lower() in problematic_encodings.split():
181 if _encoding.lower() in problematic_encodings.split():
182 for f in funcs.split():
182 for f in funcs.split():
183 wrapname(f, wrapper)
183 wrapname(f, wrapper)
184 if os.name == 'nt':
184 if os.name == 'nt':
185 for f in winfuncs.split():
185 for f in winfuncs.split():
186 wrapname(f, wrapper)
186 wrapname(f, wrapper)
187 wrapname("mercurial.osutil.listdir", wrapperforlistdir)
187 wrapname("mercurial.osutil.listdir", wrapperforlistdir)
188 # wrap functions to be called with local byte string arguments
188 # wrap functions to be called with local byte string arguments
189 for f in rfuncs.split():
189 for f in rfuncs.split():
190 wrapname(f, reversewrapper)
190 wrapname(f, reversewrapper)
191 # Check sys.args manually instead of using ui.debug() because
191 # Check sys.args manually instead of using ui.debug() because
192 # command line options is not yet applied when
192 # command line options is not yet applied when
193 # extensions.loadall() is called.
193 # extensions.loadall() is called.
194 if '--debug' in sys.argv:
194 if '--debug' in sys.argv:
195 ui.write("[win32mbcs] activated with encoding: %s\n"
195 ui.write(("[win32mbcs] activated with encoding: %s\n")
196 % _encoding)
196 % _encoding)
@@ -1,3559 +1,3560 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import os
11 import os
12 import re
12 import re
13 import sys
13 import sys
14 import tempfile
14 import tempfile
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 bin,
18 bin,
19 hex,
19 hex,
20 nullid,
20 nullid,
21 nullrev,
21 nullrev,
22 short,
22 short,
23 )
23 )
24
24
25 from . import (
25 from . import (
26 bookmarks,
26 bookmarks,
27 changelog,
27 changelog,
28 copies,
28 copies,
29 crecord as crecordmod,
29 crecord as crecordmod,
30 encoding,
30 encoding,
31 error,
31 error,
32 formatter,
32 formatter,
33 graphmod,
33 graphmod,
34 lock as lockmod,
34 lock as lockmod,
35 match as matchmod,
35 match as matchmod,
36 obsolete,
36 obsolete,
37 patch,
37 patch,
38 pathutil,
38 pathutil,
39 phases,
39 phases,
40 repair,
40 repair,
41 revlog,
41 revlog,
42 revset,
42 revset,
43 scmutil,
43 scmutil,
44 templatekw,
44 templatekw,
45 templater,
45 templater,
46 util,
46 util,
47 )
47 )
48 stringio = util.stringio
48 stringio = util.stringio
49
49
50 def ishunk(x):
50 def ishunk(x):
51 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
51 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
52 return isinstance(x, hunkclasses)
52 return isinstance(x, hunkclasses)
53
53
54 def newandmodified(chunks, originalchunks):
54 def newandmodified(chunks, originalchunks):
55 newlyaddedandmodifiedfiles = set()
55 newlyaddedandmodifiedfiles = set()
56 for chunk in chunks:
56 for chunk in chunks:
57 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
57 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
58 originalchunks:
58 originalchunks:
59 newlyaddedandmodifiedfiles.add(chunk.header.filename())
59 newlyaddedandmodifiedfiles.add(chunk.header.filename())
60 return newlyaddedandmodifiedfiles
60 return newlyaddedandmodifiedfiles
61
61
62 def parsealiases(cmd):
62 def parsealiases(cmd):
63 return cmd.lstrip("^").split("|")
63 return cmd.lstrip("^").split("|")
64
64
65 def setupwrapcolorwrite(ui):
65 def setupwrapcolorwrite(ui):
66 # wrap ui.write so diff output can be labeled/colorized
66 # wrap ui.write so diff output can be labeled/colorized
67 def wrapwrite(orig, *args, **kw):
67 def wrapwrite(orig, *args, **kw):
68 label = kw.pop('label', '')
68 label = kw.pop('label', '')
69 for chunk, l in patch.difflabel(lambda: args):
69 for chunk, l in patch.difflabel(lambda: args):
70 orig(chunk, label=label + l)
70 orig(chunk, label=label + l)
71
71
72 oldwrite = ui.write
72 oldwrite = ui.write
73 def wrap(*args, **kwargs):
73 def wrap(*args, **kwargs):
74 return wrapwrite(oldwrite, *args, **kwargs)
74 return wrapwrite(oldwrite, *args, **kwargs)
75 setattr(ui, 'write', wrap)
75 setattr(ui, 'write', wrap)
76 return oldwrite
76 return oldwrite
77
77
78 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
78 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
79 if usecurses:
79 if usecurses:
80 if testfile:
80 if testfile:
81 recordfn = crecordmod.testdecorator(testfile,
81 recordfn = crecordmod.testdecorator(testfile,
82 crecordmod.testchunkselector)
82 crecordmod.testchunkselector)
83 else:
83 else:
84 recordfn = crecordmod.chunkselector
84 recordfn = crecordmod.chunkselector
85
85
86 return crecordmod.filterpatch(ui, originalhunks, recordfn)
86 return crecordmod.filterpatch(ui, originalhunks, recordfn)
87
87
88 else:
88 else:
89 return patch.filterpatch(ui, originalhunks, operation)
89 return patch.filterpatch(ui, originalhunks, operation)
90
90
91 def recordfilter(ui, originalhunks, operation=None):
91 def recordfilter(ui, originalhunks, operation=None):
92 """ Prompts the user to filter the originalhunks and return a list of
92 """ Prompts the user to filter the originalhunks and return a list of
93 selected hunks.
93 selected hunks.
94 *operation* is used for to build ui messages to indicate the user what
94 *operation* is used for to build ui messages to indicate the user what
95 kind of filtering they are doing: reverting, committing, shelving, etc.
95 kind of filtering they are doing: reverting, committing, shelving, etc.
96 (see patch.filterpatch).
96 (see patch.filterpatch).
97 """
97 """
98 usecurses = crecordmod.checkcurses(ui)
98 usecurses = crecordmod.checkcurses(ui)
99 testfile = ui.config('experimental', 'crecordtest', None)
99 testfile = ui.config('experimental', 'crecordtest', None)
100 oldwrite = setupwrapcolorwrite(ui)
100 oldwrite = setupwrapcolorwrite(ui)
101 try:
101 try:
102 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
102 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
103 testfile, operation)
103 testfile, operation)
104 finally:
104 finally:
105 ui.write = oldwrite
105 ui.write = oldwrite
106 return newchunks, newopts
106 return newchunks, newopts
107
107
108 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
108 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
109 filterfn, *pats, **opts):
109 filterfn, *pats, **opts):
110 from . import merge as mergemod
110 from . import merge as mergemod
111 if not ui.interactive():
111 if not ui.interactive():
112 if cmdsuggest:
112 if cmdsuggest:
113 msg = _('running non-interactively, use %s instead') % cmdsuggest
113 msg = _('running non-interactively, use %s instead') % cmdsuggest
114 else:
114 else:
115 msg = _('running non-interactively')
115 msg = _('running non-interactively')
116 raise error.Abort(msg)
116 raise error.Abort(msg)
117
117
118 # make sure username is set before going interactive
118 # make sure username is set before going interactive
119 if not opts.get('user'):
119 if not opts.get('user'):
120 ui.username() # raise exception, username not provided
120 ui.username() # raise exception, username not provided
121
121
122 def recordfunc(ui, repo, message, match, opts):
122 def recordfunc(ui, repo, message, match, opts):
123 """This is generic record driver.
123 """This is generic record driver.
124
124
125 Its job is to interactively filter local changes, and
125 Its job is to interactively filter local changes, and
126 accordingly prepare working directory into a state in which the
126 accordingly prepare working directory into a state in which the
127 job can be delegated to a non-interactive commit command such as
127 job can be delegated to a non-interactive commit command such as
128 'commit' or 'qrefresh'.
128 'commit' or 'qrefresh'.
129
129
130 After the actual job is done by non-interactive command, the
130 After the actual job is done by non-interactive command, the
131 working directory is restored to its original state.
131 working directory is restored to its original state.
132
132
133 In the end we'll record interesting changes, and everything else
133 In the end we'll record interesting changes, and everything else
134 will be left in place, so the user can continue working.
134 will be left in place, so the user can continue working.
135 """
135 """
136
136
137 checkunfinished(repo, commit=True)
137 checkunfinished(repo, commit=True)
138 wctx = repo[None]
138 wctx = repo[None]
139 merge = len(wctx.parents()) > 1
139 merge = len(wctx.parents()) > 1
140 if merge:
140 if merge:
141 raise error.Abort(_('cannot partially commit a merge '
141 raise error.Abort(_('cannot partially commit a merge '
142 '(use "hg commit" instead)'))
142 '(use "hg commit" instead)'))
143
143
144 def fail(f, msg):
144 def fail(f, msg):
145 raise error.Abort('%s: %s' % (f, msg))
145 raise error.Abort('%s: %s' % (f, msg))
146
146
147 force = opts.get('force')
147 force = opts.get('force')
148 if not force:
148 if not force:
149 vdirs = []
149 vdirs = []
150 match.explicitdir = vdirs.append
150 match.explicitdir = vdirs.append
151 match.bad = fail
151 match.bad = fail
152
152
153 status = repo.status(match=match)
153 status = repo.status(match=match)
154 if not force:
154 if not force:
155 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
155 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
156 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
156 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
157 diffopts.nodates = True
157 diffopts.nodates = True
158 diffopts.git = True
158 diffopts.git = True
159 diffopts.showfunc = True
159 diffopts.showfunc = True
160 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
160 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
161 originalchunks = patch.parsepatch(originaldiff)
161 originalchunks = patch.parsepatch(originaldiff)
162
162
163 # 1. filter patch, since we are intending to apply subset of it
163 # 1. filter patch, since we are intending to apply subset of it
164 try:
164 try:
165 chunks, newopts = filterfn(ui, originalchunks)
165 chunks, newopts = filterfn(ui, originalchunks)
166 except patch.PatchError as err:
166 except patch.PatchError as err:
167 raise error.Abort(_('error parsing patch: %s') % err)
167 raise error.Abort(_('error parsing patch: %s') % err)
168 opts.update(newopts)
168 opts.update(newopts)
169
169
170 # We need to keep a backup of files that have been newly added and
170 # We need to keep a backup of files that have been newly added and
171 # modified during the recording process because there is a previous
171 # modified during the recording process because there is a previous
172 # version without the edit in the workdir
172 # version without the edit in the workdir
173 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
173 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
174 contenders = set()
174 contenders = set()
175 for h in chunks:
175 for h in chunks:
176 try:
176 try:
177 contenders.update(set(h.files()))
177 contenders.update(set(h.files()))
178 except AttributeError:
178 except AttributeError:
179 pass
179 pass
180
180
181 changed = status.modified + status.added + status.removed
181 changed = status.modified + status.added + status.removed
182 newfiles = [f for f in changed if f in contenders]
182 newfiles = [f for f in changed if f in contenders]
183 if not newfiles:
183 if not newfiles:
184 ui.status(_('no changes to record\n'))
184 ui.status(_('no changes to record\n'))
185 return 0
185 return 0
186
186
187 modified = set(status.modified)
187 modified = set(status.modified)
188
188
189 # 2. backup changed files, so we can restore them in the end
189 # 2. backup changed files, so we can restore them in the end
190
190
191 if backupall:
191 if backupall:
192 tobackup = changed
192 tobackup = changed
193 else:
193 else:
194 tobackup = [f for f in newfiles if f in modified or f in \
194 tobackup = [f for f in newfiles if f in modified or f in \
195 newlyaddedandmodifiedfiles]
195 newlyaddedandmodifiedfiles]
196 backups = {}
196 backups = {}
197 if tobackup:
197 if tobackup:
198 backupdir = repo.join('record-backups')
198 backupdir = repo.join('record-backups')
199 try:
199 try:
200 os.mkdir(backupdir)
200 os.mkdir(backupdir)
201 except OSError as err:
201 except OSError as err:
202 if err.errno != errno.EEXIST:
202 if err.errno != errno.EEXIST:
203 raise
203 raise
204 try:
204 try:
205 # backup continues
205 # backup continues
206 for f in tobackup:
206 for f in tobackup:
207 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
207 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
208 dir=backupdir)
208 dir=backupdir)
209 os.close(fd)
209 os.close(fd)
210 ui.debug('backup %r as %r\n' % (f, tmpname))
210 ui.debug('backup %r as %r\n' % (f, tmpname))
211 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
211 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
212 backups[f] = tmpname
212 backups[f] = tmpname
213
213
214 fp = stringio()
214 fp = stringio()
215 for c in chunks:
215 for c in chunks:
216 fname = c.filename()
216 fname = c.filename()
217 if fname in backups:
217 if fname in backups:
218 c.write(fp)
218 c.write(fp)
219 dopatch = fp.tell()
219 dopatch = fp.tell()
220 fp.seek(0)
220 fp.seek(0)
221
221
222 # 2.5 optionally review / modify patch in text editor
222 # 2.5 optionally review / modify patch in text editor
223 if opts.get('review', False):
223 if opts.get('review', False):
224 patchtext = (crecordmod.diffhelptext
224 patchtext = (crecordmod.diffhelptext
225 + crecordmod.patchhelptext
225 + crecordmod.patchhelptext
226 + fp.read())
226 + fp.read())
227 reviewedpatch = ui.edit(patchtext, "",
227 reviewedpatch = ui.edit(patchtext, "",
228 extra={"suffix": ".diff"})
228 extra={"suffix": ".diff"})
229 fp.truncate(0)
229 fp.truncate(0)
230 fp.write(reviewedpatch)
230 fp.write(reviewedpatch)
231 fp.seek(0)
231 fp.seek(0)
232
232
233 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
233 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
234 # 3a. apply filtered patch to clean repo (clean)
234 # 3a. apply filtered patch to clean repo (clean)
235 if backups:
235 if backups:
236 # Equivalent to hg.revert
236 # Equivalent to hg.revert
237 m = scmutil.matchfiles(repo, backups.keys())
237 m = scmutil.matchfiles(repo, backups.keys())
238 mergemod.update(repo, repo.dirstate.p1(),
238 mergemod.update(repo, repo.dirstate.p1(),
239 False, True, matcher=m)
239 False, True, matcher=m)
240
240
241 # 3b. (apply)
241 # 3b. (apply)
242 if dopatch:
242 if dopatch:
243 try:
243 try:
244 ui.debug('applying patch\n')
244 ui.debug('applying patch\n')
245 ui.debug(fp.getvalue())
245 ui.debug(fp.getvalue())
246 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
246 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
247 except patch.PatchError as err:
247 except patch.PatchError as err:
248 raise error.Abort(str(err))
248 raise error.Abort(str(err))
249 del fp
249 del fp
250
250
251 # 4. We prepared working directory according to filtered
251 # 4. We prepared working directory according to filtered
252 # patch. Now is the time to delegate the job to
252 # patch. Now is the time to delegate the job to
253 # commit/qrefresh or the like!
253 # commit/qrefresh or the like!
254
254
255 # Make all of the pathnames absolute.
255 # Make all of the pathnames absolute.
256 newfiles = [repo.wjoin(nf) for nf in newfiles]
256 newfiles = [repo.wjoin(nf) for nf in newfiles]
257 return commitfunc(ui, repo, *newfiles, **opts)
257 return commitfunc(ui, repo, *newfiles, **opts)
258 finally:
258 finally:
259 # 5. finally restore backed-up files
259 # 5. finally restore backed-up files
260 try:
260 try:
261 dirstate = repo.dirstate
261 dirstate = repo.dirstate
262 for realname, tmpname in backups.iteritems():
262 for realname, tmpname in backups.iteritems():
263 ui.debug('restoring %r to %r\n' % (tmpname, realname))
263 ui.debug('restoring %r to %r\n' % (tmpname, realname))
264
264
265 if dirstate[realname] == 'n':
265 if dirstate[realname] == 'n':
266 # without normallookup, restoring timestamp
266 # without normallookup, restoring timestamp
267 # may cause partially committed files
267 # may cause partially committed files
268 # to be treated as unmodified
268 # to be treated as unmodified
269 dirstate.normallookup(realname)
269 dirstate.normallookup(realname)
270
270
271 # copystat=True here and above are a hack to trick any
271 # copystat=True here and above are a hack to trick any
272 # editors that have f open that we haven't modified them.
272 # editors that have f open that we haven't modified them.
273 #
273 #
274 # Also note that this racy as an editor could notice the
274 # Also note that this racy as an editor could notice the
275 # file's mtime before we've finished writing it.
275 # file's mtime before we've finished writing it.
276 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
276 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
277 os.unlink(tmpname)
277 os.unlink(tmpname)
278 if tobackup:
278 if tobackup:
279 os.rmdir(backupdir)
279 os.rmdir(backupdir)
280 except OSError:
280 except OSError:
281 pass
281 pass
282
282
283 def recordinwlock(ui, repo, message, match, opts):
283 def recordinwlock(ui, repo, message, match, opts):
284 with repo.wlock():
284 with repo.wlock():
285 return recordfunc(ui, repo, message, match, opts)
285 return recordfunc(ui, repo, message, match, opts)
286
286
287 return commit(ui, repo, recordinwlock, pats, opts)
287 return commit(ui, repo, recordinwlock, pats, opts)
288
288
289 def findpossible(cmd, table, strict=False):
289 def findpossible(cmd, table, strict=False):
290 """
290 """
291 Return cmd -> (aliases, command table entry)
291 Return cmd -> (aliases, command table entry)
292 for each matching command.
292 for each matching command.
293 Return debug commands (or their aliases) only if no normal command matches.
293 Return debug commands (or their aliases) only if no normal command matches.
294 """
294 """
295 choice = {}
295 choice = {}
296 debugchoice = {}
296 debugchoice = {}
297
297
298 if cmd in table:
298 if cmd in table:
299 # short-circuit exact matches, "log" alias beats "^log|history"
299 # short-circuit exact matches, "log" alias beats "^log|history"
300 keys = [cmd]
300 keys = [cmd]
301 else:
301 else:
302 keys = table.keys()
302 keys = table.keys()
303
303
304 allcmds = []
304 allcmds = []
305 for e in keys:
305 for e in keys:
306 aliases = parsealiases(e)
306 aliases = parsealiases(e)
307 allcmds.extend(aliases)
307 allcmds.extend(aliases)
308 found = None
308 found = None
309 if cmd in aliases:
309 if cmd in aliases:
310 found = cmd
310 found = cmd
311 elif not strict:
311 elif not strict:
312 for a in aliases:
312 for a in aliases:
313 if a.startswith(cmd):
313 if a.startswith(cmd):
314 found = a
314 found = a
315 break
315 break
316 if found is not None:
316 if found is not None:
317 if aliases[0].startswith("debug") or found.startswith("debug"):
317 if aliases[0].startswith("debug") or found.startswith("debug"):
318 debugchoice[found] = (aliases, table[e])
318 debugchoice[found] = (aliases, table[e])
319 else:
319 else:
320 choice[found] = (aliases, table[e])
320 choice[found] = (aliases, table[e])
321
321
322 if not choice and debugchoice:
322 if not choice and debugchoice:
323 choice = debugchoice
323 choice = debugchoice
324
324
325 return choice, allcmds
325 return choice, allcmds
326
326
327 def findcmd(cmd, table, strict=True):
327 def findcmd(cmd, table, strict=True):
328 """Return (aliases, command table entry) for command string."""
328 """Return (aliases, command table entry) for command string."""
329 choice, allcmds = findpossible(cmd, table, strict)
329 choice, allcmds = findpossible(cmd, table, strict)
330
330
331 if cmd in choice:
331 if cmd in choice:
332 return choice[cmd]
332 return choice[cmd]
333
333
334 if len(choice) > 1:
334 if len(choice) > 1:
335 clist = choice.keys()
335 clist = choice.keys()
336 clist.sort()
336 clist.sort()
337 raise error.AmbiguousCommand(cmd, clist)
337 raise error.AmbiguousCommand(cmd, clist)
338
338
339 if choice:
339 if choice:
340 return choice.values()[0]
340 return choice.values()[0]
341
341
342 raise error.UnknownCommand(cmd, allcmds)
342 raise error.UnknownCommand(cmd, allcmds)
343
343
344 def findrepo(p):
344 def findrepo(p):
345 while not os.path.isdir(os.path.join(p, ".hg")):
345 while not os.path.isdir(os.path.join(p, ".hg")):
346 oldp, p = p, os.path.dirname(p)
346 oldp, p = p, os.path.dirname(p)
347 if p == oldp:
347 if p == oldp:
348 return None
348 return None
349
349
350 return p
350 return p
351
351
352 def bailifchanged(repo, merge=True):
352 def bailifchanged(repo, merge=True):
353 if merge and repo.dirstate.p2() != nullid:
353 if merge and repo.dirstate.p2() != nullid:
354 raise error.Abort(_('outstanding uncommitted merge'))
354 raise error.Abort(_('outstanding uncommitted merge'))
355 modified, added, removed, deleted = repo.status()[:4]
355 modified, added, removed, deleted = repo.status()[:4]
356 if modified or added or removed or deleted:
356 if modified or added or removed or deleted:
357 raise error.Abort(_('uncommitted changes'))
357 raise error.Abort(_('uncommitted changes'))
358 ctx = repo[None]
358 ctx = repo[None]
359 for s in sorted(ctx.substate):
359 for s in sorted(ctx.substate):
360 ctx.sub(s).bailifchanged()
360 ctx.sub(s).bailifchanged()
361
361
362 def logmessage(ui, opts):
362 def logmessage(ui, opts):
363 """ get the log message according to -m and -l option """
363 """ get the log message according to -m and -l option """
364 message = opts.get('message')
364 message = opts.get('message')
365 logfile = opts.get('logfile')
365 logfile = opts.get('logfile')
366
366
367 if message and logfile:
367 if message and logfile:
368 raise error.Abort(_('options --message and --logfile are mutually '
368 raise error.Abort(_('options --message and --logfile are mutually '
369 'exclusive'))
369 'exclusive'))
370 if not message and logfile:
370 if not message and logfile:
371 try:
371 try:
372 if logfile == '-':
372 if logfile == '-':
373 message = ui.fin.read()
373 message = ui.fin.read()
374 else:
374 else:
375 message = '\n'.join(util.readfile(logfile).splitlines())
375 message = '\n'.join(util.readfile(logfile).splitlines())
376 except IOError as inst:
376 except IOError as inst:
377 raise error.Abort(_("can't read commit message '%s': %s") %
377 raise error.Abort(_("can't read commit message '%s': %s") %
378 (logfile, inst.strerror))
378 (logfile, inst.strerror))
379 return message
379 return message
380
380
381 def mergeeditform(ctxorbool, baseformname):
381 def mergeeditform(ctxorbool, baseformname):
382 """return appropriate editform name (referencing a committemplate)
382 """return appropriate editform name (referencing a committemplate)
383
383
384 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
384 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
385 merging is committed.
385 merging is committed.
386
386
387 This returns baseformname with '.merge' appended if it is a merge,
387 This returns baseformname with '.merge' appended if it is a merge,
388 otherwise '.normal' is appended.
388 otherwise '.normal' is appended.
389 """
389 """
390 if isinstance(ctxorbool, bool):
390 if isinstance(ctxorbool, bool):
391 if ctxorbool:
391 if ctxorbool:
392 return baseformname + ".merge"
392 return baseformname + ".merge"
393 elif 1 < len(ctxorbool.parents()):
393 elif 1 < len(ctxorbool.parents()):
394 return baseformname + ".merge"
394 return baseformname + ".merge"
395
395
396 return baseformname + ".normal"
396 return baseformname + ".normal"
397
397
398 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
398 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
399 editform='', **opts):
399 editform='', **opts):
400 """get appropriate commit message editor according to '--edit' option
400 """get appropriate commit message editor according to '--edit' option
401
401
402 'finishdesc' is a function to be called with edited commit message
402 'finishdesc' is a function to be called with edited commit message
403 (= 'description' of the new changeset) just after editing, but
403 (= 'description' of the new changeset) just after editing, but
404 before checking empty-ness. It should return actual text to be
404 before checking empty-ness. It should return actual text to be
405 stored into history. This allows to change description before
405 stored into history. This allows to change description before
406 storing.
406 storing.
407
407
408 'extramsg' is a extra message to be shown in the editor instead of
408 'extramsg' is a extra message to be shown in the editor instead of
409 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
409 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
410 is automatically added.
410 is automatically added.
411
411
412 'editform' is a dot-separated list of names, to distinguish
412 'editform' is a dot-separated list of names, to distinguish
413 the purpose of commit text editing.
413 the purpose of commit text editing.
414
414
415 'getcommiteditor' returns 'commitforceeditor' regardless of
415 'getcommiteditor' returns 'commitforceeditor' regardless of
416 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
416 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
417 they are specific for usage in MQ.
417 they are specific for usage in MQ.
418 """
418 """
419 if edit or finishdesc or extramsg:
419 if edit or finishdesc or extramsg:
420 return lambda r, c, s: commitforceeditor(r, c, s,
420 return lambda r, c, s: commitforceeditor(r, c, s,
421 finishdesc=finishdesc,
421 finishdesc=finishdesc,
422 extramsg=extramsg,
422 extramsg=extramsg,
423 editform=editform)
423 editform=editform)
424 elif editform:
424 elif editform:
425 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
425 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
426 else:
426 else:
427 return commiteditor
427 return commiteditor
428
428
429 def loglimit(opts):
429 def loglimit(opts):
430 """get the log limit according to option -l/--limit"""
430 """get the log limit according to option -l/--limit"""
431 limit = opts.get('limit')
431 limit = opts.get('limit')
432 if limit:
432 if limit:
433 try:
433 try:
434 limit = int(limit)
434 limit = int(limit)
435 except ValueError:
435 except ValueError:
436 raise error.Abort(_('limit must be a positive integer'))
436 raise error.Abort(_('limit must be a positive integer'))
437 if limit <= 0:
437 if limit <= 0:
438 raise error.Abort(_('limit must be positive'))
438 raise error.Abort(_('limit must be positive'))
439 else:
439 else:
440 limit = None
440 limit = None
441 return limit
441 return limit
442
442
443 def makefilename(repo, pat, node, desc=None,
443 def makefilename(repo, pat, node, desc=None,
444 total=None, seqno=None, revwidth=None, pathname=None):
444 total=None, seqno=None, revwidth=None, pathname=None):
445 node_expander = {
445 node_expander = {
446 'H': lambda: hex(node),
446 'H': lambda: hex(node),
447 'R': lambda: str(repo.changelog.rev(node)),
447 'R': lambda: str(repo.changelog.rev(node)),
448 'h': lambda: short(node),
448 'h': lambda: short(node),
449 'm': lambda: re.sub('[^\w]', '_', str(desc))
449 'm': lambda: re.sub('[^\w]', '_', str(desc))
450 }
450 }
451 expander = {
451 expander = {
452 '%': lambda: '%',
452 '%': lambda: '%',
453 'b': lambda: os.path.basename(repo.root),
453 'b': lambda: os.path.basename(repo.root),
454 }
454 }
455
455
456 try:
456 try:
457 if node:
457 if node:
458 expander.update(node_expander)
458 expander.update(node_expander)
459 if node:
459 if node:
460 expander['r'] = (lambda:
460 expander['r'] = (lambda:
461 str(repo.changelog.rev(node)).zfill(revwidth or 0))
461 str(repo.changelog.rev(node)).zfill(revwidth or 0))
462 if total is not None:
462 if total is not None:
463 expander['N'] = lambda: str(total)
463 expander['N'] = lambda: str(total)
464 if seqno is not None:
464 if seqno is not None:
465 expander['n'] = lambda: str(seqno)
465 expander['n'] = lambda: str(seqno)
466 if total is not None and seqno is not None:
466 if total is not None and seqno is not None:
467 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
467 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
468 if pathname is not None:
468 if pathname is not None:
469 expander['s'] = lambda: os.path.basename(pathname)
469 expander['s'] = lambda: os.path.basename(pathname)
470 expander['d'] = lambda: os.path.dirname(pathname) or '.'
470 expander['d'] = lambda: os.path.dirname(pathname) or '.'
471 expander['p'] = lambda: pathname
471 expander['p'] = lambda: pathname
472
472
473 newname = []
473 newname = []
474 patlen = len(pat)
474 patlen = len(pat)
475 i = 0
475 i = 0
476 while i < patlen:
476 while i < patlen:
477 c = pat[i]
477 c = pat[i]
478 if c == '%':
478 if c == '%':
479 i += 1
479 i += 1
480 c = pat[i]
480 c = pat[i]
481 c = expander[c]()
481 c = expander[c]()
482 newname.append(c)
482 newname.append(c)
483 i += 1
483 i += 1
484 return ''.join(newname)
484 return ''.join(newname)
485 except KeyError as inst:
485 except KeyError as inst:
486 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
486 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
487 inst.args[0])
487 inst.args[0])
488
488
489 class _unclosablefile(object):
489 class _unclosablefile(object):
490 def __init__(self, fp):
490 def __init__(self, fp):
491 self._fp = fp
491 self._fp = fp
492
492
493 def close(self):
493 def close(self):
494 pass
494 pass
495
495
496 def __iter__(self):
496 def __iter__(self):
497 return iter(self._fp)
497 return iter(self._fp)
498
498
499 def __getattr__(self, attr):
499 def __getattr__(self, attr):
500 return getattr(self._fp, attr)
500 return getattr(self._fp, attr)
501
501
502 def makefileobj(repo, pat, node=None, desc=None, total=None,
502 def makefileobj(repo, pat, node=None, desc=None, total=None,
503 seqno=None, revwidth=None, mode='wb', modemap=None,
503 seqno=None, revwidth=None, mode='wb', modemap=None,
504 pathname=None):
504 pathname=None):
505
505
506 writable = mode not in ('r', 'rb')
506 writable = mode not in ('r', 'rb')
507
507
508 if not pat or pat == '-':
508 if not pat or pat == '-':
509 if writable:
509 if writable:
510 fp = repo.ui.fout
510 fp = repo.ui.fout
511 else:
511 else:
512 fp = repo.ui.fin
512 fp = repo.ui.fin
513 return _unclosablefile(fp)
513 return _unclosablefile(fp)
514 if util.safehasattr(pat, 'write') and writable:
514 if util.safehasattr(pat, 'write') and writable:
515 return pat
515 return pat
516 if util.safehasattr(pat, 'read') and 'r' in mode:
516 if util.safehasattr(pat, 'read') and 'r' in mode:
517 return pat
517 return pat
518 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
518 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
519 if modemap is not None:
519 if modemap is not None:
520 mode = modemap.get(fn, mode)
520 mode = modemap.get(fn, mode)
521 if mode == 'wb':
521 if mode == 'wb':
522 modemap[fn] = 'ab'
522 modemap[fn] = 'ab'
523 return open(fn, mode)
523 return open(fn, mode)
524
524
525 def openrevlog(repo, cmd, file_, opts):
525 def openrevlog(repo, cmd, file_, opts):
526 """opens the changelog, manifest, a filelog or a given revlog"""
526 """opens the changelog, manifest, a filelog or a given revlog"""
527 cl = opts['changelog']
527 cl = opts['changelog']
528 mf = opts['manifest']
528 mf = opts['manifest']
529 dir = opts['dir']
529 dir = opts['dir']
530 msg = None
530 msg = None
531 if cl and mf:
531 if cl and mf:
532 msg = _('cannot specify --changelog and --manifest at the same time')
532 msg = _('cannot specify --changelog and --manifest at the same time')
533 elif cl and dir:
533 elif cl and dir:
534 msg = _('cannot specify --changelog and --dir at the same time')
534 msg = _('cannot specify --changelog and --dir at the same time')
535 elif cl or mf:
535 elif cl or mf:
536 if file_:
536 if file_:
537 msg = _('cannot specify filename with --changelog or --manifest')
537 msg = _('cannot specify filename with --changelog or --manifest')
538 elif not repo:
538 elif not repo:
539 msg = _('cannot specify --changelog or --manifest or --dir '
539 msg = _('cannot specify --changelog or --manifest or --dir '
540 'without a repository')
540 'without a repository')
541 if msg:
541 if msg:
542 raise error.Abort(msg)
542 raise error.Abort(msg)
543
543
544 r = None
544 r = None
545 if repo:
545 if repo:
546 if cl:
546 if cl:
547 r = repo.unfiltered().changelog
547 r = repo.unfiltered().changelog
548 elif dir:
548 elif dir:
549 if 'treemanifest' not in repo.requirements:
549 if 'treemanifest' not in repo.requirements:
550 raise error.Abort(_("--dir can only be used on repos with "
550 raise error.Abort(_("--dir can only be used on repos with "
551 "treemanifest enabled"))
551 "treemanifest enabled"))
552 dirlog = repo.dirlog(file_)
552 dirlog = repo.dirlog(file_)
553 if len(dirlog):
553 if len(dirlog):
554 r = dirlog
554 r = dirlog
555 elif mf:
555 elif mf:
556 r = repo.manifest
556 r = repo.manifest
557 elif file_:
557 elif file_:
558 filelog = repo.file(file_)
558 filelog = repo.file(file_)
559 if len(filelog):
559 if len(filelog):
560 r = filelog
560 r = filelog
561 if not r:
561 if not r:
562 if not file_:
562 if not file_:
563 raise error.CommandError(cmd, _('invalid arguments'))
563 raise error.CommandError(cmd, _('invalid arguments'))
564 if not os.path.isfile(file_):
564 if not os.path.isfile(file_):
565 raise error.Abort(_("revlog '%s' not found") % file_)
565 raise error.Abort(_("revlog '%s' not found") % file_)
566 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
566 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
567 file_[:-2] + ".i")
567 file_[:-2] + ".i")
568 return r
568 return r
569
569
570 def copy(ui, repo, pats, opts, rename=False):
570 def copy(ui, repo, pats, opts, rename=False):
571 # called with the repo lock held
571 # called with the repo lock held
572 #
572 #
573 # hgsep => pathname that uses "/" to separate directories
573 # hgsep => pathname that uses "/" to separate directories
574 # ossep => pathname that uses os.sep to separate directories
574 # ossep => pathname that uses os.sep to separate directories
575 cwd = repo.getcwd()
575 cwd = repo.getcwd()
576 targets = {}
576 targets = {}
577 after = opts.get("after")
577 after = opts.get("after")
578 dryrun = opts.get("dry_run")
578 dryrun = opts.get("dry_run")
579 wctx = repo[None]
579 wctx = repo[None]
580
580
581 def walkpat(pat):
581 def walkpat(pat):
582 srcs = []
582 srcs = []
583 if after:
583 if after:
584 badstates = '?'
584 badstates = '?'
585 else:
585 else:
586 badstates = '?r'
586 badstates = '?r'
587 m = scmutil.match(repo[None], [pat], opts, globbed=True)
587 m = scmutil.match(repo[None], [pat], opts, globbed=True)
588 for abs in repo.walk(m):
588 for abs in repo.walk(m):
589 state = repo.dirstate[abs]
589 state = repo.dirstate[abs]
590 rel = m.rel(abs)
590 rel = m.rel(abs)
591 exact = m.exact(abs)
591 exact = m.exact(abs)
592 if state in badstates:
592 if state in badstates:
593 if exact and state == '?':
593 if exact and state == '?':
594 ui.warn(_('%s: not copying - file is not managed\n') % rel)
594 ui.warn(_('%s: not copying - file is not managed\n') % rel)
595 if exact and state == 'r':
595 if exact and state == 'r':
596 ui.warn(_('%s: not copying - file has been marked for'
596 ui.warn(_('%s: not copying - file has been marked for'
597 ' remove\n') % rel)
597 ' remove\n') % rel)
598 continue
598 continue
599 # abs: hgsep
599 # abs: hgsep
600 # rel: ossep
600 # rel: ossep
601 srcs.append((abs, rel, exact))
601 srcs.append((abs, rel, exact))
602 return srcs
602 return srcs
603
603
604 # abssrc: hgsep
604 # abssrc: hgsep
605 # relsrc: ossep
605 # relsrc: ossep
606 # otarget: ossep
606 # otarget: ossep
607 def copyfile(abssrc, relsrc, otarget, exact):
607 def copyfile(abssrc, relsrc, otarget, exact):
608 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
608 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
609 if '/' in abstarget:
609 if '/' in abstarget:
610 # We cannot normalize abstarget itself, this would prevent
610 # We cannot normalize abstarget itself, this would prevent
611 # case only renames, like a => A.
611 # case only renames, like a => A.
612 abspath, absname = abstarget.rsplit('/', 1)
612 abspath, absname = abstarget.rsplit('/', 1)
613 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
613 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
614 reltarget = repo.pathto(abstarget, cwd)
614 reltarget = repo.pathto(abstarget, cwd)
615 target = repo.wjoin(abstarget)
615 target = repo.wjoin(abstarget)
616 src = repo.wjoin(abssrc)
616 src = repo.wjoin(abssrc)
617 state = repo.dirstate[abstarget]
617 state = repo.dirstate[abstarget]
618
618
619 scmutil.checkportable(ui, abstarget)
619 scmutil.checkportable(ui, abstarget)
620
620
621 # check for collisions
621 # check for collisions
622 prevsrc = targets.get(abstarget)
622 prevsrc = targets.get(abstarget)
623 if prevsrc is not None:
623 if prevsrc is not None:
624 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
624 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
625 (reltarget, repo.pathto(abssrc, cwd),
625 (reltarget, repo.pathto(abssrc, cwd),
626 repo.pathto(prevsrc, cwd)))
626 repo.pathto(prevsrc, cwd)))
627 return
627 return
628
628
629 # check for overwrites
629 # check for overwrites
630 exists = os.path.lexists(target)
630 exists = os.path.lexists(target)
631 samefile = False
631 samefile = False
632 if exists and abssrc != abstarget:
632 if exists and abssrc != abstarget:
633 if (repo.dirstate.normalize(abssrc) ==
633 if (repo.dirstate.normalize(abssrc) ==
634 repo.dirstate.normalize(abstarget)):
634 repo.dirstate.normalize(abstarget)):
635 if not rename:
635 if not rename:
636 ui.warn(_("%s: can't copy - same file\n") % reltarget)
636 ui.warn(_("%s: can't copy - same file\n") % reltarget)
637 return
637 return
638 exists = False
638 exists = False
639 samefile = True
639 samefile = True
640
640
641 if not after and exists or after and state in 'mn':
641 if not after and exists or after and state in 'mn':
642 if not opts['force']:
642 if not opts['force']:
643 ui.warn(_('%s: not overwriting - file exists\n') %
643 ui.warn(_('%s: not overwriting - file exists\n') %
644 reltarget)
644 reltarget)
645 return
645 return
646
646
647 if after:
647 if after:
648 if not exists:
648 if not exists:
649 if rename:
649 if rename:
650 ui.warn(_('%s: not recording move - %s does not exist\n') %
650 ui.warn(_('%s: not recording move - %s does not exist\n') %
651 (relsrc, reltarget))
651 (relsrc, reltarget))
652 else:
652 else:
653 ui.warn(_('%s: not recording copy - %s does not exist\n') %
653 ui.warn(_('%s: not recording copy - %s does not exist\n') %
654 (relsrc, reltarget))
654 (relsrc, reltarget))
655 return
655 return
656 elif not dryrun:
656 elif not dryrun:
657 try:
657 try:
658 if exists:
658 if exists:
659 os.unlink(target)
659 os.unlink(target)
660 targetdir = os.path.dirname(target) or '.'
660 targetdir = os.path.dirname(target) or '.'
661 if not os.path.isdir(targetdir):
661 if not os.path.isdir(targetdir):
662 os.makedirs(targetdir)
662 os.makedirs(targetdir)
663 if samefile:
663 if samefile:
664 tmp = target + "~hgrename"
664 tmp = target + "~hgrename"
665 os.rename(src, tmp)
665 os.rename(src, tmp)
666 os.rename(tmp, target)
666 os.rename(tmp, target)
667 else:
667 else:
668 util.copyfile(src, target)
668 util.copyfile(src, target)
669 srcexists = True
669 srcexists = True
670 except IOError as inst:
670 except IOError as inst:
671 if inst.errno == errno.ENOENT:
671 if inst.errno == errno.ENOENT:
672 ui.warn(_('%s: deleted in working directory\n') % relsrc)
672 ui.warn(_('%s: deleted in working directory\n') % relsrc)
673 srcexists = False
673 srcexists = False
674 else:
674 else:
675 ui.warn(_('%s: cannot copy - %s\n') %
675 ui.warn(_('%s: cannot copy - %s\n') %
676 (relsrc, inst.strerror))
676 (relsrc, inst.strerror))
677 return True # report a failure
677 return True # report a failure
678
678
679 if ui.verbose or not exact:
679 if ui.verbose or not exact:
680 if rename:
680 if rename:
681 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
681 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
682 else:
682 else:
683 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
683 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
684
684
685 targets[abstarget] = abssrc
685 targets[abstarget] = abssrc
686
686
687 # fix up dirstate
687 # fix up dirstate
688 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
688 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
689 dryrun=dryrun, cwd=cwd)
689 dryrun=dryrun, cwd=cwd)
690 if rename and not dryrun:
690 if rename and not dryrun:
691 if not after and srcexists and not samefile:
691 if not after and srcexists and not samefile:
692 util.unlinkpath(repo.wjoin(abssrc))
692 util.unlinkpath(repo.wjoin(abssrc))
693 wctx.forget([abssrc])
693 wctx.forget([abssrc])
694
694
695 # pat: ossep
695 # pat: ossep
696 # dest ossep
696 # dest ossep
697 # srcs: list of (hgsep, hgsep, ossep, bool)
697 # srcs: list of (hgsep, hgsep, ossep, bool)
698 # return: function that takes hgsep and returns ossep
698 # return: function that takes hgsep and returns ossep
699 def targetpathfn(pat, dest, srcs):
699 def targetpathfn(pat, dest, srcs):
700 if os.path.isdir(pat):
700 if os.path.isdir(pat):
701 abspfx = pathutil.canonpath(repo.root, cwd, pat)
701 abspfx = pathutil.canonpath(repo.root, cwd, pat)
702 abspfx = util.localpath(abspfx)
702 abspfx = util.localpath(abspfx)
703 if destdirexists:
703 if destdirexists:
704 striplen = len(os.path.split(abspfx)[0])
704 striplen = len(os.path.split(abspfx)[0])
705 else:
705 else:
706 striplen = len(abspfx)
706 striplen = len(abspfx)
707 if striplen:
707 if striplen:
708 striplen += len(os.sep)
708 striplen += len(os.sep)
709 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
709 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
710 elif destdirexists:
710 elif destdirexists:
711 res = lambda p: os.path.join(dest,
711 res = lambda p: os.path.join(dest,
712 os.path.basename(util.localpath(p)))
712 os.path.basename(util.localpath(p)))
713 else:
713 else:
714 res = lambda p: dest
714 res = lambda p: dest
715 return res
715 return res
716
716
717 # pat: ossep
717 # pat: ossep
718 # dest ossep
718 # dest ossep
719 # srcs: list of (hgsep, hgsep, ossep, bool)
719 # srcs: list of (hgsep, hgsep, ossep, bool)
720 # return: function that takes hgsep and returns ossep
720 # return: function that takes hgsep and returns ossep
721 def targetpathafterfn(pat, dest, srcs):
721 def targetpathafterfn(pat, dest, srcs):
722 if matchmod.patkind(pat):
722 if matchmod.patkind(pat):
723 # a mercurial pattern
723 # a mercurial pattern
724 res = lambda p: os.path.join(dest,
724 res = lambda p: os.path.join(dest,
725 os.path.basename(util.localpath(p)))
725 os.path.basename(util.localpath(p)))
726 else:
726 else:
727 abspfx = pathutil.canonpath(repo.root, cwd, pat)
727 abspfx = pathutil.canonpath(repo.root, cwd, pat)
728 if len(abspfx) < len(srcs[0][0]):
728 if len(abspfx) < len(srcs[0][0]):
729 # A directory. Either the target path contains the last
729 # A directory. Either the target path contains the last
730 # component of the source path or it does not.
730 # component of the source path or it does not.
731 def evalpath(striplen):
731 def evalpath(striplen):
732 score = 0
732 score = 0
733 for s in srcs:
733 for s in srcs:
734 t = os.path.join(dest, util.localpath(s[0])[striplen:])
734 t = os.path.join(dest, util.localpath(s[0])[striplen:])
735 if os.path.lexists(t):
735 if os.path.lexists(t):
736 score += 1
736 score += 1
737 return score
737 return score
738
738
739 abspfx = util.localpath(abspfx)
739 abspfx = util.localpath(abspfx)
740 striplen = len(abspfx)
740 striplen = len(abspfx)
741 if striplen:
741 if striplen:
742 striplen += len(os.sep)
742 striplen += len(os.sep)
743 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
743 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
744 score = evalpath(striplen)
744 score = evalpath(striplen)
745 striplen1 = len(os.path.split(abspfx)[0])
745 striplen1 = len(os.path.split(abspfx)[0])
746 if striplen1:
746 if striplen1:
747 striplen1 += len(os.sep)
747 striplen1 += len(os.sep)
748 if evalpath(striplen1) > score:
748 if evalpath(striplen1) > score:
749 striplen = striplen1
749 striplen = striplen1
750 res = lambda p: os.path.join(dest,
750 res = lambda p: os.path.join(dest,
751 util.localpath(p)[striplen:])
751 util.localpath(p)[striplen:])
752 else:
752 else:
753 # a file
753 # a file
754 if destdirexists:
754 if destdirexists:
755 res = lambda p: os.path.join(dest,
755 res = lambda p: os.path.join(dest,
756 os.path.basename(util.localpath(p)))
756 os.path.basename(util.localpath(p)))
757 else:
757 else:
758 res = lambda p: dest
758 res = lambda p: dest
759 return res
759 return res
760
760
761 pats = scmutil.expandpats(pats)
761 pats = scmutil.expandpats(pats)
762 if not pats:
762 if not pats:
763 raise error.Abort(_('no source or destination specified'))
763 raise error.Abort(_('no source or destination specified'))
764 if len(pats) == 1:
764 if len(pats) == 1:
765 raise error.Abort(_('no destination specified'))
765 raise error.Abort(_('no destination specified'))
766 dest = pats.pop()
766 dest = pats.pop()
767 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
767 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
768 if not destdirexists:
768 if not destdirexists:
769 if len(pats) > 1 or matchmod.patkind(pats[0]):
769 if len(pats) > 1 or matchmod.patkind(pats[0]):
770 raise error.Abort(_('with multiple sources, destination must be an '
770 raise error.Abort(_('with multiple sources, destination must be an '
771 'existing directory'))
771 'existing directory'))
772 if util.endswithsep(dest):
772 if util.endswithsep(dest):
773 raise error.Abort(_('destination %s is not a directory') % dest)
773 raise error.Abort(_('destination %s is not a directory') % dest)
774
774
775 tfn = targetpathfn
775 tfn = targetpathfn
776 if after:
776 if after:
777 tfn = targetpathafterfn
777 tfn = targetpathafterfn
778 copylist = []
778 copylist = []
779 for pat in pats:
779 for pat in pats:
780 srcs = walkpat(pat)
780 srcs = walkpat(pat)
781 if not srcs:
781 if not srcs:
782 continue
782 continue
783 copylist.append((tfn(pat, dest, srcs), srcs))
783 copylist.append((tfn(pat, dest, srcs), srcs))
784 if not copylist:
784 if not copylist:
785 raise error.Abort(_('no files to copy'))
785 raise error.Abort(_('no files to copy'))
786
786
787 errors = 0
787 errors = 0
788 for targetpath, srcs in copylist:
788 for targetpath, srcs in copylist:
789 for abssrc, relsrc, exact in srcs:
789 for abssrc, relsrc, exact in srcs:
790 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
790 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
791 errors += 1
791 errors += 1
792
792
793 if errors:
793 if errors:
794 ui.warn(_('(consider using --after)\n'))
794 ui.warn(_('(consider using --after)\n'))
795
795
796 return errors != 0
796 return errors != 0
797
797
798 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
798 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
799 runargs=None, appendpid=False):
799 runargs=None, appendpid=False):
800 '''Run a command as a service.'''
800 '''Run a command as a service.'''
801
801
802 def writepid(pid):
802 def writepid(pid):
803 if opts['pid_file']:
803 if opts['pid_file']:
804 if appendpid:
804 if appendpid:
805 mode = 'a'
805 mode = 'a'
806 else:
806 else:
807 mode = 'w'
807 mode = 'w'
808 fp = open(opts['pid_file'], mode)
808 fp = open(opts['pid_file'], mode)
809 fp.write(str(pid) + '\n')
809 fp.write(str(pid) + '\n')
810 fp.close()
810 fp.close()
811
811
812 if opts['daemon'] and not opts['daemon_postexec']:
812 if opts['daemon'] and not opts['daemon_postexec']:
813 # Signal child process startup with file removal
813 # Signal child process startup with file removal
814 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
814 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
815 os.close(lockfd)
815 os.close(lockfd)
816 try:
816 try:
817 if not runargs:
817 if not runargs:
818 runargs = util.hgcmd() + sys.argv[1:]
818 runargs = util.hgcmd() + sys.argv[1:]
819 runargs.append('--daemon-postexec=unlink:%s' % lockpath)
819 runargs.append('--daemon-postexec=unlink:%s' % lockpath)
820 # Don't pass --cwd to the child process, because we've already
820 # Don't pass --cwd to the child process, because we've already
821 # changed directory.
821 # changed directory.
822 for i in xrange(1, len(runargs)):
822 for i in xrange(1, len(runargs)):
823 if runargs[i].startswith('--cwd='):
823 if runargs[i].startswith('--cwd='):
824 del runargs[i]
824 del runargs[i]
825 break
825 break
826 elif runargs[i].startswith('--cwd'):
826 elif runargs[i].startswith('--cwd'):
827 del runargs[i:i + 2]
827 del runargs[i:i + 2]
828 break
828 break
829 def condfn():
829 def condfn():
830 return not os.path.exists(lockpath)
830 return not os.path.exists(lockpath)
831 pid = util.rundetached(runargs, condfn)
831 pid = util.rundetached(runargs, condfn)
832 if pid < 0:
832 if pid < 0:
833 raise error.Abort(_('child process failed to start'))
833 raise error.Abort(_('child process failed to start'))
834 writepid(pid)
834 writepid(pid)
835 finally:
835 finally:
836 try:
836 try:
837 os.unlink(lockpath)
837 os.unlink(lockpath)
838 except OSError as e:
838 except OSError as e:
839 if e.errno != errno.ENOENT:
839 if e.errno != errno.ENOENT:
840 raise
840 raise
841 if parentfn:
841 if parentfn:
842 return parentfn(pid)
842 return parentfn(pid)
843 else:
843 else:
844 return
844 return
845
845
846 if initfn:
846 if initfn:
847 initfn()
847 initfn()
848
848
849 if not opts['daemon']:
849 if not opts['daemon']:
850 writepid(util.getpid())
850 writepid(util.getpid())
851
851
852 if opts['daemon_postexec']:
852 if opts['daemon_postexec']:
853 try:
853 try:
854 os.setsid()
854 os.setsid()
855 except AttributeError:
855 except AttributeError:
856 pass
856 pass
857 for inst in opts['daemon_postexec']:
857 for inst in opts['daemon_postexec']:
858 if inst.startswith('unlink:'):
858 if inst.startswith('unlink:'):
859 lockpath = inst[7:]
859 lockpath = inst[7:]
860 os.unlink(lockpath)
860 os.unlink(lockpath)
861 elif inst.startswith('chdir:'):
861 elif inst.startswith('chdir:'):
862 os.chdir(inst[6:])
862 os.chdir(inst[6:])
863 elif inst != 'none':
863 elif inst != 'none':
864 raise error.Abort(_('invalid value for --daemon-postexec: %s')
864 raise error.Abort(_('invalid value for --daemon-postexec: %s')
865 % inst)
865 % inst)
866 util.hidewindow()
866 util.hidewindow()
867 sys.stdout.flush()
867 sys.stdout.flush()
868 sys.stderr.flush()
868 sys.stderr.flush()
869
869
870 nullfd = os.open(os.devnull, os.O_RDWR)
870 nullfd = os.open(os.devnull, os.O_RDWR)
871 logfilefd = nullfd
871 logfilefd = nullfd
872 if logfile:
872 if logfile:
873 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
873 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
874 os.dup2(nullfd, 0)
874 os.dup2(nullfd, 0)
875 os.dup2(logfilefd, 1)
875 os.dup2(logfilefd, 1)
876 os.dup2(logfilefd, 2)
876 os.dup2(logfilefd, 2)
877 if nullfd not in (0, 1, 2):
877 if nullfd not in (0, 1, 2):
878 os.close(nullfd)
878 os.close(nullfd)
879 if logfile and logfilefd not in (0, 1, 2):
879 if logfile and logfilefd not in (0, 1, 2):
880 os.close(logfilefd)
880 os.close(logfilefd)
881
881
882 if runfn:
882 if runfn:
883 return runfn()
883 return runfn()
884
884
885 ## facility to let extension process additional data into an import patch
885 ## facility to let extension process additional data into an import patch
886 # list of identifier to be executed in order
886 # list of identifier to be executed in order
887 extrapreimport = [] # run before commit
887 extrapreimport = [] # run before commit
888 extrapostimport = [] # run after commit
888 extrapostimport = [] # run after commit
889 # mapping from identifier to actual import function
889 # mapping from identifier to actual import function
890 #
890 #
891 # 'preimport' are run before the commit is made and are provided the following
891 # 'preimport' are run before the commit is made and are provided the following
892 # arguments:
892 # arguments:
893 # - repo: the localrepository instance,
893 # - repo: the localrepository instance,
894 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
894 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
895 # - extra: the future extra dictionary of the changeset, please mutate it,
895 # - extra: the future extra dictionary of the changeset, please mutate it,
896 # - opts: the import options.
896 # - opts: the import options.
897 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
897 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
898 # mutation of in memory commit and more. Feel free to rework the code to get
898 # mutation of in memory commit and more. Feel free to rework the code to get
899 # there.
899 # there.
900 extrapreimportmap = {}
900 extrapreimportmap = {}
901 # 'postimport' are run after the commit is made and are provided the following
901 # 'postimport' are run after the commit is made and are provided the following
902 # argument:
902 # argument:
903 # - ctx: the changectx created by import.
903 # - ctx: the changectx created by import.
904 extrapostimportmap = {}
904 extrapostimportmap = {}
905
905
906 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
906 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
907 """Utility function used by commands.import to import a single patch
907 """Utility function used by commands.import to import a single patch
908
908
909 This function is explicitly defined here to help the evolve extension to
909 This function is explicitly defined here to help the evolve extension to
910 wrap this part of the import logic.
910 wrap this part of the import logic.
911
911
912 The API is currently a bit ugly because it a simple code translation from
912 The API is currently a bit ugly because it a simple code translation from
913 the import command. Feel free to make it better.
913 the import command. Feel free to make it better.
914
914
915 :hunk: a patch (as a binary string)
915 :hunk: a patch (as a binary string)
916 :parents: nodes that will be parent of the created commit
916 :parents: nodes that will be parent of the created commit
917 :opts: the full dict of option passed to the import command
917 :opts: the full dict of option passed to the import command
918 :msgs: list to save commit message to.
918 :msgs: list to save commit message to.
919 (used in case we need to save it when failing)
919 (used in case we need to save it when failing)
920 :updatefunc: a function that update a repo to a given node
920 :updatefunc: a function that update a repo to a given node
921 updatefunc(<repo>, <node>)
921 updatefunc(<repo>, <node>)
922 """
922 """
923 # avoid cycle context -> subrepo -> cmdutil
923 # avoid cycle context -> subrepo -> cmdutil
924 from . import context
924 from . import context
925 extractdata = patch.extract(ui, hunk)
925 extractdata = patch.extract(ui, hunk)
926 tmpname = extractdata.get('filename')
926 tmpname = extractdata.get('filename')
927 message = extractdata.get('message')
927 message = extractdata.get('message')
928 user = opts.get('user') or extractdata.get('user')
928 user = opts.get('user') or extractdata.get('user')
929 date = opts.get('date') or extractdata.get('date')
929 date = opts.get('date') or extractdata.get('date')
930 branch = extractdata.get('branch')
930 branch = extractdata.get('branch')
931 nodeid = extractdata.get('nodeid')
931 nodeid = extractdata.get('nodeid')
932 p1 = extractdata.get('p1')
932 p1 = extractdata.get('p1')
933 p2 = extractdata.get('p2')
933 p2 = extractdata.get('p2')
934
934
935 nocommit = opts.get('no_commit')
935 nocommit = opts.get('no_commit')
936 importbranch = opts.get('import_branch')
936 importbranch = opts.get('import_branch')
937 update = not opts.get('bypass')
937 update = not opts.get('bypass')
938 strip = opts["strip"]
938 strip = opts["strip"]
939 prefix = opts["prefix"]
939 prefix = opts["prefix"]
940 sim = float(opts.get('similarity') or 0)
940 sim = float(opts.get('similarity') or 0)
941 if not tmpname:
941 if not tmpname:
942 return (None, None, False)
942 return (None, None, False)
943
943
944 rejects = False
944 rejects = False
945
945
946 try:
946 try:
947 cmdline_message = logmessage(ui, opts)
947 cmdline_message = logmessage(ui, opts)
948 if cmdline_message:
948 if cmdline_message:
949 # pickup the cmdline msg
949 # pickup the cmdline msg
950 message = cmdline_message
950 message = cmdline_message
951 elif message:
951 elif message:
952 # pickup the patch msg
952 # pickup the patch msg
953 message = message.strip()
953 message = message.strip()
954 else:
954 else:
955 # launch the editor
955 # launch the editor
956 message = None
956 message = None
957 ui.debug('message:\n%s\n' % message)
957 ui.debug('message:\n%s\n' % message)
958
958
959 if len(parents) == 1:
959 if len(parents) == 1:
960 parents.append(repo[nullid])
960 parents.append(repo[nullid])
961 if opts.get('exact'):
961 if opts.get('exact'):
962 if not nodeid or not p1:
962 if not nodeid or not p1:
963 raise error.Abort(_('not a Mercurial patch'))
963 raise error.Abort(_('not a Mercurial patch'))
964 p1 = repo[p1]
964 p1 = repo[p1]
965 p2 = repo[p2 or nullid]
965 p2 = repo[p2 or nullid]
966 elif p2:
966 elif p2:
967 try:
967 try:
968 p1 = repo[p1]
968 p1 = repo[p1]
969 p2 = repo[p2]
969 p2 = repo[p2]
970 # Without any options, consider p2 only if the
970 # Without any options, consider p2 only if the
971 # patch is being applied on top of the recorded
971 # patch is being applied on top of the recorded
972 # first parent.
972 # first parent.
973 if p1 != parents[0]:
973 if p1 != parents[0]:
974 p1 = parents[0]
974 p1 = parents[0]
975 p2 = repo[nullid]
975 p2 = repo[nullid]
976 except error.RepoError:
976 except error.RepoError:
977 p1, p2 = parents
977 p1, p2 = parents
978 if p2.node() == nullid:
978 if p2.node() == nullid:
979 ui.warn(_("warning: import the patch as a normal revision\n"
979 ui.warn(_("warning: import the patch as a normal revision\n"
980 "(use --exact to import the patch as a merge)\n"))
980 "(use --exact to import the patch as a merge)\n"))
981 else:
981 else:
982 p1, p2 = parents
982 p1, p2 = parents
983
983
984 n = None
984 n = None
985 if update:
985 if update:
986 if p1 != parents[0]:
986 if p1 != parents[0]:
987 updatefunc(repo, p1.node())
987 updatefunc(repo, p1.node())
988 if p2 != parents[1]:
988 if p2 != parents[1]:
989 repo.setparents(p1.node(), p2.node())
989 repo.setparents(p1.node(), p2.node())
990
990
991 if opts.get('exact') or importbranch:
991 if opts.get('exact') or importbranch:
992 repo.dirstate.setbranch(branch or 'default')
992 repo.dirstate.setbranch(branch or 'default')
993
993
994 partial = opts.get('partial', False)
994 partial = opts.get('partial', False)
995 files = set()
995 files = set()
996 try:
996 try:
997 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
997 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
998 files=files, eolmode=None, similarity=sim / 100.0)
998 files=files, eolmode=None, similarity=sim / 100.0)
999 except patch.PatchError as e:
999 except patch.PatchError as e:
1000 if not partial:
1000 if not partial:
1001 raise error.Abort(str(e))
1001 raise error.Abort(str(e))
1002 if partial:
1002 if partial:
1003 rejects = True
1003 rejects = True
1004
1004
1005 files = list(files)
1005 files = list(files)
1006 if nocommit:
1006 if nocommit:
1007 if message:
1007 if message:
1008 msgs.append(message)
1008 msgs.append(message)
1009 else:
1009 else:
1010 if opts.get('exact') or p2:
1010 if opts.get('exact') or p2:
1011 # If you got here, you either use --force and know what
1011 # If you got here, you either use --force and know what
1012 # you are doing or used --exact or a merge patch while
1012 # you are doing or used --exact or a merge patch while
1013 # being updated to its first parent.
1013 # being updated to its first parent.
1014 m = None
1014 m = None
1015 else:
1015 else:
1016 m = scmutil.matchfiles(repo, files or [])
1016 m = scmutil.matchfiles(repo, files or [])
1017 editform = mergeeditform(repo[None], 'import.normal')
1017 editform = mergeeditform(repo[None], 'import.normal')
1018 if opts.get('exact'):
1018 if opts.get('exact'):
1019 editor = None
1019 editor = None
1020 else:
1020 else:
1021 editor = getcommiteditor(editform=editform, **opts)
1021 editor = getcommiteditor(editform=editform, **opts)
1022 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
1022 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
1023 extra = {}
1023 extra = {}
1024 for idfunc in extrapreimport:
1024 for idfunc in extrapreimport:
1025 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1025 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1026 try:
1026 try:
1027 if partial:
1027 if partial:
1028 repo.ui.setconfig('ui', 'allowemptycommit', True)
1028 repo.ui.setconfig('ui', 'allowemptycommit', True)
1029 n = repo.commit(message, user,
1029 n = repo.commit(message, user,
1030 date, match=m,
1030 date, match=m,
1031 editor=editor, extra=extra)
1031 editor=editor, extra=extra)
1032 for idfunc in extrapostimport:
1032 for idfunc in extrapostimport:
1033 extrapostimportmap[idfunc](repo[n])
1033 extrapostimportmap[idfunc](repo[n])
1034 finally:
1034 finally:
1035 repo.ui.restoreconfig(allowemptyback)
1035 repo.ui.restoreconfig(allowemptyback)
1036 else:
1036 else:
1037 if opts.get('exact') or importbranch:
1037 if opts.get('exact') or importbranch:
1038 branch = branch or 'default'
1038 branch = branch or 'default'
1039 else:
1039 else:
1040 branch = p1.branch()
1040 branch = p1.branch()
1041 store = patch.filestore()
1041 store = patch.filestore()
1042 try:
1042 try:
1043 files = set()
1043 files = set()
1044 try:
1044 try:
1045 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1045 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1046 files, eolmode=None)
1046 files, eolmode=None)
1047 except patch.PatchError as e:
1047 except patch.PatchError as e:
1048 raise error.Abort(str(e))
1048 raise error.Abort(str(e))
1049 if opts.get('exact'):
1049 if opts.get('exact'):
1050 editor = None
1050 editor = None
1051 else:
1051 else:
1052 editor = getcommiteditor(editform='import.bypass')
1052 editor = getcommiteditor(editform='import.bypass')
1053 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1053 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1054 message,
1054 message,
1055 user,
1055 user,
1056 date,
1056 date,
1057 branch, files, store,
1057 branch, files, store,
1058 editor=editor)
1058 editor=editor)
1059 n = memctx.commit()
1059 n = memctx.commit()
1060 finally:
1060 finally:
1061 store.close()
1061 store.close()
1062 if opts.get('exact') and nocommit:
1062 if opts.get('exact') and nocommit:
1063 # --exact with --no-commit is still useful in that it does merge
1063 # --exact with --no-commit is still useful in that it does merge
1064 # and branch bits
1064 # and branch bits
1065 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1065 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1066 elif opts.get('exact') and hex(n) != nodeid:
1066 elif opts.get('exact') and hex(n) != nodeid:
1067 raise error.Abort(_('patch is damaged or loses information'))
1067 raise error.Abort(_('patch is damaged or loses information'))
1068 msg = _('applied to working directory')
1068 msg = _('applied to working directory')
1069 if n:
1069 if n:
1070 # i18n: refers to a short changeset id
1070 # i18n: refers to a short changeset id
1071 msg = _('created %s') % short(n)
1071 msg = _('created %s') % short(n)
1072 return (msg, n, rejects)
1072 return (msg, n, rejects)
1073 finally:
1073 finally:
1074 os.unlink(tmpname)
1074 os.unlink(tmpname)
1075
1075
1076 # facility to let extensions include additional data in an exported patch
1076 # facility to let extensions include additional data in an exported patch
1077 # list of identifiers to be executed in order
1077 # list of identifiers to be executed in order
1078 extraexport = []
1078 extraexport = []
1079 # mapping from identifier to actual export function
1079 # mapping from identifier to actual export function
1080 # function as to return a string to be added to the header or None
1080 # function as to return a string to be added to the header or None
1081 # it is given two arguments (sequencenumber, changectx)
1081 # it is given two arguments (sequencenumber, changectx)
1082 extraexportmap = {}
1082 extraexportmap = {}
1083
1083
1084 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1084 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1085 opts=None, match=None):
1085 opts=None, match=None):
1086 '''export changesets as hg patches.'''
1086 '''export changesets as hg patches.'''
1087
1087
1088 total = len(revs)
1088 total = len(revs)
1089 revwidth = max([len(str(rev)) for rev in revs])
1089 revwidth = max([len(str(rev)) for rev in revs])
1090 filemode = {}
1090 filemode = {}
1091
1091
1092 def single(rev, seqno, fp):
1092 def single(rev, seqno, fp):
1093 ctx = repo[rev]
1093 ctx = repo[rev]
1094 node = ctx.node()
1094 node = ctx.node()
1095 parents = [p.node() for p in ctx.parents() if p]
1095 parents = [p.node() for p in ctx.parents() if p]
1096 branch = ctx.branch()
1096 branch = ctx.branch()
1097 if switch_parent:
1097 if switch_parent:
1098 parents.reverse()
1098 parents.reverse()
1099
1099
1100 if parents:
1100 if parents:
1101 prev = parents[0]
1101 prev = parents[0]
1102 else:
1102 else:
1103 prev = nullid
1103 prev = nullid
1104
1104
1105 shouldclose = False
1105 shouldclose = False
1106 if not fp and len(template) > 0:
1106 if not fp and len(template) > 0:
1107 desc_lines = ctx.description().rstrip().split('\n')
1107 desc_lines = ctx.description().rstrip().split('\n')
1108 desc = desc_lines[0] #Commit always has a first line.
1108 desc = desc_lines[0] #Commit always has a first line.
1109 fp = makefileobj(repo, template, node, desc=desc, total=total,
1109 fp = makefileobj(repo, template, node, desc=desc, total=total,
1110 seqno=seqno, revwidth=revwidth, mode='wb',
1110 seqno=seqno, revwidth=revwidth, mode='wb',
1111 modemap=filemode)
1111 modemap=filemode)
1112 shouldclose = True
1112 shouldclose = True
1113 if fp and not getattr(fp, 'name', '<unnamed>').startswith('<'):
1113 if fp and not getattr(fp, 'name', '<unnamed>').startswith('<'):
1114 repo.ui.note("%s\n" % fp.name)
1114 repo.ui.note("%s\n" % fp.name)
1115
1115
1116 if not fp:
1116 if not fp:
1117 write = repo.ui.write
1117 write = repo.ui.write
1118 else:
1118 else:
1119 def write(s, **kw):
1119 def write(s, **kw):
1120 fp.write(s)
1120 fp.write(s)
1121
1121
1122 write("# HG changeset patch\n")
1122 write("# HG changeset patch\n")
1123 write("# User %s\n" % ctx.user())
1123 write("# User %s\n" % ctx.user())
1124 write("# Date %d %d\n" % ctx.date())
1124 write("# Date %d %d\n" % ctx.date())
1125 write("# %s\n" % util.datestr(ctx.date()))
1125 write("# %s\n" % util.datestr(ctx.date()))
1126 if branch and branch != 'default':
1126 if branch and branch != 'default':
1127 write("# Branch %s\n" % branch)
1127 write("# Branch %s\n" % branch)
1128 write("# Node ID %s\n" % hex(node))
1128 write("# Node ID %s\n" % hex(node))
1129 write("# Parent %s\n" % hex(prev))
1129 write("# Parent %s\n" % hex(prev))
1130 if len(parents) > 1:
1130 if len(parents) > 1:
1131 write("# Parent %s\n" % hex(parents[1]))
1131 write("# Parent %s\n" % hex(parents[1]))
1132
1132
1133 for headerid in extraexport:
1133 for headerid in extraexport:
1134 header = extraexportmap[headerid](seqno, ctx)
1134 header = extraexportmap[headerid](seqno, ctx)
1135 if header is not None:
1135 if header is not None:
1136 write('# %s\n' % header)
1136 write('# %s\n' % header)
1137 write(ctx.description().rstrip())
1137 write(ctx.description().rstrip())
1138 write("\n\n")
1138 write("\n\n")
1139
1139
1140 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1140 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1141 write(chunk, label=label)
1141 write(chunk, label=label)
1142
1142
1143 if shouldclose:
1143 if shouldclose:
1144 fp.close()
1144 fp.close()
1145
1145
1146 for seqno, rev in enumerate(revs):
1146 for seqno, rev in enumerate(revs):
1147 single(rev, seqno + 1, fp)
1147 single(rev, seqno + 1, fp)
1148
1148
1149 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1149 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1150 changes=None, stat=False, fp=None, prefix='',
1150 changes=None, stat=False, fp=None, prefix='',
1151 root='', listsubrepos=False):
1151 root='', listsubrepos=False):
1152 '''show diff or diffstat.'''
1152 '''show diff or diffstat.'''
1153 if fp is None:
1153 if fp is None:
1154 write = ui.write
1154 write = ui.write
1155 else:
1155 else:
1156 def write(s, **kw):
1156 def write(s, **kw):
1157 fp.write(s)
1157 fp.write(s)
1158
1158
1159 if root:
1159 if root:
1160 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1160 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1161 else:
1161 else:
1162 relroot = ''
1162 relroot = ''
1163 if relroot != '':
1163 if relroot != '':
1164 # XXX relative roots currently don't work if the root is within a
1164 # XXX relative roots currently don't work if the root is within a
1165 # subrepo
1165 # subrepo
1166 uirelroot = match.uipath(relroot)
1166 uirelroot = match.uipath(relroot)
1167 relroot += '/'
1167 relroot += '/'
1168 for matchroot in match.files():
1168 for matchroot in match.files():
1169 if not matchroot.startswith(relroot):
1169 if not matchroot.startswith(relroot):
1170 ui.warn(_('warning: %s not inside relative root %s\n') % (
1170 ui.warn(_('warning: %s not inside relative root %s\n') % (
1171 match.uipath(matchroot), uirelroot))
1171 match.uipath(matchroot), uirelroot))
1172
1172
1173 if stat:
1173 if stat:
1174 diffopts = diffopts.copy(context=0)
1174 diffopts = diffopts.copy(context=0)
1175 width = 80
1175 width = 80
1176 if not ui.plain():
1176 if not ui.plain():
1177 width = ui.termwidth()
1177 width = ui.termwidth()
1178 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1178 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1179 prefix=prefix, relroot=relroot)
1179 prefix=prefix, relroot=relroot)
1180 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1180 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1181 width=width,
1181 width=width,
1182 git=diffopts.git):
1182 git=diffopts.git):
1183 write(chunk, label=label)
1183 write(chunk, label=label)
1184 else:
1184 else:
1185 for chunk, label in patch.diffui(repo, node1, node2, match,
1185 for chunk, label in patch.diffui(repo, node1, node2, match,
1186 changes, diffopts, prefix=prefix,
1186 changes, diffopts, prefix=prefix,
1187 relroot=relroot):
1187 relroot=relroot):
1188 write(chunk, label=label)
1188 write(chunk, label=label)
1189
1189
1190 if listsubrepos:
1190 if listsubrepos:
1191 ctx1 = repo[node1]
1191 ctx1 = repo[node1]
1192 ctx2 = repo[node2]
1192 ctx2 = repo[node2]
1193 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1193 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1194 tempnode2 = node2
1194 tempnode2 = node2
1195 try:
1195 try:
1196 if node2 is not None:
1196 if node2 is not None:
1197 tempnode2 = ctx2.substate[subpath][1]
1197 tempnode2 = ctx2.substate[subpath][1]
1198 except KeyError:
1198 except KeyError:
1199 # A subrepo that existed in node1 was deleted between node1 and
1199 # A subrepo that existed in node1 was deleted between node1 and
1200 # node2 (inclusive). Thus, ctx2's substate won't contain that
1200 # node2 (inclusive). Thus, ctx2's substate won't contain that
1201 # subpath. The best we can do is to ignore it.
1201 # subpath. The best we can do is to ignore it.
1202 tempnode2 = None
1202 tempnode2 = None
1203 submatch = matchmod.subdirmatcher(subpath, match)
1203 submatch = matchmod.subdirmatcher(subpath, match)
1204 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1204 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1205 stat=stat, fp=fp, prefix=prefix)
1205 stat=stat, fp=fp, prefix=prefix)
1206
1206
1207 class changeset_printer(object):
1207 class changeset_printer(object):
1208 '''show changeset information when templating not requested.'''
1208 '''show changeset information when templating not requested.'''
1209
1209
1210 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1210 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1211 self.ui = ui
1211 self.ui = ui
1212 self.repo = repo
1212 self.repo = repo
1213 self.buffered = buffered
1213 self.buffered = buffered
1214 self.matchfn = matchfn
1214 self.matchfn = matchfn
1215 self.diffopts = diffopts
1215 self.diffopts = diffopts
1216 self.header = {}
1216 self.header = {}
1217 self.hunk = {}
1217 self.hunk = {}
1218 self.lastheader = None
1218 self.lastheader = None
1219 self.footer = None
1219 self.footer = None
1220
1220
1221 def flush(self, ctx):
1221 def flush(self, ctx):
1222 rev = ctx.rev()
1222 rev = ctx.rev()
1223 if rev in self.header:
1223 if rev in self.header:
1224 h = self.header[rev]
1224 h = self.header[rev]
1225 if h != self.lastheader:
1225 if h != self.lastheader:
1226 self.lastheader = h
1226 self.lastheader = h
1227 self.ui.write(h)
1227 self.ui.write(h)
1228 del self.header[rev]
1228 del self.header[rev]
1229 if rev in self.hunk:
1229 if rev in self.hunk:
1230 self.ui.write(self.hunk[rev])
1230 self.ui.write(self.hunk[rev])
1231 del self.hunk[rev]
1231 del self.hunk[rev]
1232 return 1
1232 return 1
1233 return 0
1233 return 0
1234
1234
1235 def close(self):
1235 def close(self):
1236 if self.footer:
1236 if self.footer:
1237 self.ui.write(self.footer)
1237 self.ui.write(self.footer)
1238
1238
1239 def show(self, ctx, copies=None, matchfn=None, **props):
1239 def show(self, ctx, copies=None, matchfn=None, **props):
1240 if self.buffered:
1240 if self.buffered:
1241 self.ui.pushbuffer(labeled=True)
1241 self.ui.pushbuffer(labeled=True)
1242 self._show(ctx, copies, matchfn, props)
1242 self._show(ctx, copies, matchfn, props)
1243 self.hunk[ctx.rev()] = self.ui.popbuffer()
1243 self.hunk[ctx.rev()] = self.ui.popbuffer()
1244 else:
1244 else:
1245 self._show(ctx, copies, matchfn, props)
1245 self._show(ctx, copies, matchfn, props)
1246
1246
1247 def _show(self, ctx, copies, matchfn, props):
1247 def _show(self, ctx, copies, matchfn, props):
1248 '''show a single changeset or file revision'''
1248 '''show a single changeset or file revision'''
1249 changenode = ctx.node()
1249 changenode = ctx.node()
1250 rev = ctx.rev()
1250 rev = ctx.rev()
1251 if self.ui.debugflag:
1251 if self.ui.debugflag:
1252 hexfunc = hex
1252 hexfunc = hex
1253 else:
1253 else:
1254 hexfunc = short
1254 hexfunc = short
1255 # as of now, wctx.node() and wctx.rev() return None, but we want to
1255 # as of now, wctx.node() and wctx.rev() return None, but we want to
1256 # show the same values as {node} and {rev} templatekw
1256 # show the same values as {node} and {rev} templatekw
1257 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1257 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1258
1258
1259 if self.ui.quiet:
1259 if self.ui.quiet:
1260 self.ui.write("%d:%s\n" % revnode, label='log.node')
1260 self.ui.write("%d:%s\n" % revnode, label='log.node')
1261 return
1261 return
1262
1262
1263 date = util.datestr(ctx.date())
1263 date = util.datestr(ctx.date())
1264
1264
1265 # i18n: column positioning for "hg log"
1265 # i18n: column positioning for "hg log"
1266 self.ui.write(_("changeset: %d:%s\n") % revnode,
1266 self.ui.write(_("changeset: %d:%s\n") % revnode,
1267 label='log.changeset changeset.%s' % ctx.phasestr())
1267 label='log.changeset changeset.%s' % ctx.phasestr())
1268
1268
1269 # branches are shown first before any other names due to backwards
1269 # branches are shown first before any other names due to backwards
1270 # compatibility
1270 # compatibility
1271 branch = ctx.branch()
1271 branch = ctx.branch()
1272 # don't show the default branch name
1272 # don't show the default branch name
1273 if branch != 'default':
1273 if branch != 'default':
1274 # i18n: column positioning for "hg log"
1274 # i18n: column positioning for "hg log"
1275 self.ui.write(_("branch: %s\n") % branch,
1275 self.ui.write(_("branch: %s\n") % branch,
1276 label='log.branch')
1276 label='log.branch')
1277
1277
1278 for nsname, ns in self.repo.names.iteritems():
1278 for nsname, ns in self.repo.names.iteritems():
1279 # branches has special logic already handled above, so here we just
1279 # branches has special logic already handled above, so here we just
1280 # skip it
1280 # skip it
1281 if nsname == 'branches':
1281 if nsname == 'branches':
1282 continue
1282 continue
1283 # we will use the templatename as the color name since those two
1283 # we will use the templatename as the color name since those two
1284 # should be the same
1284 # should be the same
1285 for name in ns.names(self.repo, changenode):
1285 for name in ns.names(self.repo, changenode):
1286 self.ui.write(ns.logfmt % name,
1286 self.ui.write(ns.logfmt % name,
1287 label='log.%s' % ns.colorname)
1287 label='log.%s' % ns.colorname)
1288 if self.ui.debugflag:
1288 if self.ui.debugflag:
1289 # i18n: column positioning for "hg log"
1289 # i18n: column positioning for "hg log"
1290 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1290 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1291 label='log.phase')
1291 label='log.phase')
1292 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1292 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1293 label = 'log.parent changeset.%s' % pctx.phasestr()
1293 label = 'log.parent changeset.%s' % pctx.phasestr()
1294 # i18n: column positioning for "hg log"
1294 # i18n: column positioning for "hg log"
1295 self.ui.write(_("parent: %d:%s\n")
1295 self.ui.write(_("parent: %d:%s\n")
1296 % (pctx.rev(), hexfunc(pctx.node())),
1296 % (pctx.rev(), hexfunc(pctx.node())),
1297 label=label)
1297 label=label)
1298
1298
1299 if self.ui.debugflag and rev is not None:
1299 if self.ui.debugflag and rev is not None:
1300 mnode = ctx.manifestnode()
1300 mnode = ctx.manifestnode()
1301 # i18n: column positioning for "hg log"
1301 # i18n: column positioning for "hg log"
1302 self.ui.write(_("manifest: %d:%s\n") %
1302 self.ui.write(_("manifest: %d:%s\n") %
1303 (self.repo.manifest.rev(mnode), hex(mnode)),
1303 (self.repo.manifest.rev(mnode), hex(mnode)),
1304 label='ui.debug log.manifest')
1304 label='ui.debug log.manifest')
1305 # i18n: column positioning for "hg log"
1305 # i18n: column positioning for "hg log"
1306 self.ui.write(_("user: %s\n") % ctx.user(),
1306 self.ui.write(_("user: %s\n") % ctx.user(),
1307 label='log.user')
1307 label='log.user')
1308 # i18n: column positioning for "hg log"
1308 # i18n: column positioning for "hg log"
1309 self.ui.write(_("date: %s\n") % date,
1309 self.ui.write(_("date: %s\n") % date,
1310 label='log.date')
1310 label='log.date')
1311
1311
1312 if self.ui.debugflag:
1312 if self.ui.debugflag:
1313 files = ctx.p1().status(ctx)[:3]
1313 files = ctx.p1().status(ctx)[:3]
1314 for key, value in zip([# i18n: column positioning for "hg log"
1314 for key, value in zip([# i18n: column positioning for "hg log"
1315 _("files:"),
1315 _("files:"),
1316 # i18n: column positioning for "hg log"
1316 # i18n: column positioning for "hg log"
1317 _("files+:"),
1317 _("files+:"),
1318 # i18n: column positioning for "hg log"
1318 # i18n: column positioning for "hg log"
1319 _("files-:")], files):
1319 _("files-:")], files):
1320 if value:
1320 if value:
1321 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1321 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1322 label='ui.debug log.files')
1322 label='ui.debug log.files')
1323 elif ctx.files() and self.ui.verbose:
1323 elif ctx.files() and self.ui.verbose:
1324 # i18n: column positioning for "hg log"
1324 # i18n: column positioning for "hg log"
1325 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1325 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1326 label='ui.note log.files')
1326 label='ui.note log.files')
1327 if copies and self.ui.verbose:
1327 if copies and self.ui.verbose:
1328 copies = ['%s (%s)' % c for c in copies]
1328 copies = ['%s (%s)' % c for c in copies]
1329 # i18n: column positioning for "hg log"
1329 # i18n: column positioning for "hg log"
1330 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1330 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1331 label='ui.note log.copies')
1331 label='ui.note log.copies')
1332
1332
1333 extra = ctx.extra()
1333 extra = ctx.extra()
1334 if extra and self.ui.debugflag:
1334 if extra and self.ui.debugflag:
1335 for key, value in sorted(extra.items()):
1335 for key, value in sorted(extra.items()):
1336 # i18n: column positioning for "hg log"
1336 # i18n: column positioning for "hg log"
1337 self.ui.write(_("extra: %s=%s\n")
1337 self.ui.write(_("extra: %s=%s\n")
1338 % (key, value.encode('string_escape')),
1338 % (key, value.encode('string_escape')),
1339 label='ui.debug log.extra')
1339 label='ui.debug log.extra')
1340
1340
1341 description = ctx.description().strip()
1341 description = ctx.description().strip()
1342 if description:
1342 if description:
1343 if self.ui.verbose:
1343 if self.ui.verbose:
1344 self.ui.write(_("description:\n"),
1344 self.ui.write(_("description:\n"),
1345 label='ui.note log.description')
1345 label='ui.note log.description')
1346 self.ui.write(description,
1346 self.ui.write(description,
1347 label='ui.note log.description')
1347 label='ui.note log.description')
1348 self.ui.write("\n\n")
1348 self.ui.write("\n\n")
1349 else:
1349 else:
1350 # i18n: column positioning for "hg log"
1350 # i18n: column positioning for "hg log"
1351 self.ui.write(_("summary: %s\n") %
1351 self.ui.write(_("summary: %s\n") %
1352 description.splitlines()[0],
1352 description.splitlines()[0],
1353 label='log.summary')
1353 label='log.summary')
1354 self.ui.write("\n")
1354 self.ui.write("\n")
1355
1355
1356 self.showpatch(ctx, matchfn)
1356 self.showpatch(ctx, matchfn)
1357
1357
1358 def showpatch(self, ctx, matchfn):
1358 def showpatch(self, ctx, matchfn):
1359 if not matchfn:
1359 if not matchfn:
1360 matchfn = self.matchfn
1360 matchfn = self.matchfn
1361 if matchfn:
1361 if matchfn:
1362 stat = self.diffopts.get('stat')
1362 stat = self.diffopts.get('stat')
1363 diff = self.diffopts.get('patch')
1363 diff = self.diffopts.get('patch')
1364 diffopts = patch.diffallopts(self.ui, self.diffopts)
1364 diffopts = patch.diffallopts(self.ui, self.diffopts)
1365 node = ctx.node()
1365 node = ctx.node()
1366 prev = ctx.p1().node()
1366 prev = ctx.p1().node()
1367 if stat:
1367 if stat:
1368 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1368 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1369 match=matchfn, stat=True)
1369 match=matchfn, stat=True)
1370 if diff:
1370 if diff:
1371 if stat:
1371 if stat:
1372 self.ui.write("\n")
1372 self.ui.write("\n")
1373 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1373 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1374 match=matchfn, stat=False)
1374 match=matchfn, stat=False)
1375 self.ui.write("\n")
1375 self.ui.write("\n")
1376
1376
1377 class jsonchangeset(changeset_printer):
1377 class jsonchangeset(changeset_printer):
1378 '''format changeset information.'''
1378 '''format changeset information.'''
1379
1379
1380 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1380 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1381 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1381 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1382 self.cache = {}
1382 self.cache = {}
1383 self._first = True
1383 self._first = True
1384
1384
1385 def close(self):
1385 def close(self):
1386 if not self._first:
1386 if not self._first:
1387 self.ui.write("\n]\n")
1387 self.ui.write("\n]\n")
1388 else:
1388 else:
1389 self.ui.write("[]\n")
1389 self.ui.write("[]\n")
1390
1390
1391 def _show(self, ctx, copies, matchfn, props):
1391 def _show(self, ctx, copies, matchfn, props):
1392 '''show a single changeset or file revision'''
1392 '''show a single changeset or file revision'''
1393 rev = ctx.rev()
1393 rev = ctx.rev()
1394 if rev is None:
1394 if rev is None:
1395 jrev = jnode = 'null'
1395 jrev = jnode = 'null'
1396 else:
1396 else:
1397 jrev = str(rev)
1397 jrev = str(rev)
1398 jnode = '"%s"' % hex(ctx.node())
1398 jnode = '"%s"' % hex(ctx.node())
1399 j = encoding.jsonescape
1399 j = encoding.jsonescape
1400
1400
1401 if self._first:
1401 if self._first:
1402 self.ui.write("[\n {")
1402 self.ui.write("[\n {")
1403 self._first = False
1403 self._first = False
1404 else:
1404 else:
1405 self.ui.write(",\n {")
1405 self.ui.write(",\n {")
1406
1406
1407 if self.ui.quiet:
1407 if self.ui.quiet:
1408 self.ui.write('\n "rev": %s' % jrev)
1408 self.ui.write(('\n "rev": %s') % jrev)
1409 self.ui.write(',\n "node": %s' % jnode)
1409 self.ui.write((',\n "node": %s') % jnode)
1410 self.ui.write('\n }')
1410 self.ui.write('\n }')
1411 return
1411 return
1412
1412
1413 self.ui.write('\n "rev": %s' % jrev)
1413 self.ui.write(('\n "rev": %s') % jrev)
1414 self.ui.write(',\n "node": %s' % jnode)
1414 self.ui.write((',\n "node": %s') % jnode)
1415 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1415 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1416 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1416 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1417 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1417 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1418 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1418 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1419 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1419 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1420
1420
1421 self.ui.write(',\n "bookmarks": [%s]' %
1421 self.ui.write((',\n "bookmarks": [%s]') %
1422 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1422 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1423 self.ui.write(',\n "tags": [%s]' %
1423 self.ui.write((',\n "tags": [%s]') %
1424 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1424 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1425 self.ui.write(',\n "parents": [%s]' %
1425 self.ui.write((',\n "parents": [%s]') %
1426 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1426 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1427
1427
1428 if self.ui.debugflag:
1428 if self.ui.debugflag:
1429 if rev is None:
1429 if rev is None:
1430 jmanifestnode = 'null'
1430 jmanifestnode = 'null'
1431 else:
1431 else:
1432 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1432 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1433 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1433 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1434
1434
1435 self.ui.write(',\n "extra": {%s}' %
1435 self.ui.write((',\n "extra": {%s}') %
1436 ", ".join('"%s": "%s"' % (j(k), j(v))
1436 ", ".join('"%s": "%s"' % (j(k), j(v))
1437 for k, v in ctx.extra().items()))
1437 for k, v in ctx.extra().items()))
1438
1438
1439 files = ctx.p1().status(ctx)
1439 files = ctx.p1().status(ctx)
1440 self.ui.write(',\n "modified": [%s]' %
1440 self.ui.write((',\n "modified": [%s]') %
1441 ", ".join('"%s"' % j(f) for f in files[0]))
1441 ", ".join('"%s"' % j(f) for f in files[0]))
1442 self.ui.write(',\n "added": [%s]' %
1442 self.ui.write((',\n "added": [%s]') %
1443 ", ".join('"%s"' % j(f) for f in files[1]))
1443 ", ".join('"%s"' % j(f) for f in files[1]))
1444 self.ui.write(',\n "removed": [%s]' %
1444 self.ui.write((',\n "removed": [%s]') %
1445 ", ".join('"%s"' % j(f) for f in files[2]))
1445 ", ".join('"%s"' % j(f) for f in files[2]))
1446
1446
1447 elif self.ui.verbose:
1447 elif self.ui.verbose:
1448 self.ui.write(',\n "files": [%s]' %
1448 self.ui.write((',\n "files": [%s]') %
1449 ", ".join('"%s"' % j(f) for f in ctx.files()))
1449 ", ".join('"%s"' % j(f) for f in ctx.files()))
1450
1450
1451 if copies:
1451 if copies:
1452 self.ui.write(',\n "copies": {%s}' %
1452 self.ui.write((',\n "copies": {%s}') %
1453 ", ".join('"%s": "%s"' % (j(k), j(v))
1453 ", ".join('"%s": "%s"' % (j(k), j(v))
1454 for k, v in copies))
1454 for k, v in copies))
1455
1455
1456 matchfn = self.matchfn
1456 matchfn = self.matchfn
1457 if matchfn:
1457 if matchfn:
1458 stat = self.diffopts.get('stat')
1458 stat = self.diffopts.get('stat')
1459 diff = self.diffopts.get('patch')
1459 diff = self.diffopts.get('patch')
1460 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1460 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1461 node, prev = ctx.node(), ctx.p1().node()
1461 node, prev = ctx.node(), ctx.p1().node()
1462 if stat:
1462 if stat:
1463 self.ui.pushbuffer()
1463 self.ui.pushbuffer()
1464 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1464 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1465 match=matchfn, stat=True)
1465 match=matchfn, stat=True)
1466 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1466 self.ui.write((',\n "diffstat": "%s"')
1467 % j(self.ui.popbuffer()))
1467 if diff:
1468 if diff:
1468 self.ui.pushbuffer()
1469 self.ui.pushbuffer()
1469 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1470 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1470 match=matchfn, stat=False)
1471 match=matchfn, stat=False)
1471 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1472 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1472
1473
1473 self.ui.write("\n }")
1474 self.ui.write("\n }")
1474
1475
1475 class changeset_templater(changeset_printer):
1476 class changeset_templater(changeset_printer):
1476 '''format changeset information.'''
1477 '''format changeset information.'''
1477
1478
1478 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1479 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1479 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1480 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1480 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1481 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1481 filters = {'formatnode': formatnode}
1482 filters = {'formatnode': formatnode}
1482 defaulttempl = {
1483 defaulttempl = {
1483 'parent': '{rev}:{node|formatnode} ',
1484 'parent': '{rev}:{node|formatnode} ',
1484 'manifest': '{rev}:{node|formatnode}',
1485 'manifest': '{rev}:{node|formatnode}',
1485 'file_copy': '{name} ({source})',
1486 'file_copy': '{name} ({source})',
1486 'extra': '{key}={value|stringescape}'
1487 'extra': '{key}={value|stringescape}'
1487 }
1488 }
1488 # filecopy is preserved for compatibility reasons
1489 # filecopy is preserved for compatibility reasons
1489 defaulttempl['filecopy'] = defaulttempl['file_copy']
1490 defaulttempl['filecopy'] = defaulttempl['file_copy']
1490 assert not (tmpl and mapfile)
1491 assert not (tmpl and mapfile)
1491 if mapfile:
1492 if mapfile:
1492 self.t = templater.templater.frommapfile(mapfile, filters=filters,
1493 self.t = templater.templater.frommapfile(mapfile, filters=filters,
1493 cache=defaulttempl)
1494 cache=defaulttempl)
1494 else:
1495 else:
1495 self.t = formatter.maketemplater(ui, 'changeset', tmpl,
1496 self.t = formatter.maketemplater(ui, 'changeset', tmpl,
1496 filters=filters,
1497 filters=filters,
1497 cache=defaulttempl)
1498 cache=defaulttempl)
1498
1499
1499 self.cache = {}
1500 self.cache = {}
1500
1501
1501 # find correct templates for current mode
1502 # find correct templates for current mode
1502 tmplmodes = [
1503 tmplmodes = [
1503 (True, None),
1504 (True, None),
1504 (self.ui.verbose, 'verbose'),
1505 (self.ui.verbose, 'verbose'),
1505 (self.ui.quiet, 'quiet'),
1506 (self.ui.quiet, 'quiet'),
1506 (self.ui.debugflag, 'debug'),
1507 (self.ui.debugflag, 'debug'),
1507 ]
1508 ]
1508
1509
1509 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1510 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1510 'docheader': '', 'docfooter': ''}
1511 'docheader': '', 'docfooter': ''}
1511 for mode, postfix in tmplmodes:
1512 for mode, postfix in tmplmodes:
1512 for t in self._parts:
1513 for t in self._parts:
1513 cur = t
1514 cur = t
1514 if postfix:
1515 if postfix:
1515 cur += "_" + postfix
1516 cur += "_" + postfix
1516 if mode and cur in self.t:
1517 if mode and cur in self.t:
1517 self._parts[t] = cur
1518 self._parts[t] = cur
1518
1519
1519 if self._parts['docheader']:
1520 if self._parts['docheader']:
1520 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1521 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1521
1522
1522 def close(self):
1523 def close(self):
1523 if self._parts['docfooter']:
1524 if self._parts['docfooter']:
1524 if not self.footer:
1525 if not self.footer:
1525 self.footer = ""
1526 self.footer = ""
1526 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1527 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1527 return super(changeset_templater, self).close()
1528 return super(changeset_templater, self).close()
1528
1529
1529 def _show(self, ctx, copies, matchfn, props):
1530 def _show(self, ctx, copies, matchfn, props):
1530 '''show a single changeset or file revision'''
1531 '''show a single changeset or file revision'''
1531 props = props.copy()
1532 props = props.copy()
1532 props.update(templatekw.keywords)
1533 props.update(templatekw.keywords)
1533 props['templ'] = self.t
1534 props['templ'] = self.t
1534 props['ctx'] = ctx
1535 props['ctx'] = ctx
1535 props['repo'] = self.repo
1536 props['repo'] = self.repo
1536 props['ui'] = self.repo.ui
1537 props['ui'] = self.repo.ui
1537 props['revcache'] = {'copies': copies}
1538 props['revcache'] = {'copies': copies}
1538 props['cache'] = self.cache
1539 props['cache'] = self.cache
1539
1540
1540 # write header
1541 # write header
1541 if self._parts['header']:
1542 if self._parts['header']:
1542 h = templater.stringify(self.t(self._parts['header'], **props))
1543 h = templater.stringify(self.t(self._parts['header'], **props))
1543 if self.buffered:
1544 if self.buffered:
1544 self.header[ctx.rev()] = h
1545 self.header[ctx.rev()] = h
1545 else:
1546 else:
1546 if self.lastheader != h:
1547 if self.lastheader != h:
1547 self.lastheader = h
1548 self.lastheader = h
1548 self.ui.write(h)
1549 self.ui.write(h)
1549
1550
1550 # write changeset metadata, then patch if requested
1551 # write changeset metadata, then patch if requested
1551 key = self._parts['changeset']
1552 key = self._parts['changeset']
1552 self.ui.write(templater.stringify(self.t(key, **props)))
1553 self.ui.write(templater.stringify(self.t(key, **props)))
1553 self.showpatch(ctx, matchfn)
1554 self.showpatch(ctx, matchfn)
1554
1555
1555 if self._parts['footer']:
1556 if self._parts['footer']:
1556 if not self.footer:
1557 if not self.footer:
1557 self.footer = templater.stringify(
1558 self.footer = templater.stringify(
1558 self.t(self._parts['footer'], **props))
1559 self.t(self._parts['footer'], **props))
1559
1560
1560 def gettemplate(ui, tmpl, style):
1561 def gettemplate(ui, tmpl, style):
1561 """
1562 """
1562 Find the template matching the given template spec or style.
1563 Find the template matching the given template spec or style.
1563 """
1564 """
1564
1565
1565 # ui settings
1566 # ui settings
1566 if not tmpl and not style: # template are stronger than style
1567 if not tmpl and not style: # template are stronger than style
1567 tmpl = ui.config('ui', 'logtemplate')
1568 tmpl = ui.config('ui', 'logtemplate')
1568 if tmpl:
1569 if tmpl:
1569 return templater.unquotestring(tmpl), None
1570 return templater.unquotestring(tmpl), None
1570 else:
1571 else:
1571 style = util.expandpath(ui.config('ui', 'style', ''))
1572 style = util.expandpath(ui.config('ui', 'style', ''))
1572
1573
1573 if not tmpl and style:
1574 if not tmpl and style:
1574 mapfile = style
1575 mapfile = style
1575 if not os.path.split(mapfile)[0]:
1576 if not os.path.split(mapfile)[0]:
1576 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1577 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1577 or templater.templatepath(mapfile))
1578 or templater.templatepath(mapfile))
1578 if mapname:
1579 if mapname:
1579 mapfile = mapname
1580 mapfile = mapname
1580 return None, mapfile
1581 return None, mapfile
1581
1582
1582 if not tmpl:
1583 if not tmpl:
1583 return None, None
1584 return None, None
1584
1585
1585 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1586 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1586
1587
1587 def show_changeset(ui, repo, opts, buffered=False):
1588 def show_changeset(ui, repo, opts, buffered=False):
1588 """show one changeset using template or regular display.
1589 """show one changeset using template or regular display.
1589
1590
1590 Display format will be the first non-empty hit of:
1591 Display format will be the first non-empty hit of:
1591 1. option 'template'
1592 1. option 'template'
1592 2. option 'style'
1593 2. option 'style'
1593 3. [ui] setting 'logtemplate'
1594 3. [ui] setting 'logtemplate'
1594 4. [ui] setting 'style'
1595 4. [ui] setting 'style'
1595 If all of these values are either the unset or the empty string,
1596 If all of these values are either the unset or the empty string,
1596 regular display via changeset_printer() is done.
1597 regular display via changeset_printer() is done.
1597 """
1598 """
1598 # options
1599 # options
1599 matchfn = None
1600 matchfn = None
1600 if opts.get('patch') or opts.get('stat'):
1601 if opts.get('patch') or opts.get('stat'):
1601 matchfn = scmutil.matchall(repo)
1602 matchfn = scmutil.matchall(repo)
1602
1603
1603 if opts.get('template') == 'json':
1604 if opts.get('template') == 'json':
1604 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1605 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1605
1606
1606 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1607 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1607
1608
1608 if not tmpl and not mapfile:
1609 if not tmpl and not mapfile:
1609 return changeset_printer(ui, repo, matchfn, opts, buffered)
1610 return changeset_printer(ui, repo, matchfn, opts, buffered)
1610
1611
1611 return changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile, buffered)
1612 return changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile, buffered)
1612
1613
1613 def showmarker(ui, marker, index=None):
1614 def showmarker(ui, marker, index=None):
1614 """utility function to display obsolescence marker in a readable way
1615 """utility function to display obsolescence marker in a readable way
1615
1616
1616 To be used by debug function."""
1617 To be used by debug function."""
1617 if index is not None:
1618 if index is not None:
1618 ui.write("%i " % index)
1619 ui.write("%i " % index)
1619 ui.write(hex(marker.precnode()))
1620 ui.write(hex(marker.precnode()))
1620 for repl in marker.succnodes():
1621 for repl in marker.succnodes():
1621 ui.write(' ')
1622 ui.write(' ')
1622 ui.write(hex(repl))
1623 ui.write(hex(repl))
1623 ui.write(' %X ' % marker.flags())
1624 ui.write(' %X ' % marker.flags())
1624 parents = marker.parentnodes()
1625 parents = marker.parentnodes()
1625 if parents is not None:
1626 if parents is not None:
1626 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1627 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1627 ui.write('(%s) ' % util.datestr(marker.date()))
1628 ui.write('(%s) ' % util.datestr(marker.date()))
1628 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1629 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1629 sorted(marker.metadata().items())
1630 sorted(marker.metadata().items())
1630 if t[0] != 'date')))
1631 if t[0] != 'date')))
1631 ui.write('\n')
1632 ui.write('\n')
1632
1633
1633 def finddate(ui, repo, date):
1634 def finddate(ui, repo, date):
1634 """Find the tipmost changeset that matches the given date spec"""
1635 """Find the tipmost changeset that matches the given date spec"""
1635
1636
1636 df = util.matchdate(date)
1637 df = util.matchdate(date)
1637 m = scmutil.matchall(repo)
1638 m = scmutil.matchall(repo)
1638 results = {}
1639 results = {}
1639
1640
1640 def prep(ctx, fns):
1641 def prep(ctx, fns):
1641 d = ctx.date()
1642 d = ctx.date()
1642 if df(d[0]):
1643 if df(d[0]):
1643 results[ctx.rev()] = d
1644 results[ctx.rev()] = d
1644
1645
1645 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1646 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1646 rev = ctx.rev()
1647 rev = ctx.rev()
1647 if rev in results:
1648 if rev in results:
1648 ui.status(_("found revision %s from %s\n") %
1649 ui.status(_("found revision %s from %s\n") %
1649 (rev, util.datestr(results[rev])))
1650 (rev, util.datestr(results[rev])))
1650 return str(rev)
1651 return str(rev)
1651
1652
1652 raise error.Abort(_("revision matching date not found"))
1653 raise error.Abort(_("revision matching date not found"))
1653
1654
1654 def increasingwindows(windowsize=8, sizelimit=512):
1655 def increasingwindows(windowsize=8, sizelimit=512):
1655 while True:
1656 while True:
1656 yield windowsize
1657 yield windowsize
1657 if windowsize < sizelimit:
1658 if windowsize < sizelimit:
1658 windowsize *= 2
1659 windowsize *= 2
1659
1660
1660 class FileWalkError(Exception):
1661 class FileWalkError(Exception):
1661 pass
1662 pass
1662
1663
1663 def walkfilerevs(repo, match, follow, revs, fncache):
1664 def walkfilerevs(repo, match, follow, revs, fncache):
1664 '''Walks the file history for the matched files.
1665 '''Walks the file history for the matched files.
1665
1666
1666 Returns the changeset revs that are involved in the file history.
1667 Returns the changeset revs that are involved in the file history.
1667
1668
1668 Throws FileWalkError if the file history can't be walked using
1669 Throws FileWalkError if the file history can't be walked using
1669 filelogs alone.
1670 filelogs alone.
1670 '''
1671 '''
1671 wanted = set()
1672 wanted = set()
1672 copies = []
1673 copies = []
1673 minrev, maxrev = min(revs), max(revs)
1674 minrev, maxrev = min(revs), max(revs)
1674 def filerevgen(filelog, last):
1675 def filerevgen(filelog, last):
1675 """
1676 """
1676 Only files, no patterns. Check the history of each file.
1677 Only files, no patterns. Check the history of each file.
1677
1678
1678 Examines filelog entries within minrev, maxrev linkrev range
1679 Examines filelog entries within minrev, maxrev linkrev range
1679 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1680 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1680 tuples in backwards order
1681 tuples in backwards order
1681 """
1682 """
1682 cl_count = len(repo)
1683 cl_count = len(repo)
1683 revs = []
1684 revs = []
1684 for j in xrange(0, last + 1):
1685 for j in xrange(0, last + 1):
1685 linkrev = filelog.linkrev(j)
1686 linkrev = filelog.linkrev(j)
1686 if linkrev < minrev:
1687 if linkrev < minrev:
1687 continue
1688 continue
1688 # only yield rev for which we have the changelog, it can
1689 # only yield rev for which we have the changelog, it can
1689 # happen while doing "hg log" during a pull or commit
1690 # happen while doing "hg log" during a pull or commit
1690 if linkrev >= cl_count:
1691 if linkrev >= cl_count:
1691 break
1692 break
1692
1693
1693 parentlinkrevs = []
1694 parentlinkrevs = []
1694 for p in filelog.parentrevs(j):
1695 for p in filelog.parentrevs(j):
1695 if p != nullrev:
1696 if p != nullrev:
1696 parentlinkrevs.append(filelog.linkrev(p))
1697 parentlinkrevs.append(filelog.linkrev(p))
1697 n = filelog.node(j)
1698 n = filelog.node(j)
1698 revs.append((linkrev, parentlinkrevs,
1699 revs.append((linkrev, parentlinkrevs,
1699 follow and filelog.renamed(n)))
1700 follow and filelog.renamed(n)))
1700
1701
1701 return reversed(revs)
1702 return reversed(revs)
1702 def iterfiles():
1703 def iterfiles():
1703 pctx = repo['.']
1704 pctx = repo['.']
1704 for filename in match.files():
1705 for filename in match.files():
1705 if follow:
1706 if follow:
1706 if filename not in pctx:
1707 if filename not in pctx:
1707 raise error.Abort(_('cannot follow file not in parent '
1708 raise error.Abort(_('cannot follow file not in parent '
1708 'revision: "%s"') % filename)
1709 'revision: "%s"') % filename)
1709 yield filename, pctx[filename].filenode()
1710 yield filename, pctx[filename].filenode()
1710 else:
1711 else:
1711 yield filename, None
1712 yield filename, None
1712 for filename_node in copies:
1713 for filename_node in copies:
1713 yield filename_node
1714 yield filename_node
1714
1715
1715 for file_, node in iterfiles():
1716 for file_, node in iterfiles():
1716 filelog = repo.file(file_)
1717 filelog = repo.file(file_)
1717 if not len(filelog):
1718 if not len(filelog):
1718 if node is None:
1719 if node is None:
1719 # A zero count may be a directory or deleted file, so
1720 # A zero count may be a directory or deleted file, so
1720 # try to find matching entries on the slow path.
1721 # try to find matching entries on the slow path.
1721 if follow:
1722 if follow:
1722 raise error.Abort(
1723 raise error.Abort(
1723 _('cannot follow nonexistent file: "%s"') % file_)
1724 _('cannot follow nonexistent file: "%s"') % file_)
1724 raise FileWalkError("Cannot walk via filelog")
1725 raise FileWalkError("Cannot walk via filelog")
1725 else:
1726 else:
1726 continue
1727 continue
1727
1728
1728 if node is None:
1729 if node is None:
1729 last = len(filelog) - 1
1730 last = len(filelog) - 1
1730 else:
1731 else:
1731 last = filelog.rev(node)
1732 last = filelog.rev(node)
1732
1733
1733 # keep track of all ancestors of the file
1734 # keep track of all ancestors of the file
1734 ancestors = set([filelog.linkrev(last)])
1735 ancestors = set([filelog.linkrev(last)])
1735
1736
1736 # iterate from latest to oldest revision
1737 # iterate from latest to oldest revision
1737 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1738 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1738 if not follow:
1739 if not follow:
1739 if rev > maxrev:
1740 if rev > maxrev:
1740 continue
1741 continue
1741 else:
1742 else:
1742 # Note that last might not be the first interesting
1743 # Note that last might not be the first interesting
1743 # rev to us:
1744 # rev to us:
1744 # if the file has been changed after maxrev, we'll
1745 # if the file has been changed after maxrev, we'll
1745 # have linkrev(last) > maxrev, and we still need
1746 # have linkrev(last) > maxrev, and we still need
1746 # to explore the file graph
1747 # to explore the file graph
1747 if rev not in ancestors:
1748 if rev not in ancestors:
1748 continue
1749 continue
1749 # XXX insert 1327 fix here
1750 # XXX insert 1327 fix here
1750 if flparentlinkrevs:
1751 if flparentlinkrevs:
1751 ancestors.update(flparentlinkrevs)
1752 ancestors.update(flparentlinkrevs)
1752
1753
1753 fncache.setdefault(rev, []).append(file_)
1754 fncache.setdefault(rev, []).append(file_)
1754 wanted.add(rev)
1755 wanted.add(rev)
1755 if copied:
1756 if copied:
1756 copies.append(copied)
1757 copies.append(copied)
1757
1758
1758 return wanted
1759 return wanted
1759
1760
1760 class _followfilter(object):
1761 class _followfilter(object):
1761 def __init__(self, repo, onlyfirst=False):
1762 def __init__(self, repo, onlyfirst=False):
1762 self.repo = repo
1763 self.repo = repo
1763 self.startrev = nullrev
1764 self.startrev = nullrev
1764 self.roots = set()
1765 self.roots = set()
1765 self.onlyfirst = onlyfirst
1766 self.onlyfirst = onlyfirst
1766
1767
1767 def match(self, rev):
1768 def match(self, rev):
1768 def realparents(rev):
1769 def realparents(rev):
1769 if self.onlyfirst:
1770 if self.onlyfirst:
1770 return self.repo.changelog.parentrevs(rev)[0:1]
1771 return self.repo.changelog.parentrevs(rev)[0:1]
1771 else:
1772 else:
1772 return filter(lambda x: x != nullrev,
1773 return filter(lambda x: x != nullrev,
1773 self.repo.changelog.parentrevs(rev))
1774 self.repo.changelog.parentrevs(rev))
1774
1775
1775 if self.startrev == nullrev:
1776 if self.startrev == nullrev:
1776 self.startrev = rev
1777 self.startrev = rev
1777 return True
1778 return True
1778
1779
1779 if rev > self.startrev:
1780 if rev > self.startrev:
1780 # forward: all descendants
1781 # forward: all descendants
1781 if not self.roots:
1782 if not self.roots:
1782 self.roots.add(self.startrev)
1783 self.roots.add(self.startrev)
1783 for parent in realparents(rev):
1784 for parent in realparents(rev):
1784 if parent in self.roots:
1785 if parent in self.roots:
1785 self.roots.add(rev)
1786 self.roots.add(rev)
1786 return True
1787 return True
1787 else:
1788 else:
1788 # backwards: all parents
1789 # backwards: all parents
1789 if not self.roots:
1790 if not self.roots:
1790 self.roots.update(realparents(self.startrev))
1791 self.roots.update(realparents(self.startrev))
1791 if rev in self.roots:
1792 if rev in self.roots:
1792 self.roots.remove(rev)
1793 self.roots.remove(rev)
1793 self.roots.update(realparents(rev))
1794 self.roots.update(realparents(rev))
1794 return True
1795 return True
1795
1796
1796 return False
1797 return False
1797
1798
1798 def walkchangerevs(repo, match, opts, prepare):
1799 def walkchangerevs(repo, match, opts, prepare):
1799 '''Iterate over files and the revs in which they changed.
1800 '''Iterate over files and the revs in which they changed.
1800
1801
1801 Callers most commonly need to iterate backwards over the history
1802 Callers most commonly need to iterate backwards over the history
1802 in which they are interested. Doing so has awful (quadratic-looking)
1803 in which they are interested. Doing so has awful (quadratic-looking)
1803 performance, so we use iterators in a "windowed" way.
1804 performance, so we use iterators in a "windowed" way.
1804
1805
1805 We walk a window of revisions in the desired order. Within the
1806 We walk a window of revisions in the desired order. Within the
1806 window, we first walk forwards to gather data, then in the desired
1807 window, we first walk forwards to gather data, then in the desired
1807 order (usually backwards) to display it.
1808 order (usually backwards) to display it.
1808
1809
1809 This function returns an iterator yielding contexts. Before
1810 This function returns an iterator yielding contexts. Before
1810 yielding each context, the iterator will first call the prepare
1811 yielding each context, the iterator will first call the prepare
1811 function on each context in the window in forward order.'''
1812 function on each context in the window in forward order.'''
1812
1813
1813 follow = opts.get('follow') or opts.get('follow_first')
1814 follow = opts.get('follow') or opts.get('follow_first')
1814 revs = _logrevs(repo, opts)
1815 revs = _logrevs(repo, opts)
1815 if not revs:
1816 if not revs:
1816 return []
1817 return []
1817 wanted = set()
1818 wanted = set()
1818 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1819 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1819 opts.get('removed'))
1820 opts.get('removed'))
1820 fncache = {}
1821 fncache = {}
1821 change = repo.changectx
1822 change = repo.changectx
1822
1823
1823 # First step is to fill wanted, the set of revisions that we want to yield.
1824 # First step is to fill wanted, the set of revisions that we want to yield.
1824 # When it does not induce extra cost, we also fill fncache for revisions in
1825 # When it does not induce extra cost, we also fill fncache for revisions in
1825 # wanted: a cache of filenames that were changed (ctx.files()) and that
1826 # wanted: a cache of filenames that were changed (ctx.files()) and that
1826 # match the file filtering conditions.
1827 # match the file filtering conditions.
1827
1828
1828 if match.always():
1829 if match.always():
1829 # No files, no patterns. Display all revs.
1830 # No files, no patterns. Display all revs.
1830 wanted = revs
1831 wanted = revs
1831 elif not slowpath:
1832 elif not slowpath:
1832 # We only have to read through the filelog to find wanted revisions
1833 # We only have to read through the filelog to find wanted revisions
1833
1834
1834 try:
1835 try:
1835 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1836 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1836 except FileWalkError:
1837 except FileWalkError:
1837 slowpath = True
1838 slowpath = True
1838
1839
1839 # We decided to fall back to the slowpath because at least one
1840 # We decided to fall back to the slowpath because at least one
1840 # of the paths was not a file. Check to see if at least one of them
1841 # of the paths was not a file. Check to see if at least one of them
1841 # existed in history, otherwise simply return
1842 # existed in history, otherwise simply return
1842 for path in match.files():
1843 for path in match.files():
1843 if path == '.' or path in repo.store:
1844 if path == '.' or path in repo.store:
1844 break
1845 break
1845 else:
1846 else:
1846 return []
1847 return []
1847
1848
1848 if slowpath:
1849 if slowpath:
1849 # We have to read the changelog to match filenames against
1850 # We have to read the changelog to match filenames against
1850 # changed files
1851 # changed files
1851
1852
1852 if follow:
1853 if follow:
1853 raise error.Abort(_('can only follow copies/renames for explicit '
1854 raise error.Abort(_('can only follow copies/renames for explicit '
1854 'filenames'))
1855 'filenames'))
1855
1856
1856 # The slow path checks files modified in every changeset.
1857 # The slow path checks files modified in every changeset.
1857 # This is really slow on large repos, so compute the set lazily.
1858 # This is really slow on large repos, so compute the set lazily.
1858 class lazywantedset(object):
1859 class lazywantedset(object):
1859 def __init__(self):
1860 def __init__(self):
1860 self.set = set()
1861 self.set = set()
1861 self.revs = set(revs)
1862 self.revs = set(revs)
1862
1863
1863 # No need to worry about locality here because it will be accessed
1864 # No need to worry about locality here because it will be accessed
1864 # in the same order as the increasing window below.
1865 # in the same order as the increasing window below.
1865 def __contains__(self, value):
1866 def __contains__(self, value):
1866 if value in self.set:
1867 if value in self.set:
1867 return True
1868 return True
1868 elif not value in self.revs:
1869 elif not value in self.revs:
1869 return False
1870 return False
1870 else:
1871 else:
1871 self.revs.discard(value)
1872 self.revs.discard(value)
1872 ctx = change(value)
1873 ctx = change(value)
1873 matches = filter(match, ctx.files())
1874 matches = filter(match, ctx.files())
1874 if matches:
1875 if matches:
1875 fncache[value] = matches
1876 fncache[value] = matches
1876 self.set.add(value)
1877 self.set.add(value)
1877 return True
1878 return True
1878 return False
1879 return False
1879
1880
1880 def discard(self, value):
1881 def discard(self, value):
1881 self.revs.discard(value)
1882 self.revs.discard(value)
1882 self.set.discard(value)
1883 self.set.discard(value)
1883
1884
1884 wanted = lazywantedset()
1885 wanted = lazywantedset()
1885
1886
1886 # it might be worthwhile to do this in the iterator if the rev range
1887 # it might be worthwhile to do this in the iterator if the rev range
1887 # is descending and the prune args are all within that range
1888 # is descending and the prune args are all within that range
1888 for rev in opts.get('prune', ()):
1889 for rev in opts.get('prune', ()):
1889 rev = repo[rev].rev()
1890 rev = repo[rev].rev()
1890 ff = _followfilter(repo)
1891 ff = _followfilter(repo)
1891 stop = min(revs[0], revs[-1])
1892 stop = min(revs[0], revs[-1])
1892 for x in xrange(rev, stop - 1, -1):
1893 for x in xrange(rev, stop - 1, -1):
1893 if ff.match(x):
1894 if ff.match(x):
1894 wanted = wanted - [x]
1895 wanted = wanted - [x]
1895
1896
1896 # Now that wanted is correctly initialized, we can iterate over the
1897 # Now that wanted is correctly initialized, we can iterate over the
1897 # revision range, yielding only revisions in wanted.
1898 # revision range, yielding only revisions in wanted.
1898 def iterate():
1899 def iterate():
1899 if follow and match.always():
1900 if follow and match.always():
1900 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1901 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1901 def want(rev):
1902 def want(rev):
1902 return ff.match(rev) and rev in wanted
1903 return ff.match(rev) and rev in wanted
1903 else:
1904 else:
1904 def want(rev):
1905 def want(rev):
1905 return rev in wanted
1906 return rev in wanted
1906
1907
1907 it = iter(revs)
1908 it = iter(revs)
1908 stopiteration = False
1909 stopiteration = False
1909 for windowsize in increasingwindows():
1910 for windowsize in increasingwindows():
1910 nrevs = []
1911 nrevs = []
1911 for i in xrange(windowsize):
1912 for i in xrange(windowsize):
1912 rev = next(it, None)
1913 rev = next(it, None)
1913 if rev is None:
1914 if rev is None:
1914 stopiteration = True
1915 stopiteration = True
1915 break
1916 break
1916 elif want(rev):
1917 elif want(rev):
1917 nrevs.append(rev)
1918 nrevs.append(rev)
1918 for rev in sorted(nrevs):
1919 for rev in sorted(nrevs):
1919 fns = fncache.get(rev)
1920 fns = fncache.get(rev)
1920 ctx = change(rev)
1921 ctx = change(rev)
1921 if not fns:
1922 if not fns:
1922 def fns_generator():
1923 def fns_generator():
1923 for f in ctx.files():
1924 for f in ctx.files():
1924 if match(f):
1925 if match(f):
1925 yield f
1926 yield f
1926 fns = fns_generator()
1927 fns = fns_generator()
1927 prepare(ctx, fns)
1928 prepare(ctx, fns)
1928 for rev in nrevs:
1929 for rev in nrevs:
1929 yield change(rev)
1930 yield change(rev)
1930
1931
1931 if stopiteration:
1932 if stopiteration:
1932 break
1933 break
1933
1934
1934 return iterate()
1935 return iterate()
1935
1936
1936 def _makefollowlogfilematcher(repo, files, followfirst):
1937 def _makefollowlogfilematcher(repo, files, followfirst):
1937 # When displaying a revision with --patch --follow FILE, we have
1938 # When displaying a revision with --patch --follow FILE, we have
1938 # to know which file of the revision must be diffed. With
1939 # to know which file of the revision must be diffed. With
1939 # --follow, we want the names of the ancestors of FILE in the
1940 # --follow, we want the names of the ancestors of FILE in the
1940 # revision, stored in "fcache". "fcache" is populated by
1941 # revision, stored in "fcache". "fcache" is populated by
1941 # reproducing the graph traversal already done by --follow revset
1942 # reproducing the graph traversal already done by --follow revset
1942 # and relating linkrevs to file names (which is not "correct" but
1943 # and relating linkrevs to file names (which is not "correct" but
1943 # good enough).
1944 # good enough).
1944 fcache = {}
1945 fcache = {}
1945 fcacheready = [False]
1946 fcacheready = [False]
1946 pctx = repo['.']
1947 pctx = repo['.']
1947
1948
1948 def populate():
1949 def populate():
1949 for fn in files:
1950 for fn in files:
1950 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1951 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1951 for c in i:
1952 for c in i:
1952 fcache.setdefault(c.linkrev(), set()).add(c.path())
1953 fcache.setdefault(c.linkrev(), set()).add(c.path())
1953
1954
1954 def filematcher(rev):
1955 def filematcher(rev):
1955 if not fcacheready[0]:
1956 if not fcacheready[0]:
1956 # Lazy initialization
1957 # Lazy initialization
1957 fcacheready[0] = True
1958 fcacheready[0] = True
1958 populate()
1959 populate()
1959 return scmutil.matchfiles(repo, fcache.get(rev, []))
1960 return scmutil.matchfiles(repo, fcache.get(rev, []))
1960
1961
1961 return filematcher
1962 return filematcher
1962
1963
1963 def _makenofollowlogfilematcher(repo, pats, opts):
1964 def _makenofollowlogfilematcher(repo, pats, opts):
1964 '''hook for extensions to override the filematcher for non-follow cases'''
1965 '''hook for extensions to override the filematcher for non-follow cases'''
1965 return None
1966 return None
1966
1967
1967 def _makelogrevset(repo, pats, opts, revs):
1968 def _makelogrevset(repo, pats, opts, revs):
1968 """Return (expr, filematcher) where expr is a revset string built
1969 """Return (expr, filematcher) where expr is a revset string built
1969 from log options and file patterns or None. If --stat or --patch
1970 from log options and file patterns or None. If --stat or --patch
1970 are not passed filematcher is None. Otherwise it is a callable
1971 are not passed filematcher is None. Otherwise it is a callable
1971 taking a revision number and returning a match objects filtering
1972 taking a revision number and returning a match objects filtering
1972 the files to be detailed when displaying the revision.
1973 the files to be detailed when displaying the revision.
1973 """
1974 """
1974 opt2revset = {
1975 opt2revset = {
1975 'no_merges': ('not merge()', None),
1976 'no_merges': ('not merge()', None),
1976 'only_merges': ('merge()', None),
1977 'only_merges': ('merge()', None),
1977 '_ancestors': ('ancestors(%(val)s)', None),
1978 '_ancestors': ('ancestors(%(val)s)', None),
1978 '_fancestors': ('_firstancestors(%(val)s)', None),
1979 '_fancestors': ('_firstancestors(%(val)s)', None),
1979 '_descendants': ('descendants(%(val)s)', None),
1980 '_descendants': ('descendants(%(val)s)', None),
1980 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1981 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1981 '_matchfiles': ('_matchfiles(%(val)s)', None),
1982 '_matchfiles': ('_matchfiles(%(val)s)', None),
1982 'date': ('date(%(val)r)', None),
1983 'date': ('date(%(val)r)', None),
1983 'branch': ('branch(%(val)r)', ' or '),
1984 'branch': ('branch(%(val)r)', ' or '),
1984 '_patslog': ('filelog(%(val)r)', ' or '),
1985 '_patslog': ('filelog(%(val)r)', ' or '),
1985 '_patsfollow': ('follow(%(val)r)', ' or '),
1986 '_patsfollow': ('follow(%(val)r)', ' or '),
1986 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1987 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1987 'keyword': ('keyword(%(val)r)', ' or '),
1988 'keyword': ('keyword(%(val)r)', ' or '),
1988 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1989 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1989 'user': ('user(%(val)r)', ' or '),
1990 'user': ('user(%(val)r)', ' or '),
1990 }
1991 }
1991
1992
1992 opts = dict(opts)
1993 opts = dict(opts)
1993 # follow or not follow?
1994 # follow or not follow?
1994 follow = opts.get('follow') or opts.get('follow_first')
1995 follow = opts.get('follow') or opts.get('follow_first')
1995 if opts.get('follow_first'):
1996 if opts.get('follow_first'):
1996 followfirst = 1
1997 followfirst = 1
1997 else:
1998 else:
1998 followfirst = 0
1999 followfirst = 0
1999 # --follow with FILE behavior depends on revs...
2000 # --follow with FILE behavior depends on revs...
2000 it = iter(revs)
2001 it = iter(revs)
2001 startrev = next(it)
2002 startrev = next(it)
2002 followdescendants = startrev < next(it, startrev)
2003 followdescendants = startrev < next(it, startrev)
2003
2004
2004 # branch and only_branch are really aliases and must be handled at
2005 # branch and only_branch are really aliases and must be handled at
2005 # the same time
2006 # the same time
2006 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2007 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2007 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2008 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2008 # pats/include/exclude are passed to match.match() directly in
2009 # pats/include/exclude are passed to match.match() directly in
2009 # _matchfiles() revset but walkchangerevs() builds its matcher with
2010 # _matchfiles() revset but walkchangerevs() builds its matcher with
2010 # scmutil.match(). The difference is input pats are globbed on
2011 # scmutil.match(). The difference is input pats are globbed on
2011 # platforms without shell expansion (windows).
2012 # platforms without shell expansion (windows).
2012 wctx = repo[None]
2013 wctx = repo[None]
2013 match, pats = scmutil.matchandpats(wctx, pats, opts)
2014 match, pats = scmutil.matchandpats(wctx, pats, opts)
2014 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2015 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2015 opts.get('removed'))
2016 opts.get('removed'))
2016 if not slowpath:
2017 if not slowpath:
2017 for f in match.files():
2018 for f in match.files():
2018 if follow and f not in wctx:
2019 if follow and f not in wctx:
2019 # If the file exists, it may be a directory, so let it
2020 # If the file exists, it may be a directory, so let it
2020 # take the slow path.
2021 # take the slow path.
2021 if os.path.exists(repo.wjoin(f)):
2022 if os.path.exists(repo.wjoin(f)):
2022 slowpath = True
2023 slowpath = True
2023 continue
2024 continue
2024 else:
2025 else:
2025 raise error.Abort(_('cannot follow file not in parent '
2026 raise error.Abort(_('cannot follow file not in parent '
2026 'revision: "%s"') % f)
2027 'revision: "%s"') % f)
2027 filelog = repo.file(f)
2028 filelog = repo.file(f)
2028 if not filelog:
2029 if not filelog:
2029 # A zero count may be a directory or deleted file, so
2030 # A zero count may be a directory or deleted file, so
2030 # try to find matching entries on the slow path.
2031 # try to find matching entries on the slow path.
2031 if follow:
2032 if follow:
2032 raise error.Abort(
2033 raise error.Abort(
2033 _('cannot follow nonexistent file: "%s"') % f)
2034 _('cannot follow nonexistent file: "%s"') % f)
2034 slowpath = True
2035 slowpath = True
2035
2036
2036 # We decided to fall back to the slowpath because at least one
2037 # We decided to fall back to the slowpath because at least one
2037 # of the paths was not a file. Check to see if at least one of them
2038 # of the paths was not a file. Check to see if at least one of them
2038 # existed in history - in that case, we'll continue down the
2039 # existed in history - in that case, we'll continue down the
2039 # slowpath; otherwise, we can turn off the slowpath
2040 # slowpath; otherwise, we can turn off the slowpath
2040 if slowpath:
2041 if slowpath:
2041 for path in match.files():
2042 for path in match.files():
2042 if path == '.' or path in repo.store:
2043 if path == '.' or path in repo.store:
2043 break
2044 break
2044 else:
2045 else:
2045 slowpath = False
2046 slowpath = False
2046
2047
2047 fpats = ('_patsfollow', '_patsfollowfirst')
2048 fpats = ('_patsfollow', '_patsfollowfirst')
2048 fnopats = (('_ancestors', '_fancestors'),
2049 fnopats = (('_ancestors', '_fancestors'),
2049 ('_descendants', '_fdescendants'))
2050 ('_descendants', '_fdescendants'))
2050 if slowpath:
2051 if slowpath:
2051 # See walkchangerevs() slow path.
2052 # See walkchangerevs() slow path.
2052 #
2053 #
2053 # pats/include/exclude cannot be represented as separate
2054 # pats/include/exclude cannot be represented as separate
2054 # revset expressions as their filtering logic applies at file
2055 # revset expressions as their filtering logic applies at file
2055 # level. For instance "-I a -X a" matches a revision touching
2056 # level. For instance "-I a -X a" matches a revision touching
2056 # "a" and "b" while "file(a) and not file(b)" does
2057 # "a" and "b" while "file(a) and not file(b)" does
2057 # not. Besides, filesets are evaluated against the working
2058 # not. Besides, filesets are evaluated against the working
2058 # directory.
2059 # directory.
2059 matchargs = ['r:', 'd:relpath']
2060 matchargs = ['r:', 'd:relpath']
2060 for p in pats:
2061 for p in pats:
2061 matchargs.append('p:' + p)
2062 matchargs.append('p:' + p)
2062 for p in opts.get('include', []):
2063 for p in opts.get('include', []):
2063 matchargs.append('i:' + p)
2064 matchargs.append('i:' + p)
2064 for p in opts.get('exclude', []):
2065 for p in opts.get('exclude', []):
2065 matchargs.append('x:' + p)
2066 matchargs.append('x:' + p)
2066 matchargs = ','.join(('%r' % p) for p in matchargs)
2067 matchargs = ','.join(('%r' % p) for p in matchargs)
2067 opts['_matchfiles'] = matchargs
2068 opts['_matchfiles'] = matchargs
2068 if follow:
2069 if follow:
2069 opts[fnopats[0][followfirst]] = '.'
2070 opts[fnopats[0][followfirst]] = '.'
2070 else:
2071 else:
2071 if follow:
2072 if follow:
2072 if pats:
2073 if pats:
2073 # follow() revset interprets its file argument as a
2074 # follow() revset interprets its file argument as a
2074 # manifest entry, so use match.files(), not pats.
2075 # manifest entry, so use match.files(), not pats.
2075 opts[fpats[followfirst]] = list(match.files())
2076 opts[fpats[followfirst]] = list(match.files())
2076 else:
2077 else:
2077 op = fnopats[followdescendants][followfirst]
2078 op = fnopats[followdescendants][followfirst]
2078 opts[op] = 'rev(%d)' % startrev
2079 opts[op] = 'rev(%d)' % startrev
2079 else:
2080 else:
2080 opts['_patslog'] = list(pats)
2081 opts['_patslog'] = list(pats)
2081
2082
2082 filematcher = None
2083 filematcher = None
2083 if opts.get('patch') or opts.get('stat'):
2084 if opts.get('patch') or opts.get('stat'):
2084 # When following files, track renames via a special matcher.
2085 # When following files, track renames via a special matcher.
2085 # If we're forced to take the slowpath it means we're following
2086 # If we're forced to take the slowpath it means we're following
2086 # at least one pattern/directory, so don't bother with rename tracking.
2087 # at least one pattern/directory, so don't bother with rename tracking.
2087 if follow and not match.always() and not slowpath:
2088 if follow and not match.always() and not slowpath:
2088 # _makefollowlogfilematcher expects its files argument to be
2089 # _makefollowlogfilematcher expects its files argument to be
2089 # relative to the repo root, so use match.files(), not pats.
2090 # relative to the repo root, so use match.files(), not pats.
2090 filematcher = _makefollowlogfilematcher(repo, match.files(),
2091 filematcher = _makefollowlogfilematcher(repo, match.files(),
2091 followfirst)
2092 followfirst)
2092 else:
2093 else:
2093 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2094 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2094 if filematcher is None:
2095 if filematcher is None:
2095 filematcher = lambda rev: match
2096 filematcher = lambda rev: match
2096
2097
2097 expr = []
2098 expr = []
2098 for op, val in sorted(opts.iteritems()):
2099 for op, val in sorted(opts.iteritems()):
2099 if not val:
2100 if not val:
2100 continue
2101 continue
2101 if op not in opt2revset:
2102 if op not in opt2revset:
2102 continue
2103 continue
2103 revop, andor = opt2revset[op]
2104 revop, andor = opt2revset[op]
2104 if '%(val)' not in revop:
2105 if '%(val)' not in revop:
2105 expr.append(revop)
2106 expr.append(revop)
2106 else:
2107 else:
2107 if not isinstance(val, list):
2108 if not isinstance(val, list):
2108 e = revop % {'val': val}
2109 e = revop % {'val': val}
2109 else:
2110 else:
2110 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2111 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2111 expr.append(e)
2112 expr.append(e)
2112
2113
2113 if expr:
2114 if expr:
2114 expr = '(' + ' and '.join(expr) + ')'
2115 expr = '(' + ' and '.join(expr) + ')'
2115 else:
2116 else:
2116 expr = None
2117 expr = None
2117 return expr, filematcher
2118 return expr, filematcher
2118
2119
2119 def _logrevs(repo, opts):
2120 def _logrevs(repo, opts):
2120 # Default --rev value depends on --follow but --follow behavior
2121 # Default --rev value depends on --follow but --follow behavior
2121 # depends on revisions resolved from --rev...
2122 # depends on revisions resolved from --rev...
2122 follow = opts.get('follow') or opts.get('follow_first')
2123 follow = opts.get('follow') or opts.get('follow_first')
2123 if opts.get('rev'):
2124 if opts.get('rev'):
2124 revs = scmutil.revrange(repo, opts['rev'])
2125 revs = scmutil.revrange(repo, opts['rev'])
2125 elif follow and repo.dirstate.p1() == nullid:
2126 elif follow and repo.dirstate.p1() == nullid:
2126 revs = revset.baseset()
2127 revs = revset.baseset()
2127 elif follow:
2128 elif follow:
2128 revs = repo.revs('reverse(:.)')
2129 revs = repo.revs('reverse(:.)')
2129 else:
2130 else:
2130 revs = revset.spanset(repo)
2131 revs = revset.spanset(repo)
2131 revs.reverse()
2132 revs.reverse()
2132 return revs
2133 return revs
2133
2134
2134 def getgraphlogrevs(repo, pats, opts):
2135 def getgraphlogrevs(repo, pats, opts):
2135 """Return (revs, expr, filematcher) where revs is an iterable of
2136 """Return (revs, expr, filematcher) where revs is an iterable of
2136 revision numbers, expr is a revset string built from log options
2137 revision numbers, expr is a revset string built from log options
2137 and file patterns or None, and used to filter 'revs'. If --stat or
2138 and file patterns or None, and used to filter 'revs'. If --stat or
2138 --patch are not passed filematcher is None. Otherwise it is a
2139 --patch are not passed filematcher is None. Otherwise it is a
2139 callable taking a revision number and returning a match objects
2140 callable taking a revision number and returning a match objects
2140 filtering the files to be detailed when displaying the revision.
2141 filtering the files to be detailed when displaying the revision.
2141 """
2142 """
2142 limit = loglimit(opts)
2143 limit = loglimit(opts)
2143 revs = _logrevs(repo, opts)
2144 revs = _logrevs(repo, opts)
2144 if not revs:
2145 if not revs:
2145 return revset.baseset(), None, None
2146 return revset.baseset(), None, None
2146 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2147 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2147 if opts.get('rev'):
2148 if opts.get('rev'):
2148 # User-specified revs might be unsorted, but don't sort before
2149 # User-specified revs might be unsorted, but don't sort before
2149 # _makelogrevset because it might depend on the order of revs
2150 # _makelogrevset because it might depend on the order of revs
2150 if not (revs.isdescending() or revs.istopo()):
2151 if not (revs.isdescending() or revs.istopo()):
2151 revs.sort(reverse=True)
2152 revs.sort(reverse=True)
2152 if expr:
2153 if expr:
2153 # Revset matchers often operate faster on revisions in changelog
2154 # Revset matchers often operate faster on revisions in changelog
2154 # order, because most filters deal with the changelog.
2155 # order, because most filters deal with the changelog.
2155 revs.reverse()
2156 revs.reverse()
2156 matcher = revset.match(repo.ui, expr)
2157 matcher = revset.match(repo.ui, expr)
2157 # Revset matches can reorder revisions. "A or B" typically returns
2158 # Revset matches can reorder revisions. "A or B" typically returns
2158 # returns the revision matching A then the revision matching B. Sort
2159 # returns the revision matching A then the revision matching B. Sort
2159 # again to fix that.
2160 # again to fix that.
2160 revs = matcher(repo, revs)
2161 revs = matcher(repo, revs)
2161 revs.sort(reverse=True)
2162 revs.sort(reverse=True)
2162 if limit is not None:
2163 if limit is not None:
2163 limitedrevs = []
2164 limitedrevs = []
2164 for idx, rev in enumerate(revs):
2165 for idx, rev in enumerate(revs):
2165 if idx >= limit:
2166 if idx >= limit:
2166 break
2167 break
2167 limitedrevs.append(rev)
2168 limitedrevs.append(rev)
2168 revs = revset.baseset(limitedrevs)
2169 revs = revset.baseset(limitedrevs)
2169
2170
2170 return revs, expr, filematcher
2171 return revs, expr, filematcher
2171
2172
2172 def getlogrevs(repo, pats, opts):
2173 def getlogrevs(repo, pats, opts):
2173 """Return (revs, expr, filematcher) where revs is an iterable of
2174 """Return (revs, expr, filematcher) where revs is an iterable of
2174 revision numbers, expr is a revset string built from log options
2175 revision numbers, expr is a revset string built from log options
2175 and file patterns or None, and used to filter 'revs'. If --stat or
2176 and file patterns or None, and used to filter 'revs'. If --stat or
2176 --patch are not passed filematcher is None. Otherwise it is a
2177 --patch are not passed filematcher is None. Otherwise it is a
2177 callable taking a revision number and returning a match objects
2178 callable taking a revision number and returning a match objects
2178 filtering the files to be detailed when displaying the revision.
2179 filtering the files to be detailed when displaying the revision.
2179 """
2180 """
2180 limit = loglimit(opts)
2181 limit = loglimit(opts)
2181 revs = _logrevs(repo, opts)
2182 revs = _logrevs(repo, opts)
2182 if not revs:
2183 if not revs:
2183 return revset.baseset([]), None, None
2184 return revset.baseset([]), None, None
2184 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2185 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2185 if expr:
2186 if expr:
2186 # Revset matchers often operate faster on revisions in changelog
2187 # Revset matchers often operate faster on revisions in changelog
2187 # order, because most filters deal with the changelog.
2188 # order, because most filters deal with the changelog.
2188 if not opts.get('rev'):
2189 if not opts.get('rev'):
2189 revs.reverse()
2190 revs.reverse()
2190 matcher = revset.match(repo.ui, expr)
2191 matcher = revset.match(repo.ui, expr)
2191 # Revset matches can reorder revisions. "A or B" typically returns
2192 # Revset matches can reorder revisions. "A or B" typically returns
2192 # returns the revision matching A then the revision matching B. Sort
2193 # returns the revision matching A then the revision matching B. Sort
2193 # again to fix that.
2194 # again to fix that.
2194 fixopts = ['branch', 'only_branch', 'keyword', 'user']
2195 fixopts = ['branch', 'only_branch', 'keyword', 'user']
2195 oldrevs = revs
2196 oldrevs = revs
2196 revs = matcher(repo, revs)
2197 revs = matcher(repo, revs)
2197 if not opts.get('rev'):
2198 if not opts.get('rev'):
2198 revs.sort(reverse=True)
2199 revs.sort(reverse=True)
2199 elif len(pats) > 1 or any(len(opts.get(op, [])) > 1 for op in fixopts):
2200 elif len(pats) > 1 or any(len(opts.get(op, [])) > 1 for op in fixopts):
2200 # XXX "A or B" is known to change the order; fix it by filtering
2201 # XXX "A or B" is known to change the order; fix it by filtering
2201 # matched set again (issue5100)
2202 # matched set again (issue5100)
2202 revs = oldrevs & revs
2203 revs = oldrevs & revs
2203 if limit is not None:
2204 if limit is not None:
2204 limitedrevs = []
2205 limitedrevs = []
2205 for idx, r in enumerate(revs):
2206 for idx, r in enumerate(revs):
2206 if limit <= idx:
2207 if limit <= idx:
2207 break
2208 break
2208 limitedrevs.append(r)
2209 limitedrevs.append(r)
2209 revs = revset.baseset(limitedrevs)
2210 revs = revset.baseset(limitedrevs)
2210
2211
2211 return revs, expr, filematcher
2212 return revs, expr, filematcher
2212
2213
2213 def _graphnodeformatter(ui, displayer):
2214 def _graphnodeformatter(ui, displayer):
2214 spec = ui.config('ui', 'graphnodetemplate')
2215 spec = ui.config('ui', 'graphnodetemplate')
2215 if not spec:
2216 if not spec:
2216 return templatekw.showgraphnode # fast path for "{graphnode}"
2217 return templatekw.showgraphnode # fast path for "{graphnode}"
2217
2218
2218 templ = formatter.gettemplater(ui, 'graphnode', spec)
2219 templ = formatter.gettemplater(ui, 'graphnode', spec)
2219 cache = {}
2220 cache = {}
2220 if isinstance(displayer, changeset_templater):
2221 if isinstance(displayer, changeset_templater):
2221 cache = displayer.cache # reuse cache of slow templates
2222 cache = displayer.cache # reuse cache of slow templates
2222 props = templatekw.keywords.copy()
2223 props = templatekw.keywords.copy()
2223 props['templ'] = templ
2224 props['templ'] = templ
2224 props['cache'] = cache
2225 props['cache'] = cache
2225 def formatnode(repo, ctx):
2226 def formatnode(repo, ctx):
2226 props['ctx'] = ctx
2227 props['ctx'] = ctx
2227 props['repo'] = repo
2228 props['repo'] = repo
2228 props['ui'] = repo.ui
2229 props['ui'] = repo.ui
2229 props['revcache'] = {}
2230 props['revcache'] = {}
2230 return templater.stringify(templ('graphnode', **props))
2231 return templater.stringify(templ('graphnode', **props))
2231 return formatnode
2232 return formatnode
2232
2233
2233 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2234 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2234 filematcher=None):
2235 filematcher=None):
2235 formatnode = _graphnodeformatter(ui, displayer)
2236 formatnode = _graphnodeformatter(ui, displayer)
2236 state = graphmod.asciistate()
2237 state = graphmod.asciistate()
2237 styles = state['styles']
2238 styles = state['styles']
2238
2239
2239 # only set graph styling if HGPLAIN is not set.
2240 # only set graph styling if HGPLAIN is not set.
2240 if ui.plain('graph'):
2241 if ui.plain('graph'):
2241 # set all edge styles to |, the default pre-3.8 behaviour
2242 # set all edge styles to |, the default pre-3.8 behaviour
2242 styles.update(dict.fromkeys(styles, '|'))
2243 styles.update(dict.fromkeys(styles, '|'))
2243 else:
2244 else:
2244 edgetypes = {
2245 edgetypes = {
2245 'parent': graphmod.PARENT,
2246 'parent': graphmod.PARENT,
2246 'grandparent': graphmod.GRANDPARENT,
2247 'grandparent': graphmod.GRANDPARENT,
2247 'missing': graphmod.MISSINGPARENT
2248 'missing': graphmod.MISSINGPARENT
2248 }
2249 }
2249 for name, key in edgetypes.items():
2250 for name, key in edgetypes.items():
2250 # experimental config: experimental.graphstyle.*
2251 # experimental config: experimental.graphstyle.*
2251 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2252 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2252 styles[key])
2253 styles[key])
2253 if not styles[key]:
2254 if not styles[key]:
2254 styles[key] = None
2255 styles[key] = None
2255
2256
2256 # experimental config: experimental.graphshorten
2257 # experimental config: experimental.graphshorten
2257 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2258 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2258
2259
2259 for rev, type, ctx, parents in dag:
2260 for rev, type, ctx, parents in dag:
2260 char = formatnode(repo, ctx)
2261 char = formatnode(repo, ctx)
2261 copies = None
2262 copies = None
2262 if getrenamed and ctx.rev():
2263 if getrenamed and ctx.rev():
2263 copies = []
2264 copies = []
2264 for fn in ctx.files():
2265 for fn in ctx.files():
2265 rename = getrenamed(fn, ctx.rev())
2266 rename = getrenamed(fn, ctx.rev())
2266 if rename:
2267 if rename:
2267 copies.append((fn, rename[0]))
2268 copies.append((fn, rename[0]))
2268 revmatchfn = None
2269 revmatchfn = None
2269 if filematcher is not None:
2270 if filematcher is not None:
2270 revmatchfn = filematcher(ctx.rev())
2271 revmatchfn = filematcher(ctx.rev())
2271 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2272 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2272 lines = displayer.hunk.pop(rev).split('\n')
2273 lines = displayer.hunk.pop(rev).split('\n')
2273 if not lines[-1]:
2274 if not lines[-1]:
2274 del lines[-1]
2275 del lines[-1]
2275 displayer.flush(ctx)
2276 displayer.flush(ctx)
2276 edges = edgefn(type, char, lines, state, rev, parents)
2277 edges = edgefn(type, char, lines, state, rev, parents)
2277 for type, char, lines, coldata in edges:
2278 for type, char, lines, coldata in edges:
2278 graphmod.ascii(ui, state, type, char, lines, coldata)
2279 graphmod.ascii(ui, state, type, char, lines, coldata)
2279 displayer.close()
2280 displayer.close()
2280
2281
2281 def graphlog(ui, repo, *pats, **opts):
2282 def graphlog(ui, repo, *pats, **opts):
2282 # Parameters are identical to log command ones
2283 # Parameters are identical to log command ones
2283 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2284 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2284 revdag = graphmod.dagwalker(repo, revs)
2285 revdag = graphmod.dagwalker(repo, revs)
2285
2286
2286 getrenamed = None
2287 getrenamed = None
2287 if opts.get('copies'):
2288 if opts.get('copies'):
2288 endrev = None
2289 endrev = None
2289 if opts.get('rev'):
2290 if opts.get('rev'):
2290 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2291 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2291 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2292 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2292 displayer = show_changeset(ui, repo, opts, buffered=True)
2293 displayer = show_changeset(ui, repo, opts, buffered=True)
2293 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2294 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2294 filematcher)
2295 filematcher)
2295
2296
2296 def checkunsupportedgraphflags(pats, opts):
2297 def checkunsupportedgraphflags(pats, opts):
2297 for op in ["newest_first"]:
2298 for op in ["newest_first"]:
2298 if op in opts and opts[op]:
2299 if op in opts and opts[op]:
2299 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2300 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2300 % op.replace("_", "-"))
2301 % op.replace("_", "-"))
2301
2302
2302 def graphrevs(repo, nodes, opts):
2303 def graphrevs(repo, nodes, opts):
2303 limit = loglimit(opts)
2304 limit = loglimit(opts)
2304 nodes.reverse()
2305 nodes.reverse()
2305 if limit is not None:
2306 if limit is not None:
2306 nodes = nodes[:limit]
2307 nodes = nodes[:limit]
2307 return graphmod.nodes(repo, nodes)
2308 return graphmod.nodes(repo, nodes)
2308
2309
2309 def add(ui, repo, match, prefix, explicitonly, **opts):
2310 def add(ui, repo, match, prefix, explicitonly, **opts):
2310 join = lambda f: os.path.join(prefix, f)
2311 join = lambda f: os.path.join(prefix, f)
2311 bad = []
2312 bad = []
2312
2313
2313 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2314 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2314 names = []
2315 names = []
2315 wctx = repo[None]
2316 wctx = repo[None]
2316 cca = None
2317 cca = None
2317 abort, warn = scmutil.checkportabilityalert(ui)
2318 abort, warn = scmutil.checkportabilityalert(ui)
2318 if abort or warn:
2319 if abort or warn:
2319 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2320 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2320
2321
2321 badmatch = matchmod.badmatch(match, badfn)
2322 badmatch = matchmod.badmatch(match, badfn)
2322 dirstate = repo.dirstate
2323 dirstate = repo.dirstate
2323 # We don't want to just call wctx.walk here, since it would return a lot of
2324 # We don't want to just call wctx.walk here, since it would return a lot of
2324 # clean files, which we aren't interested in and takes time.
2325 # clean files, which we aren't interested in and takes time.
2325 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2326 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2326 True, False, full=False)):
2327 True, False, full=False)):
2327 exact = match.exact(f)
2328 exact = match.exact(f)
2328 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2329 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2329 if cca:
2330 if cca:
2330 cca(f)
2331 cca(f)
2331 names.append(f)
2332 names.append(f)
2332 if ui.verbose or not exact:
2333 if ui.verbose or not exact:
2333 ui.status(_('adding %s\n') % match.rel(f))
2334 ui.status(_('adding %s\n') % match.rel(f))
2334
2335
2335 for subpath in sorted(wctx.substate):
2336 for subpath in sorted(wctx.substate):
2336 sub = wctx.sub(subpath)
2337 sub = wctx.sub(subpath)
2337 try:
2338 try:
2338 submatch = matchmod.subdirmatcher(subpath, match)
2339 submatch = matchmod.subdirmatcher(subpath, match)
2339 if opts.get('subrepos'):
2340 if opts.get('subrepos'):
2340 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2341 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2341 else:
2342 else:
2342 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2343 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2343 except error.LookupError:
2344 except error.LookupError:
2344 ui.status(_("skipping missing subrepository: %s\n")
2345 ui.status(_("skipping missing subrepository: %s\n")
2345 % join(subpath))
2346 % join(subpath))
2346
2347
2347 if not opts.get('dry_run'):
2348 if not opts.get('dry_run'):
2348 rejected = wctx.add(names, prefix)
2349 rejected = wctx.add(names, prefix)
2349 bad.extend(f for f in rejected if f in match.files())
2350 bad.extend(f for f in rejected if f in match.files())
2350 return bad
2351 return bad
2351
2352
2352 def forget(ui, repo, match, prefix, explicitonly):
2353 def forget(ui, repo, match, prefix, explicitonly):
2353 join = lambda f: os.path.join(prefix, f)
2354 join = lambda f: os.path.join(prefix, f)
2354 bad = []
2355 bad = []
2355 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2356 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2356 wctx = repo[None]
2357 wctx = repo[None]
2357 forgot = []
2358 forgot = []
2358
2359
2359 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2360 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2360 forget = sorted(s[0] + s[1] + s[3] + s[6])
2361 forget = sorted(s[0] + s[1] + s[3] + s[6])
2361 if explicitonly:
2362 if explicitonly:
2362 forget = [f for f in forget if match.exact(f)]
2363 forget = [f for f in forget if match.exact(f)]
2363
2364
2364 for subpath in sorted(wctx.substate):
2365 for subpath in sorted(wctx.substate):
2365 sub = wctx.sub(subpath)
2366 sub = wctx.sub(subpath)
2366 try:
2367 try:
2367 submatch = matchmod.subdirmatcher(subpath, match)
2368 submatch = matchmod.subdirmatcher(subpath, match)
2368 subbad, subforgot = sub.forget(submatch, prefix)
2369 subbad, subforgot = sub.forget(submatch, prefix)
2369 bad.extend([subpath + '/' + f for f in subbad])
2370 bad.extend([subpath + '/' + f for f in subbad])
2370 forgot.extend([subpath + '/' + f for f in subforgot])
2371 forgot.extend([subpath + '/' + f for f in subforgot])
2371 except error.LookupError:
2372 except error.LookupError:
2372 ui.status(_("skipping missing subrepository: %s\n")
2373 ui.status(_("skipping missing subrepository: %s\n")
2373 % join(subpath))
2374 % join(subpath))
2374
2375
2375 if not explicitonly:
2376 if not explicitonly:
2376 for f in match.files():
2377 for f in match.files():
2377 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2378 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2378 if f not in forgot:
2379 if f not in forgot:
2379 if repo.wvfs.exists(f):
2380 if repo.wvfs.exists(f):
2380 # Don't complain if the exact case match wasn't given.
2381 # Don't complain if the exact case match wasn't given.
2381 # But don't do this until after checking 'forgot', so
2382 # But don't do this until after checking 'forgot', so
2382 # that subrepo files aren't normalized, and this op is
2383 # that subrepo files aren't normalized, and this op is
2383 # purely from data cached by the status walk above.
2384 # purely from data cached by the status walk above.
2384 if repo.dirstate.normalize(f) in repo.dirstate:
2385 if repo.dirstate.normalize(f) in repo.dirstate:
2385 continue
2386 continue
2386 ui.warn(_('not removing %s: '
2387 ui.warn(_('not removing %s: '
2387 'file is already untracked\n')
2388 'file is already untracked\n')
2388 % match.rel(f))
2389 % match.rel(f))
2389 bad.append(f)
2390 bad.append(f)
2390
2391
2391 for f in forget:
2392 for f in forget:
2392 if ui.verbose or not match.exact(f):
2393 if ui.verbose or not match.exact(f):
2393 ui.status(_('removing %s\n') % match.rel(f))
2394 ui.status(_('removing %s\n') % match.rel(f))
2394
2395
2395 rejected = wctx.forget(forget, prefix)
2396 rejected = wctx.forget(forget, prefix)
2396 bad.extend(f for f in rejected if f in match.files())
2397 bad.extend(f for f in rejected if f in match.files())
2397 forgot.extend(f for f in forget if f not in rejected)
2398 forgot.extend(f for f in forget if f not in rejected)
2398 return bad, forgot
2399 return bad, forgot
2399
2400
2400 def files(ui, ctx, m, fm, fmt, subrepos):
2401 def files(ui, ctx, m, fm, fmt, subrepos):
2401 rev = ctx.rev()
2402 rev = ctx.rev()
2402 ret = 1
2403 ret = 1
2403 ds = ctx.repo().dirstate
2404 ds = ctx.repo().dirstate
2404
2405
2405 for f in ctx.matches(m):
2406 for f in ctx.matches(m):
2406 if rev is None and ds[f] == 'r':
2407 if rev is None and ds[f] == 'r':
2407 continue
2408 continue
2408 fm.startitem()
2409 fm.startitem()
2409 if ui.verbose:
2410 if ui.verbose:
2410 fc = ctx[f]
2411 fc = ctx[f]
2411 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2412 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2412 fm.data(abspath=f)
2413 fm.data(abspath=f)
2413 fm.write('path', fmt, m.rel(f))
2414 fm.write('path', fmt, m.rel(f))
2414 ret = 0
2415 ret = 0
2415
2416
2416 for subpath in sorted(ctx.substate):
2417 for subpath in sorted(ctx.substate):
2417 def matchessubrepo(subpath):
2418 def matchessubrepo(subpath):
2418 return (m.exact(subpath)
2419 return (m.exact(subpath)
2419 or any(f.startswith(subpath + '/') for f in m.files()))
2420 or any(f.startswith(subpath + '/') for f in m.files()))
2420
2421
2421 if subrepos or matchessubrepo(subpath):
2422 if subrepos or matchessubrepo(subpath):
2422 sub = ctx.sub(subpath)
2423 sub = ctx.sub(subpath)
2423 try:
2424 try:
2424 submatch = matchmod.subdirmatcher(subpath, m)
2425 submatch = matchmod.subdirmatcher(subpath, m)
2425 recurse = m.exact(subpath) or subrepos
2426 recurse = m.exact(subpath) or subrepos
2426 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2427 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2427 ret = 0
2428 ret = 0
2428 except error.LookupError:
2429 except error.LookupError:
2429 ui.status(_("skipping missing subrepository: %s\n")
2430 ui.status(_("skipping missing subrepository: %s\n")
2430 % m.abs(subpath))
2431 % m.abs(subpath))
2431
2432
2432 return ret
2433 return ret
2433
2434
2434 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2435 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2435 join = lambda f: os.path.join(prefix, f)
2436 join = lambda f: os.path.join(prefix, f)
2436 ret = 0
2437 ret = 0
2437 s = repo.status(match=m, clean=True)
2438 s = repo.status(match=m, clean=True)
2438 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2439 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2439
2440
2440 wctx = repo[None]
2441 wctx = repo[None]
2441
2442
2442 if warnings is None:
2443 if warnings is None:
2443 warnings = []
2444 warnings = []
2444 warn = True
2445 warn = True
2445 else:
2446 else:
2446 warn = False
2447 warn = False
2447
2448
2448 subs = sorted(wctx.substate)
2449 subs = sorted(wctx.substate)
2449 total = len(subs)
2450 total = len(subs)
2450 count = 0
2451 count = 0
2451 for subpath in subs:
2452 for subpath in subs:
2452 def matchessubrepo(matcher, subpath):
2453 def matchessubrepo(matcher, subpath):
2453 if matcher.exact(subpath):
2454 if matcher.exact(subpath):
2454 return True
2455 return True
2455 for f in matcher.files():
2456 for f in matcher.files():
2456 if f.startswith(subpath):
2457 if f.startswith(subpath):
2457 return True
2458 return True
2458 return False
2459 return False
2459
2460
2460 count += 1
2461 count += 1
2461 if subrepos or matchessubrepo(m, subpath):
2462 if subrepos or matchessubrepo(m, subpath):
2462 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2463 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2463
2464
2464 sub = wctx.sub(subpath)
2465 sub = wctx.sub(subpath)
2465 try:
2466 try:
2466 submatch = matchmod.subdirmatcher(subpath, m)
2467 submatch = matchmod.subdirmatcher(subpath, m)
2467 if sub.removefiles(submatch, prefix, after, force, subrepos,
2468 if sub.removefiles(submatch, prefix, after, force, subrepos,
2468 warnings):
2469 warnings):
2469 ret = 1
2470 ret = 1
2470 except error.LookupError:
2471 except error.LookupError:
2471 warnings.append(_("skipping missing subrepository: %s\n")
2472 warnings.append(_("skipping missing subrepository: %s\n")
2472 % join(subpath))
2473 % join(subpath))
2473 ui.progress(_('searching'), None)
2474 ui.progress(_('searching'), None)
2474
2475
2475 # warn about failure to delete explicit files/dirs
2476 # warn about failure to delete explicit files/dirs
2476 deleteddirs = util.dirs(deleted)
2477 deleteddirs = util.dirs(deleted)
2477 files = m.files()
2478 files = m.files()
2478 total = len(files)
2479 total = len(files)
2479 count = 0
2480 count = 0
2480 for f in files:
2481 for f in files:
2481 def insubrepo():
2482 def insubrepo():
2482 for subpath in wctx.substate:
2483 for subpath in wctx.substate:
2483 if f.startswith(subpath):
2484 if f.startswith(subpath):
2484 return True
2485 return True
2485 return False
2486 return False
2486
2487
2487 count += 1
2488 count += 1
2488 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2489 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2489 isdir = f in deleteddirs or wctx.hasdir(f)
2490 isdir = f in deleteddirs or wctx.hasdir(f)
2490 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2491 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2491 continue
2492 continue
2492
2493
2493 if repo.wvfs.exists(f):
2494 if repo.wvfs.exists(f):
2494 if repo.wvfs.isdir(f):
2495 if repo.wvfs.isdir(f):
2495 warnings.append(_('not removing %s: no tracked files\n')
2496 warnings.append(_('not removing %s: no tracked files\n')
2496 % m.rel(f))
2497 % m.rel(f))
2497 else:
2498 else:
2498 warnings.append(_('not removing %s: file is untracked\n')
2499 warnings.append(_('not removing %s: file is untracked\n')
2499 % m.rel(f))
2500 % m.rel(f))
2500 # missing files will generate a warning elsewhere
2501 # missing files will generate a warning elsewhere
2501 ret = 1
2502 ret = 1
2502 ui.progress(_('deleting'), None)
2503 ui.progress(_('deleting'), None)
2503
2504
2504 if force:
2505 if force:
2505 list = modified + deleted + clean + added
2506 list = modified + deleted + clean + added
2506 elif after:
2507 elif after:
2507 list = deleted
2508 list = deleted
2508 remaining = modified + added + clean
2509 remaining = modified + added + clean
2509 total = len(remaining)
2510 total = len(remaining)
2510 count = 0
2511 count = 0
2511 for f in remaining:
2512 for f in remaining:
2512 count += 1
2513 count += 1
2513 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2514 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2514 warnings.append(_('not removing %s: file still exists\n')
2515 warnings.append(_('not removing %s: file still exists\n')
2515 % m.rel(f))
2516 % m.rel(f))
2516 ret = 1
2517 ret = 1
2517 ui.progress(_('skipping'), None)
2518 ui.progress(_('skipping'), None)
2518 else:
2519 else:
2519 list = deleted + clean
2520 list = deleted + clean
2520 total = len(modified) + len(added)
2521 total = len(modified) + len(added)
2521 count = 0
2522 count = 0
2522 for f in modified:
2523 for f in modified:
2523 count += 1
2524 count += 1
2524 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2525 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2525 warnings.append(_('not removing %s: file is modified (use -f'
2526 warnings.append(_('not removing %s: file is modified (use -f'
2526 ' to force removal)\n') % m.rel(f))
2527 ' to force removal)\n') % m.rel(f))
2527 ret = 1
2528 ret = 1
2528 for f in added:
2529 for f in added:
2529 count += 1
2530 count += 1
2530 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2531 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2531 warnings.append(_('not removing %s: file has been marked for add'
2532 warnings.append(_('not removing %s: file has been marked for add'
2532 ' (use forget to undo)\n') % m.rel(f))
2533 ' (use forget to undo)\n') % m.rel(f))
2533 ret = 1
2534 ret = 1
2534 ui.progress(_('skipping'), None)
2535 ui.progress(_('skipping'), None)
2535
2536
2536 list = sorted(list)
2537 list = sorted(list)
2537 total = len(list)
2538 total = len(list)
2538 count = 0
2539 count = 0
2539 for f in list:
2540 for f in list:
2540 count += 1
2541 count += 1
2541 if ui.verbose or not m.exact(f):
2542 if ui.verbose or not m.exact(f):
2542 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2543 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2543 ui.status(_('removing %s\n') % m.rel(f))
2544 ui.status(_('removing %s\n') % m.rel(f))
2544 ui.progress(_('deleting'), None)
2545 ui.progress(_('deleting'), None)
2545
2546
2546 with repo.wlock():
2547 with repo.wlock():
2547 if not after:
2548 if not after:
2548 for f in list:
2549 for f in list:
2549 if f in added:
2550 if f in added:
2550 continue # we never unlink added files on remove
2551 continue # we never unlink added files on remove
2551 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2552 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2552 repo[None].forget(list)
2553 repo[None].forget(list)
2553
2554
2554 if warn:
2555 if warn:
2555 for warning in warnings:
2556 for warning in warnings:
2556 ui.warn(warning)
2557 ui.warn(warning)
2557
2558
2558 return ret
2559 return ret
2559
2560
2560 def cat(ui, repo, ctx, matcher, prefix, **opts):
2561 def cat(ui, repo, ctx, matcher, prefix, **opts):
2561 err = 1
2562 err = 1
2562
2563
2563 def write(path):
2564 def write(path):
2564 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2565 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2565 pathname=os.path.join(prefix, path))
2566 pathname=os.path.join(prefix, path))
2566 data = ctx[path].data()
2567 data = ctx[path].data()
2567 if opts.get('decode'):
2568 if opts.get('decode'):
2568 data = repo.wwritedata(path, data)
2569 data = repo.wwritedata(path, data)
2569 fp.write(data)
2570 fp.write(data)
2570 fp.close()
2571 fp.close()
2571
2572
2572 # Automation often uses hg cat on single files, so special case it
2573 # Automation often uses hg cat on single files, so special case it
2573 # for performance to avoid the cost of parsing the manifest.
2574 # for performance to avoid the cost of parsing the manifest.
2574 if len(matcher.files()) == 1 and not matcher.anypats():
2575 if len(matcher.files()) == 1 and not matcher.anypats():
2575 file = matcher.files()[0]
2576 file = matcher.files()[0]
2576 mf = repo.manifest
2577 mf = repo.manifest
2577 mfnode = ctx.manifestnode()
2578 mfnode = ctx.manifestnode()
2578 if mfnode and mf.find(mfnode, file)[0]:
2579 if mfnode and mf.find(mfnode, file)[0]:
2579 write(file)
2580 write(file)
2580 return 0
2581 return 0
2581
2582
2582 # Don't warn about "missing" files that are really in subrepos
2583 # Don't warn about "missing" files that are really in subrepos
2583 def badfn(path, msg):
2584 def badfn(path, msg):
2584 for subpath in ctx.substate:
2585 for subpath in ctx.substate:
2585 if path.startswith(subpath):
2586 if path.startswith(subpath):
2586 return
2587 return
2587 matcher.bad(path, msg)
2588 matcher.bad(path, msg)
2588
2589
2589 for abs in ctx.walk(matchmod.badmatch(matcher, badfn)):
2590 for abs in ctx.walk(matchmod.badmatch(matcher, badfn)):
2590 write(abs)
2591 write(abs)
2591 err = 0
2592 err = 0
2592
2593
2593 for subpath in sorted(ctx.substate):
2594 for subpath in sorted(ctx.substate):
2594 sub = ctx.sub(subpath)
2595 sub = ctx.sub(subpath)
2595 try:
2596 try:
2596 submatch = matchmod.subdirmatcher(subpath, matcher)
2597 submatch = matchmod.subdirmatcher(subpath, matcher)
2597
2598
2598 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2599 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2599 **opts):
2600 **opts):
2600 err = 0
2601 err = 0
2601 except error.RepoLookupError:
2602 except error.RepoLookupError:
2602 ui.status(_("skipping missing subrepository: %s\n")
2603 ui.status(_("skipping missing subrepository: %s\n")
2603 % os.path.join(prefix, subpath))
2604 % os.path.join(prefix, subpath))
2604
2605
2605 return err
2606 return err
2606
2607
2607 def commit(ui, repo, commitfunc, pats, opts):
2608 def commit(ui, repo, commitfunc, pats, opts):
2608 '''commit the specified files or all outstanding changes'''
2609 '''commit the specified files or all outstanding changes'''
2609 date = opts.get('date')
2610 date = opts.get('date')
2610 if date:
2611 if date:
2611 opts['date'] = util.parsedate(date)
2612 opts['date'] = util.parsedate(date)
2612 message = logmessage(ui, opts)
2613 message = logmessage(ui, opts)
2613 matcher = scmutil.match(repo[None], pats, opts)
2614 matcher = scmutil.match(repo[None], pats, opts)
2614
2615
2615 # extract addremove carefully -- this function can be called from a command
2616 # extract addremove carefully -- this function can be called from a command
2616 # that doesn't support addremove
2617 # that doesn't support addremove
2617 if opts.get('addremove'):
2618 if opts.get('addremove'):
2618 if scmutil.addremove(repo, matcher, "", opts) != 0:
2619 if scmutil.addremove(repo, matcher, "", opts) != 0:
2619 raise error.Abort(
2620 raise error.Abort(
2620 _("failed to mark all new/missing files as added/removed"))
2621 _("failed to mark all new/missing files as added/removed"))
2621
2622
2622 return commitfunc(ui, repo, message, matcher, opts)
2623 return commitfunc(ui, repo, message, matcher, opts)
2623
2624
2624 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2625 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2625 # avoid cycle context -> subrepo -> cmdutil
2626 # avoid cycle context -> subrepo -> cmdutil
2626 from . import context
2627 from . import context
2627
2628
2628 # amend will reuse the existing user if not specified, but the obsolete
2629 # amend will reuse the existing user if not specified, but the obsolete
2629 # marker creation requires that the current user's name is specified.
2630 # marker creation requires that the current user's name is specified.
2630 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2631 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2631 ui.username() # raise exception if username not set
2632 ui.username() # raise exception if username not set
2632
2633
2633 ui.note(_('amending changeset %s\n') % old)
2634 ui.note(_('amending changeset %s\n') % old)
2634 base = old.p1()
2635 base = old.p1()
2635 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2636 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2636
2637
2637 wlock = lock = newid = None
2638 wlock = lock = newid = None
2638 try:
2639 try:
2639 wlock = repo.wlock()
2640 wlock = repo.wlock()
2640 lock = repo.lock()
2641 lock = repo.lock()
2641 with repo.transaction('amend') as tr:
2642 with repo.transaction('amend') as tr:
2642 # See if we got a message from -m or -l, if not, open the editor
2643 # See if we got a message from -m or -l, if not, open the editor
2643 # with the message of the changeset to amend
2644 # with the message of the changeset to amend
2644 message = logmessage(ui, opts)
2645 message = logmessage(ui, opts)
2645 # ensure logfile does not conflict with later enforcement of the
2646 # ensure logfile does not conflict with later enforcement of the
2646 # message. potential logfile content has been processed by
2647 # message. potential logfile content has been processed by
2647 # `logmessage` anyway.
2648 # `logmessage` anyway.
2648 opts.pop('logfile')
2649 opts.pop('logfile')
2649 # First, do a regular commit to record all changes in the working
2650 # First, do a regular commit to record all changes in the working
2650 # directory (if there are any)
2651 # directory (if there are any)
2651 ui.callhooks = False
2652 ui.callhooks = False
2652 activebookmark = repo._bookmarks.active
2653 activebookmark = repo._bookmarks.active
2653 try:
2654 try:
2654 repo._bookmarks.active = None
2655 repo._bookmarks.active = None
2655 opts['message'] = 'temporary amend commit for %s' % old
2656 opts['message'] = 'temporary amend commit for %s' % old
2656 node = commit(ui, repo, commitfunc, pats, opts)
2657 node = commit(ui, repo, commitfunc, pats, opts)
2657 finally:
2658 finally:
2658 repo._bookmarks.active = activebookmark
2659 repo._bookmarks.active = activebookmark
2659 repo._bookmarks.recordchange(tr)
2660 repo._bookmarks.recordchange(tr)
2660 ui.callhooks = True
2661 ui.callhooks = True
2661 ctx = repo[node]
2662 ctx = repo[node]
2662
2663
2663 # Participating changesets:
2664 # Participating changesets:
2664 #
2665 #
2665 # node/ctx o - new (intermediate) commit that contains changes
2666 # node/ctx o - new (intermediate) commit that contains changes
2666 # | from working dir to go into amending commit
2667 # | from working dir to go into amending commit
2667 # | (or a workingctx if there were no changes)
2668 # | (or a workingctx if there were no changes)
2668 # |
2669 # |
2669 # old o - changeset to amend
2670 # old o - changeset to amend
2670 # |
2671 # |
2671 # base o - parent of amending changeset
2672 # base o - parent of amending changeset
2672
2673
2673 # Update extra dict from amended commit (e.g. to preserve graft
2674 # Update extra dict from amended commit (e.g. to preserve graft
2674 # source)
2675 # source)
2675 extra.update(old.extra())
2676 extra.update(old.extra())
2676
2677
2677 # Also update it from the intermediate commit or from the wctx
2678 # Also update it from the intermediate commit or from the wctx
2678 extra.update(ctx.extra())
2679 extra.update(ctx.extra())
2679
2680
2680 if len(old.parents()) > 1:
2681 if len(old.parents()) > 1:
2681 # ctx.files() isn't reliable for merges, so fall back to the
2682 # ctx.files() isn't reliable for merges, so fall back to the
2682 # slower repo.status() method
2683 # slower repo.status() method
2683 files = set([fn for st in repo.status(base, old)[:3]
2684 files = set([fn for st in repo.status(base, old)[:3]
2684 for fn in st])
2685 for fn in st])
2685 else:
2686 else:
2686 files = set(old.files())
2687 files = set(old.files())
2687
2688
2688 # Second, we use either the commit we just did, or if there were no
2689 # Second, we use either the commit we just did, or if there were no
2689 # changes the parent of the working directory as the version of the
2690 # changes the parent of the working directory as the version of the
2690 # files in the final amend commit
2691 # files in the final amend commit
2691 if node:
2692 if node:
2692 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2693 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2693
2694
2694 user = ctx.user()
2695 user = ctx.user()
2695 date = ctx.date()
2696 date = ctx.date()
2696 # Recompute copies (avoid recording a -> b -> a)
2697 # Recompute copies (avoid recording a -> b -> a)
2697 copied = copies.pathcopies(base, ctx)
2698 copied = copies.pathcopies(base, ctx)
2698 if old.p2:
2699 if old.p2:
2699 copied.update(copies.pathcopies(old.p2(), ctx))
2700 copied.update(copies.pathcopies(old.p2(), ctx))
2700
2701
2701 # Prune files which were reverted by the updates: if old
2702 # Prune files which were reverted by the updates: if old
2702 # introduced file X and our intermediate commit, node,
2703 # introduced file X and our intermediate commit, node,
2703 # renamed that file, then those two files are the same and
2704 # renamed that file, then those two files are the same and
2704 # we can discard X from our list of files. Likewise if X
2705 # we can discard X from our list of files. Likewise if X
2705 # was deleted, it's no longer relevant
2706 # was deleted, it's no longer relevant
2706 files.update(ctx.files())
2707 files.update(ctx.files())
2707
2708
2708 def samefile(f):
2709 def samefile(f):
2709 if f in ctx.manifest():
2710 if f in ctx.manifest():
2710 a = ctx.filectx(f)
2711 a = ctx.filectx(f)
2711 if f in base.manifest():
2712 if f in base.manifest():
2712 b = base.filectx(f)
2713 b = base.filectx(f)
2713 return (not a.cmp(b)
2714 return (not a.cmp(b)
2714 and a.flags() == b.flags())
2715 and a.flags() == b.flags())
2715 else:
2716 else:
2716 return False
2717 return False
2717 else:
2718 else:
2718 return f not in base.manifest()
2719 return f not in base.manifest()
2719 files = [f for f in files if not samefile(f)]
2720 files = [f for f in files if not samefile(f)]
2720
2721
2721 def filectxfn(repo, ctx_, path):
2722 def filectxfn(repo, ctx_, path):
2722 try:
2723 try:
2723 fctx = ctx[path]
2724 fctx = ctx[path]
2724 flags = fctx.flags()
2725 flags = fctx.flags()
2725 mctx = context.memfilectx(repo,
2726 mctx = context.memfilectx(repo,
2726 fctx.path(), fctx.data(),
2727 fctx.path(), fctx.data(),
2727 islink='l' in flags,
2728 islink='l' in flags,
2728 isexec='x' in flags,
2729 isexec='x' in flags,
2729 copied=copied.get(path))
2730 copied=copied.get(path))
2730 return mctx
2731 return mctx
2731 except KeyError:
2732 except KeyError:
2732 return None
2733 return None
2733 else:
2734 else:
2734 ui.note(_('copying changeset %s to %s\n') % (old, base))
2735 ui.note(_('copying changeset %s to %s\n') % (old, base))
2735
2736
2736 # Use version of files as in the old cset
2737 # Use version of files as in the old cset
2737 def filectxfn(repo, ctx_, path):
2738 def filectxfn(repo, ctx_, path):
2738 try:
2739 try:
2739 return old.filectx(path)
2740 return old.filectx(path)
2740 except KeyError:
2741 except KeyError:
2741 return None
2742 return None
2742
2743
2743 user = opts.get('user') or old.user()
2744 user = opts.get('user') or old.user()
2744 date = opts.get('date') or old.date()
2745 date = opts.get('date') or old.date()
2745 editform = mergeeditform(old, 'commit.amend')
2746 editform = mergeeditform(old, 'commit.amend')
2746 editor = getcommiteditor(editform=editform, **opts)
2747 editor = getcommiteditor(editform=editform, **opts)
2747 if not message:
2748 if not message:
2748 editor = getcommiteditor(edit=True, editform=editform)
2749 editor = getcommiteditor(edit=True, editform=editform)
2749 message = old.description()
2750 message = old.description()
2750
2751
2751 pureextra = extra.copy()
2752 pureextra = extra.copy()
2752 extra['amend_source'] = old.hex()
2753 extra['amend_source'] = old.hex()
2753
2754
2754 new = context.memctx(repo,
2755 new = context.memctx(repo,
2755 parents=[base.node(), old.p2().node()],
2756 parents=[base.node(), old.p2().node()],
2756 text=message,
2757 text=message,
2757 files=files,
2758 files=files,
2758 filectxfn=filectxfn,
2759 filectxfn=filectxfn,
2759 user=user,
2760 user=user,
2760 date=date,
2761 date=date,
2761 extra=extra,
2762 extra=extra,
2762 editor=editor)
2763 editor=editor)
2763
2764
2764 newdesc = changelog.stripdesc(new.description())
2765 newdesc = changelog.stripdesc(new.description())
2765 if ((not node)
2766 if ((not node)
2766 and newdesc == old.description()
2767 and newdesc == old.description()
2767 and user == old.user()
2768 and user == old.user()
2768 and date == old.date()
2769 and date == old.date()
2769 and pureextra == old.extra()):
2770 and pureextra == old.extra()):
2770 # nothing changed. continuing here would create a new node
2771 # nothing changed. continuing here would create a new node
2771 # anyway because of the amend_source noise.
2772 # anyway because of the amend_source noise.
2772 #
2773 #
2773 # This not what we expect from amend.
2774 # This not what we expect from amend.
2774 return old.node()
2775 return old.node()
2775
2776
2776 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2777 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2777 try:
2778 try:
2778 if opts.get('secret'):
2779 if opts.get('secret'):
2779 commitphase = 'secret'
2780 commitphase = 'secret'
2780 else:
2781 else:
2781 commitphase = old.phase()
2782 commitphase = old.phase()
2782 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2783 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2783 newid = repo.commitctx(new)
2784 newid = repo.commitctx(new)
2784 finally:
2785 finally:
2785 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2786 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2786 if newid != old.node():
2787 if newid != old.node():
2787 # Reroute the working copy parent to the new changeset
2788 # Reroute the working copy parent to the new changeset
2788 repo.setparents(newid, nullid)
2789 repo.setparents(newid, nullid)
2789
2790
2790 # Move bookmarks from old parent to amend commit
2791 # Move bookmarks from old parent to amend commit
2791 bms = repo.nodebookmarks(old.node())
2792 bms = repo.nodebookmarks(old.node())
2792 if bms:
2793 if bms:
2793 marks = repo._bookmarks
2794 marks = repo._bookmarks
2794 for bm in bms:
2795 for bm in bms:
2795 ui.debug('moving bookmarks %r from %s to %s\n' %
2796 ui.debug('moving bookmarks %r from %s to %s\n' %
2796 (marks, old.hex(), hex(newid)))
2797 (marks, old.hex(), hex(newid)))
2797 marks[bm] = newid
2798 marks[bm] = newid
2798 marks.recordchange(tr)
2799 marks.recordchange(tr)
2799 #commit the whole amend process
2800 #commit the whole amend process
2800 if createmarkers:
2801 if createmarkers:
2801 # mark the new changeset as successor of the rewritten one
2802 # mark the new changeset as successor of the rewritten one
2802 new = repo[newid]
2803 new = repo[newid]
2803 obs = [(old, (new,))]
2804 obs = [(old, (new,))]
2804 if node:
2805 if node:
2805 obs.append((ctx, ()))
2806 obs.append((ctx, ()))
2806
2807
2807 obsolete.createmarkers(repo, obs)
2808 obsolete.createmarkers(repo, obs)
2808 if not createmarkers and newid != old.node():
2809 if not createmarkers and newid != old.node():
2809 # Strip the intermediate commit (if there was one) and the amended
2810 # Strip the intermediate commit (if there was one) and the amended
2810 # commit
2811 # commit
2811 if node:
2812 if node:
2812 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2813 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2813 ui.note(_('stripping amended changeset %s\n') % old)
2814 ui.note(_('stripping amended changeset %s\n') % old)
2814 repair.strip(ui, repo, old.node(), topic='amend-backup')
2815 repair.strip(ui, repo, old.node(), topic='amend-backup')
2815 finally:
2816 finally:
2816 lockmod.release(lock, wlock)
2817 lockmod.release(lock, wlock)
2817 return newid
2818 return newid
2818
2819
2819 def commiteditor(repo, ctx, subs, editform=''):
2820 def commiteditor(repo, ctx, subs, editform=''):
2820 if ctx.description():
2821 if ctx.description():
2821 return ctx.description()
2822 return ctx.description()
2822 return commitforceeditor(repo, ctx, subs, editform=editform,
2823 return commitforceeditor(repo, ctx, subs, editform=editform,
2823 unchangedmessagedetection=True)
2824 unchangedmessagedetection=True)
2824
2825
2825 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2826 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2826 editform='', unchangedmessagedetection=False):
2827 editform='', unchangedmessagedetection=False):
2827 if not extramsg:
2828 if not extramsg:
2828 extramsg = _("Leave message empty to abort commit.")
2829 extramsg = _("Leave message empty to abort commit.")
2829
2830
2830 forms = [e for e in editform.split('.') if e]
2831 forms = [e for e in editform.split('.') if e]
2831 forms.insert(0, 'changeset')
2832 forms.insert(0, 'changeset')
2832 templatetext = None
2833 templatetext = None
2833 while forms:
2834 while forms:
2834 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2835 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2835 if tmpl:
2836 if tmpl:
2836 templatetext = committext = buildcommittemplate(
2837 templatetext = committext = buildcommittemplate(
2837 repo, ctx, subs, extramsg, tmpl)
2838 repo, ctx, subs, extramsg, tmpl)
2838 break
2839 break
2839 forms.pop()
2840 forms.pop()
2840 else:
2841 else:
2841 committext = buildcommittext(repo, ctx, subs, extramsg)
2842 committext = buildcommittext(repo, ctx, subs, extramsg)
2842
2843
2843 # run editor in the repository root
2844 # run editor in the repository root
2844 olddir = os.getcwd()
2845 olddir = os.getcwd()
2845 os.chdir(repo.root)
2846 os.chdir(repo.root)
2846
2847
2847 # make in-memory changes visible to external process
2848 # make in-memory changes visible to external process
2848 tr = repo.currenttransaction()
2849 tr = repo.currenttransaction()
2849 repo.dirstate.write(tr)
2850 repo.dirstate.write(tr)
2850 pending = tr and tr.writepending() and repo.root
2851 pending = tr and tr.writepending() and repo.root
2851
2852
2852 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2853 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2853 editform=editform, pending=pending)
2854 editform=editform, pending=pending)
2854 text = re.sub("(?m)^HG:.*(\n|$)", "", editortext)
2855 text = re.sub("(?m)^HG:.*(\n|$)", "", editortext)
2855 os.chdir(olddir)
2856 os.chdir(olddir)
2856
2857
2857 if finishdesc:
2858 if finishdesc:
2858 text = finishdesc(text)
2859 text = finishdesc(text)
2859 if not text.strip():
2860 if not text.strip():
2860 raise error.Abort(_("empty commit message"))
2861 raise error.Abort(_("empty commit message"))
2861 if unchangedmessagedetection and editortext == templatetext:
2862 if unchangedmessagedetection and editortext == templatetext:
2862 raise error.Abort(_("commit message unchanged"))
2863 raise error.Abort(_("commit message unchanged"))
2863
2864
2864 return text
2865 return text
2865
2866
2866 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2867 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2867 ui = repo.ui
2868 ui = repo.ui
2868 tmpl, mapfile = gettemplate(ui, tmpl, None)
2869 tmpl, mapfile = gettemplate(ui, tmpl, None)
2869
2870
2870 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2871 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2871
2872
2872 for k, v in repo.ui.configitems('committemplate'):
2873 for k, v in repo.ui.configitems('committemplate'):
2873 if k != 'changeset':
2874 if k != 'changeset':
2874 t.t.cache[k] = v
2875 t.t.cache[k] = v
2875
2876
2876 if not extramsg:
2877 if not extramsg:
2877 extramsg = '' # ensure that extramsg is string
2878 extramsg = '' # ensure that extramsg is string
2878
2879
2879 ui.pushbuffer()
2880 ui.pushbuffer()
2880 t.show(ctx, extramsg=extramsg)
2881 t.show(ctx, extramsg=extramsg)
2881 return ui.popbuffer()
2882 return ui.popbuffer()
2882
2883
2883 def hgprefix(msg):
2884 def hgprefix(msg):
2884 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2885 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2885
2886
2886 def buildcommittext(repo, ctx, subs, extramsg):
2887 def buildcommittext(repo, ctx, subs, extramsg):
2887 edittext = []
2888 edittext = []
2888 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2889 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2889 if ctx.description():
2890 if ctx.description():
2890 edittext.append(ctx.description())
2891 edittext.append(ctx.description())
2891 edittext.append("")
2892 edittext.append("")
2892 edittext.append("") # Empty line between message and comments.
2893 edittext.append("") # Empty line between message and comments.
2893 edittext.append(hgprefix(_("Enter commit message."
2894 edittext.append(hgprefix(_("Enter commit message."
2894 " Lines beginning with 'HG:' are removed.")))
2895 " Lines beginning with 'HG:' are removed.")))
2895 edittext.append(hgprefix(extramsg))
2896 edittext.append(hgprefix(extramsg))
2896 edittext.append("HG: --")
2897 edittext.append("HG: --")
2897 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2898 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2898 if ctx.p2():
2899 if ctx.p2():
2899 edittext.append(hgprefix(_("branch merge")))
2900 edittext.append(hgprefix(_("branch merge")))
2900 if ctx.branch():
2901 if ctx.branch():
2901 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2902 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2902 if bookmarks.isactivewdirparent(repo):
2903 if bookmarks.isactivewdirparent(repo):
2903 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2904 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2904 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2905 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2905 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2906 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2906 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2907 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2907 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2908 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2908 if not added and not modified and not removed:
2909 if not added and not modified and not removed:
2909 edittext.append(hgprefix(_("no files changed")))
2910 edittext.append(hgprefix(_("no files changed")))
2910 edittext.append("")
2911 edittext.append("")
2911
2912
2912 return "\n".join(edittext)
2913 return "\n".join(edittext)
2913
2914
2914 def commitstatus(repo, node, branch, bheads=None, opts=None):
2915 def commitstatus(repo, node, branch, bheads=None, opts=None):
2915 if opts is None:
2916 if opts is None:
2916 opts = {}
2917 opts = {}
2917 ctx = repo[node]
2918 ctx = repo[node]
2918 parents = ctx.parents()
2919 parents = ctx.parents()
2919
2920
2920 if (not opts.get('amend') and bheads and node not in bheads and not
2921 if (not opts.get('amend') and bheads and node not in bheads and not
2921 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2922 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2922 repo.ui.status(_('created new head\n'))
2923 repo.ui.status(_('created new head\n'))
2923 # The message is not printed for initial roots. For the other
2924 # The message is not printed for initial roots. For the other
2924 # changesets, it is printed in the following situations:
2925 # changesets, it is printed in the following situations:
2925 #
2926 #
2926 # Par column: for the 2 parents with ...
2927 # Par column: for the 2 parents with ...
2927 # N: null or no parent
2928 # N: null or no parent
2928 # B: parent is on another named branch
2929 # B: parent is on another named branch
2929 # C: parent is a regular non head changeset
2930 # C: parent is a regular non head changeset
2930 # H: parent was a branch head of the current branch
2931 # H: parent was a branch head of the current branch
2931 # Msg column: whether we print "created new head" message
2932 # Msg column: whether we print "created new head" message
2932 # In the following, it is assumed that there already exists some
2933 # In the following, it is assumed that there already exists some
2933 # initial branch heads of the current branch, otherwise nothing is
2934 # initial branch heads of the current branch, otherwise nothing is
2934 # printed anyway.
2935 # printed anyway.
2935 #
2936 #
2936 # Par Msg Comment
2937 # Par Msg Comment
2937 # N N y additional topo root
2938 # N N y additional topo root
2938 #
2939 #
2939 # B N y additional branch root
2940 # B N y additional branch root
2940 # C N y additional topo head
2941 # C N y additional topo head
2941 # H N n usual case
2942 # H N n usual case
2942 #
2943 #
2943 # B B y weird additional branch root
2944 # B B y weird additional branch root
2944 # C B y branch merge
2945 # C B y branch merge
2945 # H B n merge with named branch
2946 # H B n merge with named branch
2946 #
2947 #
2947 # C C y additional head from merge
2948 # C C y additional head from merge
2948 # C H n merge with a head
2949 # C H n merge with a head
2949 #
2950 #
2950 # H H n head merge: head count decreases
2951 # H H n head merge: head count decreases
2951
2952
2952 if not opts.get('close_branch'):
2953 if not opts.get('close_branch'):
2953 for r in parents:
2954 for r in parents:
2954 if r.closesbranch() and r.branch() == branch:
2955 if r.closesbranch() and r.branch() == branch:
2955 repo.ui.status(_('reopening closed branch head %d\n') % r)
2956 repo.ui.status(_('reopening closed branch head %d\n') % r)
2956
2957
2957 if repo.ui.debugflag:
2958 if repo.ui.debugflag:
2958 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2959 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2959 elif repo.ui.verbose:
2960 elif repo.ui.verbose:
2960 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2961 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2961
2962
2962 def postcommitstatus(repo, pats, opts):
2963 def postcommitstatus(repo, pats, opts):
2963 return repo.status(match=scmutil.match(repo[None], pats, opts))
2964 return repo.status(match=scmutil.match(repo[None], pats, opts))
2964
2965
2965 def revert(ui, repo, ctx, parents, *pats, **opts):
2966 def revert(ui, repo, ctx, parents, *pats, **opts):
2966 parent, p2 = parents
2967 parent, p2 = parents
2967 node = ctx.node()
2968 node = ctx.node()
2968
2969
2969 mf = ctx.manifest()
2970 mf = ctx.manifest()
2970 if node == p2:
2971 if node == p2:
2971 parent = p2
2972 parent = p2
2972
2973
2973 # need all matching names in dirstate and manifest of target rev,
2974 # need all matching names in dirstate and manifest of target rev,
2974 # so have to walk both. do not print errors if files exist in one
2975 # so have to walk both. do not print errors if files exist in one
2975 # but not other. in both cases, filesets should be evaluated against
2976 # but not other. in both cases, filesets should be evaluated against
2976 # workingctx to get consistent result (issue4497). this means 'set:**'
2977 # workingctx to get consistent result (issue4497). this means 'set:**'
2977 # cannot be used to select missing files from target rev.
2978 # cannot be used to select missing files from target rev.
2978
2979
2979 # `names` is a mapping for all elements in working copy and target revision
2980 # `names` is a mapping for all elements in working copy and target revision
2980 # The mapping is in the form:
2981 # The mapping is in the form:
2981 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2982 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2982 names = {}
2983 names = {}
2983
2984
2984 with repo.wlock():
2985 with repo.wlock():
2985 ## filling of the `names` mapping
2986 ## filling of the `names` mapping
2986 # walk dirstate to fill `names`
2987 # walk dirstate to fill `names`
2987
2988
2988 interactive = opts.get('interactive', False)
2989 interactive = opts.get('interactive', False)
2989 wctx = repo[None]
2990 wctx = repo[None]
2990 m = scmutil.match(wctx, pats, opts)
2991 m = scmutil.match(wctx, pats, opts)
2991
2992
2992 # we'll need this later
2993 # we'll need this later
2993 targetsubs = sorted(s for s in wctx.substate if m(s))
2994 targetsubs = sorted(s for s in wctx.substate if m(s))
2994
2995
2995 if not m.always():
2996 if not m.always():
2996 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2997 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2997 names[abs] = m.rel(abs), m.exact(abs)
2998 names[abs] = m.rel(abs), m.exact(abs)
2998
2999
2999 # walk target manifest to fill `names`
3000 # walk target manifest to fill `names`
3000
3001
3001 def badfn(path, msg):
3002 def badfn(path, msg):
3002 if path in names:
3003 if path in names:
3003 return
3004 return
3004 if path in ctx.substate:
3005 if path in ctx.substate:
3005 return
3006 return
3006 path_ = path + '/'
3007 path_ = path + '/'
3007 for f in names:
3008 for f in names:
3008 if f.startswith(path_):
3009 if f.startswith(path_):
3009 return
3010 return
3010 ui.warn("%s: %s\n" % (m.rel(path), msg))
3011 ui.warn("%s: %s\n" % (m.rel(path), msg))
3011
3012
3012 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3013 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3013 if abs not in names:
3014 if abs not in names:
3014 names[abs] = m.rel(abs), m.exact(abs)
3015 names[abs] = m.rel(abs), m.exact(abs)
3015
3016
3016 # Find status of all file in `names`.
3017 # Find status of all file in `names`.
3017 m = scmutil.matchfiles(repo, names)
3018 m = scmutil.matchfiles(repo, names)
3018
3019
3019 changes = repo.status(node1=node, match=m,
3020 changes = repo.status(node1=node, match=m,
3020 unknown=True, ignored=True, clean=True)
3021 unknown=True, ignored=True, clean=True)
3021 else:
3022 else:
3022 changes = repo.status(node1=node, match=m)
3023 changes = repo.status(node1=node, match=m)
3023 for kind in changes:
3024 for kind in changes:
3024 for abs in kind:
3025 for abs in kind:
3025 names[abs] = m.rel(abs), m.exact(abs)
3026 names[abs] = m.rel(abs), m.exact(abs)
3026
3027
3027 m = scmutil.matchfiles(repo, names)
3028 m = scmutil.matchfiles(repo, names)
3028
3029
3029 modified = set(changes.modified)
3030 modified = set(changes.modified)
3030 added = set(changes.added)
3031 added = set(changes.added)
3031 removed = set(changes.removed)
3032 removed = set(changes.removed)
3032 _deleted = set(changes.deleted)
3033 _deleted = set(changes.deleted)
3033 unknown = set(changes.unknown)
3034 unknown = set(changes.unknown)
3034 unknown.update(changes.ignored)
3035 unknown.update(changes.ignored)
3035 clean = set(changes.clean)
3036 clean = set(changes.clean)
3036 modadded = set()
3037 modadded = set()
3037
3038
3038 # split between files known in target manifest and the others
3039 # split between files known in target manifest and the others
3039 smf = set(mf)
3040 smf = set(mf)
3040
3041
3041 # determine the exact nature of the deleted changesets
3042 # determine the exact nature of the deleted changesets
3042 deladded = _deleted - smf
3043 deladded = _deleted - smf
3043 deleted = _deleted - deladded
3044 deleted = _deleted - deladded
3044
3045
3045 # We need to account for the state of the file in the dirstate,
3046 # We need to account for the state of the file in the dirstate,
3046 # even when we revert against something else than parent. This will
3047 # even when we revert against something else than parent. This will
3047 # slightly alter the behavior of revert (doing back up or not, delete
3048 # slightly alter the behavior of revert (doing back up or not, delete
3048 # or just forget etc).
3049 # or just forget etc).
3049 if parent == node:
3050 if parent == node:
3050 dsmodified = modified
3051 dsmodified = modified
3051 dsadded = added
3052 dsadded = added
3052 dsremoved = removed
3053 dsremoved = removed
3053 # store all local modifications, useful later for rename detection
3054 # store all local modifications, useful later for rename detection
3054 localchanges = dsmodified | dsadded
3055 localchanges = dsmodified | dsadded
3055 modified, added, removed = set(), set(), set()
3056 modified, added, removed = set(), set(), set()
3056 else:
3057 else:
3057 changes = repo.status(node1=parent, match=m)
3058 changes = repo.status(node1=parent, match=m)
3058 dsmodified = set(changes.modified)
3059 dsmodified = set(changes.modified)
3059 dsadded = set(changes.added)
3060 dsadded = set(changes.added)
3060 dsremoved = set(changes.removed)
3061 dsremoved = set(changes.removed)
3061 # store all local modifications, useful later for rename detection
3062 # store all local modifications, useful later for rename detection
3062 localchanges = dsmodified | dsadded
3063 localchanges = dsmodified | dsadded
3063
3064
3064 # only take into account for removes between wc and target
3065 # only take into account for removes between wc and target
3065 clean |= dsremoved - removed
3066 clean |= dsremoved - removed
3066 dsremoved &= removed
3067 dsremoved &= removed
3067 # distinct between dirstate remove and other
3068 # distinct between dirstate remove and other
3068 removed -= dsremoved
3069 removed -= dsremoved
3069
3070
3070 modadded = added & dsmodified
3071 modadded = added & dsmodified
3071 added -= modadded
3072 added -= modadded
3072
3073
3073 # tell newly modified apart.
3074 # tell newly modified apart.
3074 dsmodified &= modified
3075 dsmodified &= modified
3075 dsmodified |= modified & dsadded # dirstate added may need backup
3076 dsmodified |= modified & dsadded # dirstate added may need backup
3076 modified -= dsmodified
3077 modified -= dsmodified
3077
3078
3078 # We need to wait for some post-processing to update this set
3079 # We need to wait for some post-processing to update this set
3079 # before making the distinction. The dirstate will be used for
3080 # before making the distinction. The dirstate will be used for
3080 # that purpose.
3081 # that purpose.
3081 dsadded = added
3082 dsadded = added
3082
3083
3083 # in case of merge, files that are actually added can be reported as
3084 # in case of merge, files that are actually added can be reported as
3084 # modified, we need to post process the result
3085 # modified, we need to post process the result
3085 if p2 != nullid:
3086 if p2 != nullid:
3086 mergeadd = dsmodified - smf
3087 mergeadd = dsmodified - smf
3087 dsadded |= mergeadd
3088 dsadded |= mergeadd
3088 dsmodified -= mergeadd
3089 dsmodified -= mergeadd
3089
3090
3090 # if f is a rename, update `names` to also revert the source
3091 # if f is a rename, update `names` to also revert the source
3091 cwd = repo.getcwd()
3092 cwd = repo.getcwd()
3092 for f in localchanges:
3093 for f in localchanges:
3093 src = repo.dirstate.copied(f)
3094 src = repo.dirstate.copied(f)
3094 # XXX should we check for rename down to target node?
3095 # XXX should we check for rename down to target node?
3095 if src and src not in names and repo.dirstate[src] == 'r':
3096 if src and src not in names and repo.dirstate[src] == 'r':
3096 dsremoved.add(src)
3097 dsremoved.add(src)
3097 names[src] = (repo.pathto(src, cwd), True)
3098 names[src] = (repo.pathto(src, cwd), True)
3098
3099
3099 # distinguish between file to forget and the other
3100 # distinguish between file to forget and the other
3100 added = set()
3101 added = set()
3101 for abs in dsadded:
3102 for abs in dsadded:
3102 if repo.dirstate[abs] != 'a':
3103 if repo.dirstate[abs] != 'a':
3103 added.add(abs)
3104 added.add(abs)
3104 dsadded -= added
3105 dsadded -= added
3105
3106
3106 for abs in deladded:
3107 for abs in deladded:
3107 if repo.dirstate[abs] == 'a':
3108 if repo.dirstate[abs] == 'a':
3108 dsadded.add(abs)
3109 dsadded.add(abs)
3109 deladded -= dsadded
3110 deladded -= dsadded
3110
3111
3111 # For files marked as removed, we check if an unknown file is present at
3112 # For files marked as removed, we check if an unknown file is present at
3112 # the same path. If a such file exists it may need to be backed up.
3113 # the same path. If a such file exists it may need to be backed up.
3113 # Making the distinction at this stage helps have simpler backup
3114 # Making the distinction at this stage helps have simpler backup
3114 # logic.
3115 # logic.
3115 removunk = set()
3116 removunk = set()
3116 for abs in removed:
3117 for abs in removed:
3117 target = repo.wjoin(abs)
3118 target = repo.wjoin(abs)
3118 if os.path.lexists(target):
3119 if os.path.lexists(target):
3119 removunk.add(abs)
3120 removunk.add(abs)
3120 removed -= removunk
3121 removed -= removunk
3121
3122
3122 dsremovunk = set()
3123 dsremovunk = set()
3123 for abs in dsremoved:
3124 for abs in dsremoved:
3124 target = repo.wjoin(abs)
3125 target = repo.wjoin(abs)
3125 if os.path.lexists(target):
3126 if os.path.lexists(target):
3126 dsremovunk.add(abs)
3127 dsremovunk.add(abs)
3127 dsremoved -= dsremovunk
3128 dsremoved -= dsremovunk
3128
3129
3129 # action to be actually performed by revert
3130 # action to be actually performed by revert
3130 # (<list of file>, message>) tuple
3131 # (<list of file>, message>) tuple
3131 actions = {'revert': ([], _('reverting %s\n')),
3132 actions = {'revert': ([], _('reverting %s\n')),
3132 'add': ([], _('adding %s\n')),
3133 'add': ([], _('adding %s\n')),
3133 'remove': ([], _('removing %s\n')),
3134 'remove': ([], _('removing %s\n')),
3134 'drop': ([], _('removing %s\n')),
3135 'drop': ([], _('removing %s\n')),
3135 'forget': ([], _('forgetting %s\n')),
3136 'forget': ([], _('forgetting %s\n')),
3136 'undelete': ([], _('undeleting %s\n')),
3137 'undelete': ([], _('undeleting %s\n')),
3137 'noop': (None, _('no changes needed to %s\n')),
3138 'noop': (None, _('no changes needed to %s\n')),
3138 'unknown': (None, _('file not managed: %s\n')),
3139 'unknown': (None, _('file not managed: %s\n')),
3139 }
3140 }
3140
3141
3141 # "constant" that convey the backup strategy.
3142 # "constant" that convey the backup strategy.
3142 # All set to `discard` if `no-backup` is set do avoid checking
3143 # All set to `discard` if `no-backup` is set do avoid checking
3143 # no_backup lower in the code.
3144 # no_backup lower in the code.
3144 # These values are ordered for comparison purposes
3145 # These values are ordered for comparison purposes
3145 backup = 2 # unconditionally do backup
3146 backup = 2 # unconditionally do backup
3146 check = 1 # check if the existing file differs from target
3147 check = 1 # check if the existing file differs from target
3147 discard = 0 # never do backup
3148 discard = 0 # never do backup
3148 if opts.get('no_backup'):
3149 if opts.get('no_backup'):
3149 backup = check = discard
3150 backup = check = discard
3150
3151
3151 backupanddel = actions['remove']
3152 backupanddel = actions['remove']
3152 if not opts.get('no_backup'):
3153 if not opts.get('no_backup'):
3153 backupanddel = actions['drop']
3154 backupanddel = actions['drop']
3154
3155
3155 disptable = (
3156 disptable = (
3156 # dispatch table:
3157 # dispatch table:
3157 # file state
3158 # file state
3158 # action
3159 # action
3159 # make backup
3160 # make backup
3160
3161
3161 ## Sets that results that will change file on disk
3162 ## Sets that results that will change file on disk
3162 # Modified compared to target, no local change
3163 # Modified compared to target, no local change
3163 (modified, actions['revert'], discard),
3164 (modified, actions['revert'], discard),
3164 # Modified compared to target, but local file is deleted
3165 # Modified compared to target, but local file is deleted
3165 (deleted, actions['revert'], discard),
3166 (deleted, actions['revert'], discard),
3166 # Modified compared to target, local change
3167 # Modified compared to target, local change
3167 (dsmodified, actions['revert'], backup),
3168 (dsmodified, actions['revert'], backup),
3168 # Added since target
3169 # Added since target
3169 (added, actions['remove'], discard),
3170 (added, actions['remove'], discard),
3170 # Added in working directory
3171 # Added in working directory
3171 (dsadded, actions['forget'], discard),
3172 (dsadded, actions['forget'], discard),
3172 # Added since target, have local modification
3173 # Added since target, have local modification
3173 (modadded, backupanddel, backup),
3174 (modadded, backupanddel, backup),
3174 # Added since target but file is missing in working directory
3175 # Added since target but file is missing in working directory
3175 (deladded, actions['drop'], discard),
3176 (deladded, actions['drop'], discard),
3176 # Removed since target, before working copy parent
3177 # Removed since target, before working copy parent
3177 (removed, actions['add'], discard),
3178 (removed, actions['add'], discard),
3178 # Same as `removed` but an unknown file exists at the same path
3179 # Same as `removed` but an unknown file exists at the same path
3179 (removunk, actions['add'], check),
3180 (removunk, actions['add'], check),
3180 # Removed since targe, marked as such in working copy parent
3181 # Removed since targe, marked as such in working copy parent
3181 (dsremoved, actions['undelete'], discard),
3182 (dsremoved, actions['undelete'], discard),
3182 # Same as `dsremoved` but an unknown file exists at the same path
3183 # Same as `dsremoved` but an unknown file exists at the same path
3183 (dsremovunk, actions['undelete'], check),
3184 (dsremovunk, actions['undelete'], check),
3184 ## the following sets does not result in any file changes
3185 ## the following sets does not result in any file changes
3185 # File with no modification
3186 # File with no modification
3186 (clean, actions['noop'], discard),
3187 (clean, actions['noop'], discard),
3187 # Existing file, not tracked anywhere
3188 # Existing file, not tracked anywhere
3188 (unknown, actions['unknown'], discard),
3189 (unknown, actions['unknown'], discard),
3189 )
3190 )
3190
3191
3191 for abs, (rel, exact) in sorted(names.items()):
3192 for abs, (rel, exact) in sorted(names.items()):
3192 # target file to be touch on disk (relative to cwd)
3193 # target file to be touch on disk (relative to cwd)
3193 target = repo.wjoin(abs)
3194 target = repo.wjoin(abs)
3194 # search the entry in the dispatch table.
3195 # search the entry in the dispatch table.
3195 # if the file is in any of these sets, it was touched in the working
3196 # if the file is in any of these sets, it was touched in the working
3196 # directory parent and we are sure it needs to be reverted.
3197 # directory parent and we are sure it needs to be reverted.
3197 for table, (xlist, msg), dobackup in disptable:
3198 for table, (xlist, msg), dobackup in disptable:
3198 if abs not in table:
3199 if abs not in table:
3199 continue
3200 continue
3200 if xlist is not None:
3201 if xlist is not None:
3201 xlist.append(abs)
3202 xlist.append(abs)
3202 if dobackup and (backup <= dobackup
3203 if dobackup and (backup <= dobackup
3203 or wctx[abs].cmp(ctx[abs])):
3204 or wctx[abs].cmp(ctx[abs])):
3204 bakname = scmutil.origpath(ui, repo, rel)
3205 bakname = scmutil.origpath(ui, repo, rel)
3205 ui.note(_('saving current version of %s as %s\n') %
3206 ui.note(_('saving current version of %s as %s\n') %
3206 (rel, bakname))
3207 (rel, bakname))
3207 if not opts.get('dry_run'):
3208 if not opts.get('dry_run'):
3208 if interactive:
3209 if interactive:
3209 util.copyfile(target, bakname)
3210 util.copyfile(target, bakname)
3210 else:
3211 else:
3211 util.rename(target, bakname)
3212 util.rename(target, bakname)
3212 if ui.verbose or not exact:
3213 if ui.verbose or not exact:
3213 if not isinstance(msg, basestring):
3214 if not isinstance(msg, basestring):
3214 msg = msg(abs)
3215 msg = msg(abs)
3215 ui.status(msg % rel)
3216 ui.status(msg % rel)
3216 elif exact:
3217 elif exact:
3217 ui.warn(msg % rel)
3218 ui.warn(msg % rel)
3218 break
3219 break
3219
3220
3220 if not opts.get('dry_run'):
3221 if not opts.get('dry_run'):
3221 needdata = ('revert', 'add', 'undelete')
3222 needdata = ('revert', 'add', 'undelete')
3222 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3223 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3223 _performrevert(repo, parents, ctx, actions, interactive)
3224 _performrevert(repo, parents, ctx, actions, interactive)
3224
3225
3225 if targetsubs:
3226 if targetsubs:
3226 # Revert the subrepos on the revert list
3227 # Revert the subrepos on the revert list
3227 for sub in targetsubs:
3228 for sub in targetsubs:
3228 try:
3229 try:
3229 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3230 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3230 except KeyError:
3231 except KeyError:
3231 raise error.Abort("subrepository '%s' does not exist in %s!"
3232 raise error.Abort("subrepository '%s' does not exist in %s!"
3232 % (sub, short(ctx.node())))
3233 % (sub, short(ctx.node())))
3233
3234
3234 def _revertprefetch(repo, ctx, *files):
3235 def _revertprefetch(repo, ctx, *files):
3235 """Let extension changing the storage layer prefetch content"""
3236 """Let extension changing the storage layer prefetch content"""
3236 pass
3237 pass
3237
3238
3238 def _performrevert(repo, parents, ctx, actions, interactive=False):
3239 def _performrevert(repo, parents, ctx, actions, interactive=False):
3239 """function that actually perform all the actions computed for revert
3240 """function that actually perform all the actions computed for revert
3240
3241
3241 This is an independent function to let extension to plug in and react to
3242 This is an independent function to let extension to plug in and react to
3242 the imminent revert.
3243 the imminent revert.
3243
3244
3244 Make sure you have the working directory locked when calling this function.
3245 Make sure you have the working directory locked when calling this function.
3245 """
3246 """
3246 parent, p2 = parents
3247 parent, p2 = parents
3247 node = ctx.node()
3248 node = ctx.node()
3248 excluded_files = []
3249 excluded_files = []
3249 matcher_opts = {"exclude": excluded_files}
3250 matcher_opts = {"exclude": excluded_files}
3250
3251
3251 def checkout(f):
3252 def checkout(f):
3252 fc = ctx[f]
3253 fc = ctx[f]
3253 repo.wwrite(f, fc.data(), fc.flags())
3254 repo.wwrite(f, fc.data(), fc.flags())
3254
3255
3255 audit_path = pathutil.pathauditor(repo.root)
3256 audit_path = pathutil.pathauditor(repo.root)
3256 for f in actions['forget'][0]:
3257 for f in actions['forget'][0]:
3257 if interactive:
3258 if interactive:
3258 choice = \
3259 choice = \
3259 repo.ui.promptchoice(
3260 repo.ui.promptchoice(
3260 _("forget added file %s (yn)?$$ &Yes $$ &No")
3261 _("forget added file %s (yn)?$$ &Yes $$ &No")
3261 % f)
3262 % f)
3262 if choice == 0:
3263 if choice == 0:
3263 repo.dirstate.drop(f)
3264 repo.dirstate.drop(f)
3264 else:
3265 else:
3265 excluded_files.append(repo.wjoin(f))
3266 excluded_files.append(repo.wjoin(f))
3266 else:
3267 else:
3267 repo.dirstate.drop(f)
3268 repo.dirstate.drop(f)
3268 for f in actions['remove'][0]:
3269 for f in actions['remove'][0]:
3269 audit_path(f)
3270 audit_path(f)
3270 try:
3271 try:
3271 util.unlinkpath(repo.wjoin(f))
3272 util.unlinkpath(repo.wjoin(f))
3272 except OSError:
3273 except OSError:
3273 pass
3274 pass
3274 repo.dirstate.remove(f)
3275 repo.dirstate.remove(f)
3275 for f in actions['drop'][0]:
3276 for f in actions['drop'][0]:
3276 audit_path(f)
3277 audit_path(f)
3277 repo.dirstate.remove(f)
3278 repo.dirstate.remove(f)
3278
3279
3279 normal = None
3280 normal = None
3280 if node == parent:
3281 if node == parent:
3281 # We're reverting to our parent. If possible, we'd like status
3282 # We're reverting to our parent. If possible, we'd like status
3282 # to report the file as clean. We have to use normallookup for
3283 # to report the file as clean. We have to use normallookup for
3283 # merges to avoid losing information about merged/dirty files.
3284 # merges to avoid losing information about merged/dirty files.
3284 if p2 != nullid:
3285 if p2 != nullid:
3285 normal = repo.dirstate.normallookup
3286 normal = repo.dirstate.normallookup
3286 else:
3287 else:
3287 normal = repo.dirstate.normal
3288 normal = repo.dirstate.normal
3288
3289
3289 newlyaddedandmodifiedfiles = set()
3290 newlyaddedandmodifiedfiles = set()
3290 if interactive:
3291 if interactive:
3291 # Prompt the user for changes to revert
3292 # Prompt the user for changes to revert
3292 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3293 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3293 m = scmutil.match(ctx, torevert, matcher_opts)
3294 m = scmutil.match(ctx, torevert, matcher_opts)
3294 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3295 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3295 diffopts.nodates = True
3296 diffopts.nodates = True
3296 diffopts.git = True
3297 diffopts.git = True
3297 reversehunks = repo.ui.configbool('experimental',
3298 reversehunks = repo.ui.configbool('experimental',
3298 'revertalternateinteractivemode',
3299 'revertalternateinteractivemode',
3299 True)
3300 True)
3300 if reversehunks:
3301 if reversehunks:
3301 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3302 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3302 else:
3303 else:
3303 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3304 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3304 originalchunks = patch.parsepatch(diff)
3305 originalchunks = patch.parsepatch(diff)
3305 operation = 'discard' if node == parent else 'revert'
3306 operation = 'discard' if node == parent else 'revert'
3306
3307
3307 try:
3308 try:
3308
3309
3309 chunks, opts = recordfilter(repo.ui, originalchunks,
3310 chunks, opts = recordfilter(repo.ui, originalchunks,
3310 operation=operation)
3311 operation=operation)
3311 if reversehunks:
3312 if reversehunks:
3312 chunks = patch.reversehunks(chunks)
3313 chunks = patch.reversehunks(chunks)
3313
3314
3314 except patch.PatchError as err:
3315 except patch.PatchError as err:
3315 raise error.Abort(_('error parsing patch: %s') % err)
3316 raise error.Abort(_('error parsing patch: %s') % err)
3316
3317
3317 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3318 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3318 # Apply changes
3319 # Apply changes
3319 fp = stringio()
3320 fp = stringio()
3320 for c in chunks:
3321 for c in chunks:
3321 c.write(fp)
3322 c.write(fp)
3322 dopatch = fp.tell()
3323 dopatch = fp.tell()
3323 fp.seek(0)
3324 fp.seek(0)
3324 if dopatch:
3325 if dopatch:
3325 try:
3326 try:
3326 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3327 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3327 except patch.PatchError as err:
3328 except patch.PatchError as err:
3328 raise error.Abort(str(err))
3329 raise error.Abort(str(err))
3329 del fp
3330 del fp
3330 else:
3331 else:
3331 for f in actions['revert'][0]:
3332 for f in actions['revert'][0]:
3332 checkout(f)
3333 checkout(f)
3333 if normal:
3334 if normal:
3334 normal(f)
3335 normal(f)
3335
3336
3336 for f in actions['add'][0]:
3337 for f in actions['add'][0]:
3337 # Don't checkout modified files, they are already created by the diff
3338 # Don't checkout modified files, they are already created by the diff
3338 if f not in newlyaddedandmodifiedfiles:
3339 if f not in newlyaddedandmodifiedfiles:
3339 checkout(f)
3340 checkout(f)
3340 repo.dirstate.add(f)
3341 repo.dirstate.add(f)
3341
3342
3342 normal = repo.dirstate.normallookup
3343 normal = repo.dirstate.normallookup
3343 if node == parent and p2 == nullid:
3344 if node == parent and p2 == nullid:
3344 normal = repo.dirstate.normal
3345 normal = repo.dirstate.normal
3345 for f in actions['undelete'][0]:
3346 for f in actions['undelete'][0]:
3346 checkout(f)
3347 checkout(f)
3347 normal(f)
3348 normal(f)
3348
3349
3349 copied = copies.pathcopies(repo[parent], ctx)
3350 copied = copies.pathcopies(repo[parent], ctx)
3350
3351
3351 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3352 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3352 if f in copied:
3353 if f in copied:
3353 repo.dirstate.copy(copied[f], f)
3354 repo.dirstate.copy(copied[f], f)
3354
3355
3355 def command(table):
3356 def command(table):
3356 """Returns a function object to be used as a decorator for making commands.
3357 """Returns a function object to be used as a decorator for making commands.
3357
3358
3358 This function receives a command table as its argument. The table should
3359 This function receives a command table as its argument. The table should
3359 be a dict.
3360 be a dict.
3360
3361
3361 The returned function can be used as a decorator for adding commands
3362 The returned function can be used as a decorator for adding commands
3362 to that command table. This function accepts multiple arguments to define
3363 to that command table. This function accepts multiple arguments to define
3363 a command.
3364 a command.
3364
3365
3365 The first argument is the command name.
3366 The first argument is the command name.
3366
3367
3367 The options argument is an iterable of tuples defining command arguments.
3368 The options argument is an iterable of tuples defining command arguments.
3368 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3369 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3369
3370
3370 The synopsis argument defines a short, one line summary of how to use the
3371 The synopsis argument defines a short, one line summary of how to use the
3371 command. This shows up in the help output.
3372 command. This shows up in the help output.
3372
3373
3373 The norepo argument defines whether the command does not require a
3374 The norepo argument defines whether the command does not require a
3374 local repository. Most commands operate against a repository, thus the
3375 local repository. Most commands operate against a repository, thus the
3375 default is False.
3376 default is False.
3376
3377
3377 The optionalrepo argument defines whether the command optionally requires
3378 The optionalrepo argument defines whether the command optionally requires
3378 a local repository.
3379 a local repository.
3379
3380
3380 The inferrepo argument defines whether to try to find a repository from the
3381 The inferrepo argument defines whether to try to find a repository from the
3381 command line arguments. If True, arguments will be examined for potential
3382 command line arguments. If True, arguments will be examined for potential
3382 repository locations. See ``findrepo()``. If a repository is found, it
3383 repository locations. See ``findrepo()``. If a repository is found, it
3383 will be used.
3384 will be used.
3384 """
3385 """
3385 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3386 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3386 inferrepo=False):
3387 inferrepo=False):
3387 def decorator(func):
3388 def decorator(func):
3388 func.norepo = norepo
3389 func.norepo = norepo
3389 func.optionalrepo = optionalrepo
3390 func.optionalrepo = optionalrepo
3390 func.inferrepo = inferrepo
3391 func.inferrepo = inferrepo
3391 if synopsis:
3392 if synopsis:
3392 table[name] = func, list(options), synopsis
3393 table[name] = func, list(options), synopsis
3393 else:
3394 else:
3394 table[name] = func, list(options)
3395 table[name] = func, list(options)
3395 return func
3396 return func
3396 return decorator
3397 return decorator
3397
3398
3398 return cmd
3399 return cmd
3399
3400
3400 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3401 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3401 # commands.outgoing. "missing" is "missing" of the result of
3402 # commands.outgoing. "missing" is "missing" of the result of
3402 # "findcommonoutgoing()"
3403 # "findcommonoutgoing()"
3403 outgoinghooks = util.hooks()
3404 outgoinghooks = util.hooks()
3404
3405
3405 # a list of (ui, repo) functions called by commands.summary
3406 # a list of (ui, repo) functions called by commands.summary
3406 summaryhooks = util.hooks()
3407 summaryhooks = util.hooks()
3407
3408
3408 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3409 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3409 #
3410 #
3410 # functions should return tuple of booleans below, if 'changes' is None:
3411 # functions should return tuple of booleans below, if 'changes' is None:
3411 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3412 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3412 #
3413 #
3413 # otherwise, 'changes' is a tuple of tuples below:
3414 # otherwise, 'changes' is a tuple of tuples below:
3414 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3415 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3415 # - (desturl, destbranch, destpeer, outgoing)
3416 # - (desturl, destbranch, destpeer, outgoing)
3416 summaryremotehooks = util.hooks()
3417 summaryremotehooks = util.hooks()
3417
3418
3418 # A list of state files kept by multistep operations like graft.
3419 # A list of state files kept by multistep operations like graft.
3419 # Since graft cannot be aborted, it is considered 'clearable' by update.
3420 # Since graft cannot be aborted, it is considered 'clearable' by update.
3420 # note: bisect is intentionally excluded
3421 # note: bisect is intentionally excluded
3421 # (state file, clearable, allowcommit, error, hint)
3422 # (state file, clearable, allowcommit, error, hint)
3422 unfinishedstates = [
3423 unfinishedstates = [
3423 ('graftstate', True, False, _('graft in progress'),
3424 ('graftstate', True, False, _('graft in progress'),
3424 _("use 'hg graft --continue' or 'hg update' to abort")),
3425 _("use 'hg graft --continue' or 'hg update' to abort")),
3425 ('updatestate', True, False, _('last update was interrupted'),
3426 ('updatestate', True, False, _('last update was interrupted'),
3426 _("use 'hg update' to get a consistent checkout"))
3427 _("use 'hg update' to get a consistent checkout"))
3427 ]
3428 ]
3428
3429
3429 def checkunfinished(repo, commit=False):
3430 def checkunfinished(repo, commit=False):
3430 '''Look for an unfinished multistep operation, like graft, and abort
3431 '''Look for an unfinished multistep operation, like graft, and abort
3431 if found. It's probably good to check this right before
3432 if found. It's probably good to check this right before
3432 bailifchanged().
3433 bailifchanged().
3433 '''
3434 '''
3434 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3435 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3435 if commit and allowcommit:
3436 if commit and allowcommit:
3436 continue
3437 continue
3437 if repo.vfs.exists(f):
3438 if repo.vfs.exists(f):
3438 raise error.Abort(msg, hint=hint)
3439 raise error.Abort(msg, hint=hint)
3439
3440
3440 def clearunfinished(repo):
3441 def clearunfinished(repo):
3441 '''Check for unfinished operations (as above), and clear the ones
3442 '''Check for unfinished operations (as above), and clear the ones
3442 that are clearable.
3443 that are clearable.
3443 '''
3444 '''
3444 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3445 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3445 if not clearable and repo.vfs.exists(f):
3446 if not clearable and repo.vfs.exists(f):
3446 raise error.Abort(msg, hint=hint)
3447 raise error.Abort(msg, hint=hint)
3447 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3448 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3448 if clearable and repo.vfs.exists(f):
3449 if clearable and repo.vfs.exists(f):
3449 util.unlink(repo.join(f))
3450 util.unlink(repo.join(f))
3450
3451
3451 afterresolvedstates = [
3452 afterresolvedstates = [
3452 ('graftstate',
3453 ('graftstate',
3453 _('hg graft --continue')),
3454 _('hg graft --continue')),
3454 ]
3455 ]
3455
3456
3456 def howtocontinue(repo):
3457 def howtocontinue(repo):
3457 '''Check for an unfinished operation and return the command to finish
3458 '''Check for an unfinished operation and return the command to finish
3458 it.
3459 it.
3459
3460
3460 afterresolvedstates tupples define a .hg/{file} and the corresponding
3461 afterresolvedstates tupples define a .hg/{file} and the corresponding
3461 command needed to finish it.
3462 command needed to finish it.
3462
3463
3463 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3464 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3464 a boolean.
3465 a boolean.
3465 '''
3466 '''
3466 contmsg = _("continue: %s")
3467 contmsg = _("continue: %s")
3467 for f, msg in afterresolvedstates:
3468 for f, msg in afterresolvedstates:
3468 if repo.vfs.exists(f):
3469 if repo.vfs.exists(f):
3469 return contmsg % msg, True
3470 return contmsg % msg, True
3470 workingctx = repo[None]
3471 workingctx = repo[None]
3471 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3472 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3472 for s in workingctx.substate)
3473 for s in workingctx.substate)
3473 if dirty:
3474 if dirty:
3474 return contmsg % _("hg commit"), False
3475 return contmsg % _("hg commit"), False
3475 return None, None
3476 return None, None
3476
3477
3477 def checkafterresolved(repo):
3478 def checkafterresolved(repo):
3478 '''Inform the user about the next action after completing hg resolve
3479 '''Inform the user about the next action after completing hg resolve
3479
3480
3480 If there's a matching afterresolvedstates, howtocontinue will yield
3481 If there's a matching afterresolvedstates, howtocontinue will yield
3481 repo.ui.warn as the reporter.
3482 repo.ui.warn as the reporter.
3482
3483
3483 Otherwise, it will yield repo.ui.note.
3484 Otherwise, it will yield repo.ui.note.
3484 '''
3485 '''
3485 msg, warning = howtocontinue(repo)
3486 msg, warning = howtocontinue(repo)
3486 if msg is not None:
3487 if msg is not None:
3487 if warning:
3488 if warning:
3488 repo.ui.warn("%s\n" % msg)
3489 repo.ui.warn("%s\n" % msg)
3489 else:
3490 else:
3490 repo.ui.note("%s\n" % msg)
3491 repo.ui.note("%s\n" % msg)
3491
3492
3492 def wrongtooltocontinue(repo, task):
3493 def wrongtooltocontinue(repo, task):
3493 '''Raise an abort suggesting how to properly continue if there is an
3494 '''Raise an abort suggesting how to properly continue if there is an
3494 active task.
3495 active task.
3495
3496
3496 Uses howtocontinue() to find the active task.
3497 Uses howtocontinue() to find the active task.
3497
3498
3498 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3499 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3499 a hint.
3500 a hint.
3500 '''
3501 '''
3501 after = howtocontinue(repo)
3502 after = howtocontinue(repo)
3502 hint = None
3503 hint = None
3503 if after[1]:
3504 if after[1]:
3504 hint = after[0]
3505 hint = after[0]
3505 raise error.Abort(_('no %s in progress') % task, hint=hint)
3506 raise error.Abort(_('no %s in progress') % task, hint=hint)
3506
3507
3507 class dirstateguard(object):
3508 class dirstateguard(object):
3508 '''Restore dirstate at unexpected failure.
3509 '''Restore dirstate at unexpected failure.
3509
3510
3510 At the construction, this class does:
3511 At the construction, this class does:
3511
3512
3512 - write current ``repo.dirstate`` out, and
3513 - write current ``repo.dirstate`` out, and
3513 - save ``.hg/dirstate`` into the backup file
3514 - save ``.hg/dirstate`` into the backup file
3514
3515
3515 This restores ``.hg/dirstate`` from backup file, if ``release()``
3516 This restores ``.hg/dirstate`` from backup file, if ``release()``
3516 is invoked before ``close()``.
3517 is invoked before ``close()``.
3517
3518
3518 This just removes the backup file at ``close()`` before ``release()``.
3519 This just removes the backup file at ``close()`` before ``release()``.
3519 '''
3520 '''
3520
3521
3521 def __init__(self, repo, name):
3522 def __init__(self, repo, name):
3522 self._repo = repo
3523 self._repo = repo
3523 self._suffix = '.backup.%s.%d' % (name, id(self))
3524 self._suffix = '.backup.%s.%d' % (name, id(self))
3524 repo.dirstate.savebackup(repo.currenttransaction(), self._suffix)
3525 repo.dirstate.savebackup(repo.currenttransaction(), self._suffix)
3525 self._active = True
3526 self._active = True
3526 self._closed = False
3527 self._closed = False
3527
3528
3528 def __del__(self):
3529 def __del__(self):
3529 if self._active: # still active
3530 if self._active: # still active
3530 # this may occur, even if this class is used correctly:
3531 # this may occur, even if this class is used correctly:
3531 # for example, releasing other resources like transaction
3532 # for example, releasing other resources like transaction
3532 # may raise exception before ``dirstateguard.release`` in
3533 # may raise exception before ``dirstateguard.release`` in
3533 # ``release(tr, ....)``.
3534 # ``release(tr, ....)``.
3534 self._abort()
3535 self._abort()
3535
3536
3536 def close(self):
3537 def close(self):
3537 if not self._active: # already inactivated
3538 if not self._active: # already inactivated
3538 msg = (_("can't close already inactivated backup: dirstate%s")
3539 msg = (_("can't close already inactivated backup: dirstate%s")
3539 % self._suffix)
3540 % self._suffix)
3540 raise error.Abort(msg)
3541 raise error.Abort(msg)
3541
3542
3542 self._repo.dirstate.clearbackup(self._repo.currenttransaction(),
3543 self._repo.dirstate.clearbackup(self._repo.currenttransaction(),
3543 self._suffix)
3544 self._suffix)
3544 self._active = False
3545 self._active = False
3545 self._closed = True
3546 self._closed = True
3546
3547
3547 def _abort(self):
3548 def _abort(self):
3548 self._repo.dirstate.restorebackup(self._repo.currenttransaction(),
3549 self._repo.dirstate.restorebackup(self._repo.currenttransaction(),
3549 self._suffix)
3550 self._suffix)
3550 self._active = False
3551 self._active = False
3551
3552
3552 def release(self):
3553 def release(self):
3553 if not self._closed:
3554 if not self._closed:
3554 if not self._active: # already inactivated
3555 if not self._active: # already inactivated
3555 msg = (_("can't release already inactivated backup:"
3556 msg = (_("can't release already inactivated backup:"
3556 " dirstate%s")
3557 " dirstate%s")
3557 % self._suffix)
3558 % self._suffix)
3558 raise error.Abort(msg)
3559 raise error.Abort(msg)
3559 self._abort()
3560 self._abort()
@@ -1,7273 +1,7274 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import operator
12 import operator
13 import os
13 import os
14 import random
14 import random
15 import re
15 import re
16 import shlex
16 import shlex
17 import socket
17 import socket
18 import sys
18 import sys
19 import tempfile
19 import tempfile
20 import time
20 import time
21
21
22 from .i18n import _
22 from .i18n import _
23 from .node import (
23 from .node import (
24 bin,
24 bin,
25 hex,
25 hex,
26 nullhex,
26 nullhex,
27 nullid,
27 nullid,
28 nullrev,
28 nullrev,
29 short,
29 short,
30 )
30 )
31 from . import (
31 from . import (
32 archival,
32 archival,
33 bookmarks,
33 bookmarks,
34 bundle2,
34 bundle2,
35 changegroup,
35 changegroup,
36 cmdutil,
36 cmdutil,
37 commandserver,
37 commandserver,
38 context,
38 context,
39 copies,
39 copies,
40 dagparser,
40 dagparser,
41 dagutil,
41 dagutil,
42 destutil,
42 destutil,
43 discovery,
43 discovery,
44 encoding,
44 encoding,
45 error,
45 error,
46 exchange,
46 exchange,
47 extensions,
47 extensions,
48 fileset,
48 fileset,
49 formatter,
49 formatter,
50 graphmod,
50 graphmod,
51 hbisect,
51 hbisect,
52 help,
52 help,
53 hg,
53 hg,
54 hgweb,
54 hgweb,
55 localrepo,
55 localrepo,
56 lock as lockmod,
56 lock as lockmod,
57 merge as mergemod,
57 merge as mergemod,
58 minirst,
58 minirst,
59 obsolete,
59 obsolete,
60 patch,
60 patch,
61 phases,
61 phases,
62 policy,
62 policy,
63 pvec,
63 pvec,
64 repair,
64 repair,
65 revlog,
65 revlog,
66 revset,
66 revset,
67 scmutil,
67 scmutil,
68 setdiscovery,
68 setdiscovery,
69 simplemerge,
69 simplemerge,
70 sshserver,
70 sshserver,
71 streamclone,
71 streamclone,
72 templatekw,
72 templatekw,
73 templater,
73 templater,
74 treediscovery,
74 treediscovery,
75 ui as uimod,
75 ui as uimod,
76 util,
76 util,
77 )
77 )
78
78
79 release = lockmod.release
79 release = lockmod.release
80
80
81 table = {}
81 table = {}
82
82
83 command = cmdutil.command(table)
83 command = cmdutil.command(table)
84
84
85 # label constants
85 # label constants
86 # until 3.5, bookmarks.current was the advertised name, not
86 # until 3.5, bookmarks.current was the advertised name, not
87 # bookmarks.active, so we must use both to avoid breaking old
87 # bookmarks.active, so we must use both to avoid breaking old
88 # custom styles
88 # custom styles
89 activebookmarklabel = 'bookmarks.active bookmarks.current'
89 activebookmarklabel = 'bookmarks.active bookmarks.current'
90
90
91 # common command options
91 # common command options
92
92
93 globalopts = [
93 globalopts = [
94 ('R', 'repository', '',
94 ('R', 'repository', '',
95 _('repository root directory or name of overlay bundle file'),
95 _('repository root directory or name of overlay bundle file'),
96 _('REPO')),
96 _('REPO')),
97 ('', 'cwd', '',
97 ('', 'cwd', '',
98 _('change working directory'), _('DIR')),
98 _('change working directory'), _('DIR')),
99 ('y', 'noninteractive', None,
99 ('y', 'noninteractive', None,
100 _('do not prompt, automatically pick the first choice for all prompts')),
100 _('do not prompt, automatically pick the first choice for all prompts')),
101 ('q', 'quiet', None, _('suppress output')),
101 ('q', 'quiet', None, _('suppress output')),
102 ('v', 'verbose', None, _('enable additional output')),
102 ('v', 'verbose', None, _('enable additional output')),
103 ('', 'config', [],
103 ('', 'config', [],
104 _('set/override config option (use \'section.name=value\')'),
104 _('set/override config option (use \'section.name=value\')'),
105 _('CONFIG')),
105 _('CONFIG')),
106 ('', 'debug', None, _('enable debugging output')),
106 ('', 'debug', None, _('enable debugging output')),
107 ('', 'debugger', None, _('start debugger')),
107 ('', 'debugger', None, _('start debugger')),
108 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
108 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
109 _('ENCODE')),
109 _('ENCODE')),
110 ('', 'encodingmode', encoding.encodingmode,
110 ('', 'encodingmode', encoding.encodingmode,
111 _('set the charset encoding mode'), _('MODE')),
111 _('set the charset encoding mode'), _('MODE')),
112 ('', 'traceback', None, _('always print a traceback on exception')),
112 ('', 'traceback', None, _('always print a traceback on exception')),
113 ('', 'time', None, _('time how long the command takes')),
113 ('', 'time', None, _('time how long the command takes')),
114 ('', 'profile', None, _('print command execution profile')),
114 ('', 'profile', None, _('print command execution profile')),
115 ('', 'version', None, _('output version information and exit')),
115 ('', 'version', None, _('output version information and exit')),
116 ('h', 'help', None, _('display help and exit')),
116 ('h', 'help', None, _('display help and exit')),
117 ('', 'hidden', False, _('consider hidden changesets')),
117 ('', 'hidden', False, _('consider hidden changesets')),
118 ]
118 ]
119
119
120 dryrunopts = [('n', 'dry-run', None,
120 dryrunopts = [('n', 'dry-run', None,
121 _('do not perform actions, just print output'))]
121 _('do not perform actions, just print output'))]
122
122
123 remoteopts = [
123 remoteopts = [
124 ('e', 'ssh', '',
124 ('e', 'ssh', '',
125 _('specify ssh command to use'), _('CMD')),
125 _('specify ssh command to use'), _('CMD')),
126 ('', 'remotecmd', '',
126 ('', 'remotecmd', '',
127 _('specify hg command to run on the remote side'), _('CMD')),
127 _('specify hg command to run on the remote side'), _('CMD')),
128 ('', 'insecure', None,
128 ('', 'insecure', None,
129 _('do not verify server certificate (ignoring web.cacerts config)')),
129 _('do not verify server certificate (ignoring web.cacerts config)')),
130 ]
130 ]
131
131
132 walkopts = [
132 walkopts = [
133 ('I', 'include', [],
133 ('I', 'include', [],
134 _('include names matching the given patterns'), _('PATTERN')),
134 _('include names matching the given patterns'), _('PATTERN')),
135 ('X', 'exclude', [],
135 ('X', 'exclude', [],
136 _('exclude names matching the given patterns'), _('PATTERN')),
136 _('exclude names matching the given patterns'), _('PATTERN')),
137 ]
137 ]
138
138
139 commitopts = [
139 commitopts = [
140 ('m', 'message', '',
140 ('m', 'message', '',
141 _('use text as commit message'), _('TEXT')),
141 _('use text as commit message'), _('TEXT')),
142 ('l', 'logfile', '',
142 ('l', 'logfile', '',
143 _('read commit message from file'), _('FILE')),
143 _('read commit message from file'), _('FILE')),
144 ]
144 ]
145
145
146 commitopts2 = [
146 commitopts2 = [
147 ('d', 'date', '',
147 ('d', 'date', '',
148 _('record the specified date as commit date'), _('DATE')),
148 _('record the specified date as commit date'), _('DATE')),
149 ('u', 'user', '',
149 ('u', 'user', '',
150 _('record the specified user as committer'), _('USER')),
150 _('record the specified user as committer'), _('USER')),
151 ]
151 ]
152
152
153 # hidden for now
153 # hidden for now
154 formatteropts = [
154 formatteropts = [
155 ('T', 'template', '',
155 ('T', 'template', '',
156 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
156 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
157 ]
157 ]
158
158
159 templateopts = [
159 templateopts = [
160 ('', 'style', '',
160 ('', 'style', '',
161 _('display using template map file (DEPRECATED)'), _('STYLE')),
161 _('display using template map file (DEPRECATED)'), _('STYLE')),
162 ('T', 'template', '',
162 ('T', 'template', '',
163 _('display with template'), _('TEMPLATE')),
163 _('display with template'), _('TEMPLATE')),
164 ]
164 ]
165
165
166 logopts = [
166 logopts = [
167 ('p', 'patch', None, _('show patch')),
167 ('p', 'patch', None, _('show patch')),
168 ('g', 'git', None, _('use git extended diff format')),
168 ('g', 'git', None, _('use git extended diff format')),
169 ('l', 'limit', '',
169 ('l', 'limit', '',
170 _('limit number of changes displayed'), _('NUM')),
170 _('limit number of changes displayed'), _('NUM')),
171 ('M', 'no-merges', None, _('do not show merges')),
171 ('M', 'no-merges', None, _('do not show merges')),
172 ('', 'stat', None, _('output diffstat-style summary of changes')),
172 ('', 'stat', None, _('output diffstat-style summary of changes')),
173 ('G', 'graph', None, _("show the revision DAG")),
173 ('G', 'graph', None, _("show the revision DAG")),
174 ] + templateopts
174 ] + templateopts
175
175
176 diffopts = [
176 diffopts = [
177 ('a', 'text', None, _('treat all files as text')),
177 ('a', 'text', None, _('treat all files as text')),
178 ('g', 'git', None, _('use git extended diff format')),
178 ('g', 'git', None, _('use git extended diff format')),
179 ('', 'nodates', None, _('omit dates from diff headers'))
179 ('', 'nodates', None, _('omit dates from diff headers'))
180 ]
180 ]
181
181
182 diffwsopts = [
182 diffwsopts = [
183 ('w', 'ignore-all-space', None,
183 ('w', 'ignore-all-space', None,
184 _('ignore white space when comparing lines')),
184 _('ignore white space when comparing lines')),
185 ('b', 'ignore-space-change', None,
185 ('b', 'ignore-space-change', None,
186 _('ignore changes in the amount of white space')),
186 _('ignore changes in the amount of white space')),
187 ('B', 'ignore-blank-lines', None,
187 ('B', 'ignore-blank-lines', None,
188 _('ignore changes whose lines are all blank')),
188 _('ignore changes whose lines are all blank')),
189 ]
189 ]
190
190
191 diffopts2 = [
191 diffopts2 = [
192 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
192 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
193 ('p', 'show-function', None, _('show which function each change is in')),
193 ('p', 'show-function', None, _('show which function each change is in')),
194 ('', 'reverse', None, _('produce a diff that undoes the changes')),
194 ('', 'reverse', None, _('produce a diff that undoes the changes')),
195 ] + diffwsopts + [
195 ] + diffwsopts + [
196 ('U', 'unified', '',
196 ('U', 'unified', '',
197 _('number of lines of context to show'), _('NUM')),
197 _('number of lines of context to show'), _('NUM')),
198 ('', 'stat', None, _('output diffstat-style summary of changes')),
198 ('', 'stat', None, _('output diffstat-style summary of changes')),
199 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
199 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
200 ]
200 ]
201
201
202 mergetoolopts = [
202 mergetoolopts = [
203 ('t', 'tool', '', _('specify merge tool')),
203 ('t', 'tool', '', _('specify merge tool')),
204 ]
204 ]
205
205
206 similarityopts = [
206 similarityopts = [
207 ('s', 'similarity', '',
207 ('s', 'similarity', '',
208 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
208 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
209 ]
209 ]
210
210
211 subrepoopts = [
211 subrepoopts = [
212 ('S', 'subrepos', None,
212 ('S', 'subrepos', None,
213 _('recurse into subrepositories'))
213 _('recurse into subrepositories'))
214 ]
214 ]
215
215
216 debugrevlogopts = [
216 debugrevlogopts = [
217 ('c', 'changelog', False, _('open changelog')),
217 ('c', 'changelog', False, _('open changelog')),
218 ('m', 'manifest', False, _('open manifest')),
218 ('m', 'manifest', False, _('open manifest')),
219 ('', 'dir', False, _('open directory manifest')),
219 ('', 'dir', False, _('open directory manifest')),
220 ]
220 ]
221
221
222 # Commands start here, listed alphabetically
222 # Commands start here, listed alphabetically
223
223
224 @command('^add',
224 @command('^add',
225 walkopts + subrepoopts + dryrunopts,
225 walkopts + subrepoopts + dryrunopts,
226 _('[OPTION]... [FILE]...'),
226 _('[OPTION]... [FILE]...'),
227 inferrepo=True)
227 inferrepo=True)
228 def add(ui, repo, *pats, **opts):
228 def add(ui, repo, *pats, **opts):
229 """add the specified files on the next commit
229 """add the specified files on the next commit
230
230
231 Schedule files to be version controlled and added to the
231 Schedule files to be version controlled and added to the
232 repository.
232 repository.
233
233
234 The files will be added to the repository at the next commit. To
234 The files will be added to the repository at the next commit. To
235 undo an add before that, see :hg:`forget`.
235 undo an add before that, see :hg:`forget`.
236
236
237 If no names are given, add all files to the repository (except
237 If no names are given, add all files to the repository (except
238 files matching ``.hgignore``).
238 files matching ``.hgignore``).
239
239
240 .. container:: verbose
240 .. container:: verbose
241
241
242 Examples:
242 Examples:
243
243
244 - New (unknown) files are added
244 - New (unknown) files are added
245 automatically by :hg:`add`::
245 automatically by :hg:`add`::
246
246
247 $ ls
247 $ ls
248 foo.c
248 foo.c
249 $ hg status
249 $ hg status
250 ? foo.c
250 ? foo.c
251 $ hg add
251 $ hg add
252 adding foo.c
252 adding foo.c
253 $ hg status
253 $ hg status
254 A foo.c
254 A foo.c
255
255
256 - Specific files to be added can be specified::
256 - Specific files to be added can be specified::
257
257
258 $ ls
258 $ ls
259 bar.c foo.c
259 bar.c foo.c
260 $ hg status
260 $ hg status
261 ? bar.c
261 ? bar.c
262 ? foo.c
262 ? foo.c
263 $ hg add bar.c
263 $ hg add bar.c
264 $ hg status
264 $ hg status
265 A bar.c
265 A bar.c
266 ? foo.c
266 ? foo.c
267
267
268 Returns 0 if all files are successfully added.
268 Returns 0 if all files are successfully added.
269 """
269 """
270
270
271 m = scmutil.match(repo[None], pats, opts)
271 m = scmutil.match(repo[None], pats, opts)
272 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
272 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
273 return rejected and 1 or 0
273 return rejected and 1 or 0
274
274
275 @command('addremove',
275 @command('addremove',
276 similarityopts + subrepoopts + walkopts + dryrunopts,
276 similarityopts + subrepoopts + walkopts + dryrunopts,
277 _('[OPTION]... [FILE]...'),
277 _('[OPTION]... [FILE]...'),
278 inferrepo=True)
278 inferrepo=True)
279 def addremove(ui, repo, *pats, **opts):
279 def addremove(ui, repo, *pats, **opts):
280 """add all new files, delete all missing files
280 """add all new files, delete all missing files
281
281
282 Add all new files and remove all missing files from the
282 Add all new files and remove all missing files from the
283 repository.
283 repository.
284
284
285 Unless names are given, new files are ignored if they match any of
285 Unless names are given, new files are ignored if they match any of
286 the patterns in ``.hgignore``. As with add, these changes take
286 the patterns in ``.hgignore``. As with add, these changes take
287 effect at the next commit.
287 effect at the next commit.
288
288
289 Use the -s/--similarity option to detect renamed files. This
289 Use the -s/--similarity option to detect renamed files. This
290 option takes a percentage between 0 (disabled) and 100 (files must
290 option takes a percentage between 0 (disabled) and 100 (files must
291 be identical) as its parameter. With a parameter greater than 0,
291 be identical) as its parameter. With a parameter greater than 0,
292 this compares every removed file with every added file and records
292 this compares every removed file with every added file and records
293 those similar enough as renames. Detecting renamed files this way
293 those similar enough as renames. Detecting renamed files this way
294 can be expensive. After using this option, :hg:`status -C` can be
294 can be expensive. After using this option, :hg:`status -C` can be
295 used to check which files were identified as moved or renamed. If
295 used to check which files were identified as moved or renamed. If
296 not specified, -s/--similarity defaults to 100 and only renames of
296 not specified, -s/--similarity defaults to 100 and only renames of
297 identical files are detected.
297 identical files are detected.
298
298
299 .. container:: verbose
299 .. container:: verbose
300
300
301 Examples:
301 Examples:
302
302
303 - A number of files (bar.c and foo.c) are new,
303 - A number of files (bar.c and foo.c) are new,
304 while foobar.c has been removed (without using :hg:`remove`)
304 while foobar.c has been removed (without using :hg:`remove`)
305 from the repository::
305 from the repository::
306
306
307 $ ls
307 $ ls
308 bar.c foo.c
308 bar.c foo.c
309 $ hg status
309 $ hg status
310 ! foobar.c
310 ! foobar.c
311 ? bar.c
311 ? bar.c
312 ? foo.c
312 ? foo.c
313 $ hg addremove
313 $ hg addremove
314 adding bar.c
314 adding bar.c
315 adding foo.c
315 adding foo.c
316 removing foobar.c
316 removing foobar.c
317 $ hg status
317 $ hg status
318 A bar.c
318 A bar.c
319 A foo.c
319 A foo.c
320 R foobar.c
320 R foobar.c
321
321
322 - A file foobar.c was moved to foo.c without using :hg:`rename`.
322 - A file foobar.c was moved to foo.c without using :hg:`rename`.
323 Afterwards, it was edited slightly::
323 Afterwards, it was edited slightly::
324
324
325 $ ls
325 $ ls
326 foo.c
326 foo.c
327 $ hg status
327 $ hg status
328 ! foobar.c
328 ! foobar.c
329 ? foo.c
329 ? foo.c
330 $ hg addremove --similarity 90
330 $ hg addremove --similarity 90
331 removing foobar.c
331 removing foobar.c
332 adding foo.c
332 adding foo.c
333 recording removal of foobar.c as rename to foo.c (94% similar)
333 recording removal of foobar.c as rename to foo.c (94% similar)
334 $ hg status -C
334 $ hg status -C
335 A foo.c
335 A foo.c
336 foobar.c
336 foobar.c
337 R foobar.c
337 R foobar.c
338
338
339 Returns 0 if all files are successfully added.
339 Returns 0 if all files are successfully added.
340 """
340 """
341 try:
341 try:
342 sim = float(opts.get('similarity') or 100)
342 sim = float(opts.get('similarity') or 100)
343 except ValueError:
343 except ValueError:
344 raise error.Abort(_('similarity must be a number'))
344 raise error.Abort(_('similarity must be a number'))
345 if sim < 0 or sim > 100:
345 if sim < 0 or sim > 100:
346 raise error.Abort(_('similarity must be between 0 and 100'))
346 raise error.Abort(_('similarity must be between 0 and 100'))
347 matcher = scmutil.match(repo[None], pats, opts)
347 matcher = scmutil.match(repo[None], pats, opts)
348 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
348 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
349
349
350 @command('^annotate|blame',
350 @command('^annotate|blame',
351 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
351 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
352 ('', 'follow', None,
352 ('', 'follow', None,
353 _('follow copies/renames and list the filename (DEPRECATED)')),
353 _('follow copies/renames and list the filename (DEPRECATED)')),
354 ('', 'no-follow', None, _("don't follow copies and renames")),
354 ('', 'no-follow', None, _("don't follow copies and renames")),
355 ('a', 'text', None, _('treat all files as text')),
355 ('a', 'text', None, _('treat all files as text')),
356 ('u', 'user', None, _('list the author (long with -v)')),
356 ('u', 'user', None, _('list the author (long with -v)')),
357 ('f', 'file', None, _('list the filename')),
357 ('f', 'file', None, _('list the filename')),
358 ('d', 'date', None, _('list the date (short with -q)')),
358 ('d', 'date', None, _('list the date (short with -q)')),
359 ('n', 'number', None, _('list the revision number (default)')),
359 ('n', 'number', None, _('list the revision number (default)')),
360 ('c', 'changeset', None, _('list the changeset')),
360 ('c', 'changeset', None, _('list the changeset')),
361 ('l', 'line-number', None, _('show line number at the first appearance'))
361 ('l', 'line-number', None, _('show line number at the first appearance'))
362 ] + diffwsopts + walkopts + formatteropts,
362 ] + diffwsopts + walkopts + formatteropts,
363 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
363 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
364 inferrepo=True)
364 inferrepo=True)
365 def annotate(ui, repo, *pats, **opts):
365 def annotate(ui, repo, *pats, **opts):
366 """show changeset information by line for each file
366 """show changeset information by line for each file
367
367
368 List changes in files, showing the revision id responsible for
368 List changes in files, showing the revision id responsible for
369 each line.
369 each line.
370
370
371 This command is useful for discovering when a change was made and
371 This command is useful for discovering when a change was made and
372 by whom.
372 by whom.
373
373
374 If you include --file, --user, or --date, the revision number is
374 If you include --file, --user, or --date, the revision number is
375 suppressed unless you also include --number.
375 suppressed unless you also include --number.
376
376
377 Without the -a/--text option, annotate will avoid processing files
377 Without the -a/--text option, annotate will avoid processing files
378 it detects as binary. With -a, annotate will annotate the file
378 it detects as binary. With -a, annotate will annotate the file
379 anyway, although the results will probably be neither useful
379 anyway, although the results will probably be neither useful
380 nor desirable.
380 nor desirable.
381
381
382 Returns 0 on success.
382 Returns 0 on success.
383 """
383 """
384 if not pats:
384 if not pats:
385 raise error.Abort(_('at least one filename or pattern is required'))
385 raise error.Abort(_('at least one filename or pattern is required'))
386
386
387 if opts.get('follow'):
387 if opts.get('follow'):
388 # --follow is deprecated and now just an alias for -f/--file
388 # --follow is deprecated and now just an alias for -f/--file
389 # to mimic the behavior of Mercurial before version 1.5
389 # to mimic the behavior of Mercurial before version 1.5
390 opts['file'] = True
390 opts['file'] = True
391
391
392 ctx = scmutil.revsingle(repo, opts.get('rev'))
392 ctx = scmutil.revsingle(repo, opts.get('rev'))
393
393
394 fm = ui.formatter('annotate', opts)
394 fm = ui.formatter('annotate', opts)
395 if ui.quiet:
395 if ui.quiet:
396 datefunc = util.shortdate
396 datefunc = util.shortdate
397 else:
397 else:
398 datefunc = util.datestr
398 datefunc = util.datestr
399 if ctx.rev() is None:
399 if ctx.rev() is None:
400 def hexfn(node):
400 def hexfn(node):
401 if node is None:
401 if node is None:
402 return None
402 return None
403 else:
403 else:
404 return fm.hexfunc(node)
404 return fm.hexfunc(node)
405 if opts.get('changeset'):
405 if opts.get('changeset'):
406 # omit "+" suffix which is appended to node hex
406 # omit "+" suffix which is appended to node hex
407 def formatrev(rev):
407 def formatrev(rev):
408 if rev is None:
408 if rev is None:
409 return '%d' % ctx.p1().rev()
409 return '%d' % ctx.p1().rev()
410 else:
410 else:
411 return '%d' % rev
411 return '%d' % rev
412 else:
412 else:
413 def formatrev(rev):
413 def formatrev(rev):
414 if rev is None:
414 if rev is None:
415 return '%d+' % ctx.p1().rev()
415 return '%d+' % ctx.p1().rev()
416 else:
416 else:
417 return '%d ' % rev
417 return '%d ' % rev
418 def formathex(hex):
418 def formathex(hex):
419 if hex is None:
419 if hex is None:
420 return '%s+' % fm.hexfunc(ctx.p1().node())
420 return '%s+' % fm.hexfunc(ctx.p1().node())
421 else:
421 else:
422 return '%s ' % hex
422 return '%s ' % hex
423 else:
423 else:
424 hexfn = fm.hexfunc
424 hexfn = fm.hexfunc
425 formatrev = formathex = str
425 formatrev = formathex = str
426
426
427 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
427 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
428 ('number', ' ', lambda x: x[0].rev(), formatrev),
428 ('number', ' ', lambda x: x[0].rev(), formatrev),
429 ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
429 ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
430 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
430 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
431 ('file', ' ', lambda x: x[0].path(), str),
431 ('file', ' ', lambda x: x[0].path(), str),
432 ('line_number', ':', lambda x: x[1], str),
432 ('line_number', ':', lambda x: x[1], str),
433 ]
433 ]
434 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
434 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
435
435
436 if (not opts.get('user') and not opts.get('changeset')
436 if (not opts.get('user') and not opts.get('changeset')
437 and not opts.get('date') and not opts.get('file')):
437 and not opts.get('date') and not opts.get('file')):
438 opts['number'] = True
438 opts['number'] = True
439
439
440 linenumber = opts.get('line_number') is not None
440 linenumber = opts.get('line_number') is not None
441 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
441 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
442 raise error.Abort(_('at least one of -n/-c is required for -l'))
442 raise error.Abort(_('at least one of -n/-c is required for -l'))
443
443
444 if fm:
444 if fm:
445 def makefunc(get, fmt):
445 def makefunc(get, fmt):
446 return get
446 return get
447 else:
447 else:
448 def makefunc(get, fmt):
448 def makefunc(get, fmt):
449 return lambda x: fmt(get(x))
449 return lambda x: fmt(get(x))
450 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
450 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
451 if opts.get(op)]
451 if opts.get(op)]
452 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
452 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
453 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
453 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
454 if opts.get(op))
454 if opts.get(op))
455
455
456 def bad(x, y):
456 def bad(x, y):
457 raise error.Abort("%s: %s" % (x, y))
457 raise error.Abort("%s: %s" % (x, y))
458
458
459 m = scmutil.match(ctx, pats, opts, badfn=bad)
459 m = scmutil.match(ctx, pats, opts, badfn=bad)
460
460
461 follow = not opts.get('no_follow')
461 follow = not opts.get('no_follow')
462 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
462 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
463 whitespace=True)
463 whitespace=True)
464 for abs in ctx.walk(m):
464 for abs in ctx.walk(m):
465 fctx = ctx[abs]
465 fctx = ctx[abs]
466 if not opts.get('text') and util.binary(fctx.data()):
466 if not opts.get('text') and util.binary(fctx.data()):
467 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
467 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
468 continue
468 continue
469
469
470 lines = fctx.annotate(follow=follow, linenumber=linenumber,
470 lines = fctx.annotate(follow=follow, linenumber=linenumber,
471 diffopts=diffopts)
471 diffopts=diffopts)
472 formats = []
472 formats = []
473 pieces = []
473 pieces = []
474
474
475 for f, sep in funcmap:
475 for f, sep in funcmap:
476 l = [f(n) for n, dummy in lines]
476 l = [f(n) for n, dummy in lines]
477 if l:
477 if l:
478 if fm:
478 if fm:
479 formats.append(['%s' for x in l])
479 formats.append(['%s' for x in l])
480 else:
480 else:
481 sizes = [encoding.colwidth(x) for x in l]
481 sizes = [encoding.colwidth(x) for x in l]
482 ml = max(sizes)
482 ml = max(sizes)
483 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
483 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
484 pieces.append(l)
484 pieces.append(l)
485
485
486 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
486 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
487 fm.startitem()
487 fm.startitem()
488 fm.write(fields, "".join(f), *p)
488 fm.write(fields, "".join(f), *p)
489 fm.write('line', ": %s", l[1])
489 fm.write('line', ": %s", l[1])
490
490
491 if lines and not lines[-1][1].endswith('\n'):
491 if lines and not lines[-1][1].endswith('\n'):
492 fm.plain('\n')
492 fm.plain('\n')
493
493
494 fm.end()
494 fm.end()
495
495
496 @command('archive',
496 @command('archive',
497 [('', 'no-decode', None, _('do not pass files through decoders')),
497 [('', 'no-decode', None, _('do not pass files through decoders')),
498 ('p', 'prefix', '', _('directory prefix for files in archive'),
498 ('p', 'prefix', '', _('directory prefix for files in archive'),
499 _('PREFIX')),
499 _('PREFIX')),
500 ('r', 'rev', '', _('revision to distribute'), _('REV')),
500 ('r', 'rev', '', _('revision to distribute'), _('REV')),
501 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
501 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
502 ] + subrepoopts + walkopts,
502 ] + subrepoopts + walkopts,
503 _('[OPTION]... DEST'))
503 _('[OPTION]... DEST'))
504 def archive(ui, repo, dest, **opts):
504 def archive(ui, repo, dest, **opts):
505 '''create an unversioned archive of a repository revision
505 '''create an unversioned archive of a repository revision
506
506
507 By default, the revision used is the parent of the working
507 By default, the revision used is the parent of the working
508 directory; use -r/--rev to specify a different revision.
508 directory; use -r/--rev to specify a different revision.
509
509
510 The archive type is automatically detected based on file
510 The archive type is automatically detected based on file
511 extension (to override, use -t/--type).
511 extension (to override, use -t/--type).
512
512
513 .. container:: verbose
513 .. container:: verbose
514
514
515 Examples:
515 Examples:
516
516
517 - create a zip file containing the 1.0 release::
517 - create a zip file containing the 1.0 release::
518
518
519 hg archive -r 1.0 project-1.0.zip
519 hg archive -r 1.0 project-1.0.zip
520
520
521 - create a tarball excluding .hg files::
521 - create a tarball excluding .hg files::
522
522
523 hg archive project.tar.gz -X ".hg*"
523 hg archive project.tar.gz -X ".hg*"
524
524
525 Valid types are:
525 Valid types are:
526
526
527 :``files``: a directory full of files (default)
527 :``files``: a directory full of files (default)
528 :``tar``: tar archive, uncompressed
528 :``tar``: tar archive, uncompressed
529 :``tbz2``: tar archive, compressed using bzip2
529 :``tbz2``: tar archive, compressed using bzip2
530 :``tgz``: tar archive, compressed using gzip
530 :``tgz``: tar archive, compressed using gzip
531 :``uzip``: zip archive, uncompressed
531 :``uzip``: zip archive, uncompressed
532 :``zip``: zip archive, compressed using deflate
532 :``zip``: zip archive, compressed using deflate
533
533
534 The exact name of the destination archive or directory is given
534 The exact name of the destination archive or directory is given
535 using a format string; see :hg:`help export` for details.
535 using a format string; see :hg:`help export` for details.
536
536
537 Each member added to an archive file has a directory prefix
537 Each member added to an archive file has a directory prefix
538 prepended. Use -p/--prefix to specify a format string for the
538 prepended. Use -p/--prefix to specify a format string for the
539 prefix. The default is the basename of the archive, with suffixes
539 prefix. The default is the basename of the archive, with suffixes
540 removed.
540 removed.
541
541
542 Returns 0 on success.
542 Returns 0 on success.
543 '''
543 '''
544
544
545 ctx = scmutil.revsingle(repo, opts.get('rev'))
545 ctx = scmutil.revsingle(repo, opts.get('rev'))
546 if not ctx:
546 if not ctx:
547 raise error.Abort(_('no working directory: please specify a revision'))
547 raise error.Abort(_('no working directory: please specify a revision'))
548 node = ctx.node()
548 node = ctx.node()
549 dest = cmdutil.makefilename(repo, dest, node)
549 dest = cmdutil.makefilename(repo, dest, node)
550 if os.path.realpath(dest) == repo.root:
550 if os.path.realpath(dest) == repo.root:
551 raise error.Abort(_('repository root cannot be destination'))
551 raise error.Abort(_('repository root cannot be destination'))
552
552
553 kind = opts.get('type') or archival.guesskind(dest) or 'files'
553 kind = opts.get('type') or archival.guesskind(dest) or 'files'
554 prefix = opts.get('prefix')
554 prefix = opts.get('prefix')
555
555
556 if dest == '-':
556 if dest == '-':
557 if kind == 'files':
557 if kind == 'files':
558 raise error.Abort(_('cannot archive plain files to stdout'))
558 raise error.Abort(_('cannot archive plain files to stdout'))
559 dest = cmdutil.makefileobj(repo, dest)
559 dest = cmdutil.makefileobj(repo, dest)
560 if not prefix:
560 if not prefix:
561 prefix = os.path.basename(repo.root) + '-%h'
561 prefix = os.path.basename(repo.root) + '-%h'
562
562
563 prefix = cmdutil.makefilename(repo, prefix, node)
563 prefix = cmdutil.makefilename(repo, prefix, node)
564 matchfn = scmutil.match(ctx, [], opts)
564 matchfn = scmutil.match(ctx, [], opts)
565 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
565 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
566 matchfn, prefix, subrepos=opts.get('subrepos'))
566 matchfn, prefix, subrepos=opts.get('subrepos'))
567
567
568 @command('backout',
568 @command('backout',
569 [('', 'merge', None, _('merge with old dirstate parent after backout')),
569 [('', 'merge', None, _('merge with old dirstate parent after backout')),
570 ('', 'commit', None,
570 ('', 'commit', None,
571 _('commit if no conflicts were encountered (DEPRECATED)')),
571 _('commit if no conflicts were encountered (DEPRECATED)')),
572 ('', 'no-commit', None, _('do not commit')),
572 ('', 'no-commit', None, _('do not commit')),
573 ('', 'parent', '',
573 ('', 'parent', '',
574 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
574 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
575 ('r', 'rev', '', _('revision to backout'), _('REV')),
575 ('r', 'rev', '', _('revision to backout'), _('REV')),
576 ('e', 'edit', False, _('invoke editor on commit messages')),
576 ('e', 'edit', False, _('invoke editor on commit messages')),
577 ] + mergetoolopts + walkopts + commitopts + commitopts2,
577 ] + mergetoolopts + walkopts + commitopts + commitopts2,
578 _('[OPTION]... [-r] REV'))
578 _('[OPTION]... [-r] REV'))
579 def backout(ui, repo, node=None, rev=None, **opts):
579 def backout(ui, repo, node=None, rev=None, **opts):
580 '''reverse effect of earlier changeset
580 '''reverse effect of earlier changeset
581
581
582 Prepare a new changeset with the effect of REV undone in the
582 Prepare a new changeset with the effect of REV undone in the
583 current working directory. If no conflicts were encountered,
583 current working directory. If no conflicts were encountered,
584 it will be committed immediately.
584 it will be committed immediately.
585
585
586 If REV is the parent of the working directory, then this new changeset
586 If REV is the parent of the working directory, then this new changeset
587 is committed automatically (unless --no-commit is specified).
587 is committed automatically (unless --no-commit is specified).
588
588
589 .. note::
589 .. note::
590
590
591 :hg:`backout` cannot be used to fix either an unwanted or
591 :hg:`backout` cannot be used to fix either an unwanted or
592 incorrect merge.
592 incorrect merge.
593
593
594 .. container:: verbose
594 .. container:: verbose
595
595
596 Examples:
596 Examples:
597
597
598 - Reverse the effect of the parent of the working directory.
598 - Reverse the effect of the parent of the working directory.
599 This backout will be committed immediately::
599 This backout will be committed immediately::
600
600
601 hg backout -r .
601 hg backout -r .
602
602
603 - Reverse the effect of previous bad revision 23::
603 - Reverse the effect of previous bad revision 23::
604
604
605 hg backout -r 23
605 hg backout -r 23
606
606
607 - Reverse the effect of previous bad revision 23 and
607 - Reverse the effect of previous bad revision 23 and
608 leave changes uncommitted::
608 leave changes uncommitted::
609
609
610 hg backout -r 23 --no-commit
610 hg backout -r 23 --no-commit
611 hg commit -m "Backout revision 23"
611 hg commit -m "Backout revision 23"
612
612
613 By default, the pending changeset will have one parent,
613 By default, the pending changeset will have one parent,
614 maintaining a linear history. With --merge, the pending
614 maintaining a linear history. With --merge, the pending
615 changeset will instead have two parents: the old parent of the
615 changeset will instead have two parents: the old parent of the
616 working directory and a new child of REV that simply undoes REV.
616 working directory and a new child of REV that simply undoes REV.
617
617
618 Before version 1.7, the behavior without --merge was equivalent
618 Before version 1.7, the behavior without --merge was equivalent
619 to specifying --merge followed by :hg:`update --clean .` to
619 to specifying --merge followed by :hg:`update --clean .` to
620 cancel the merge and leave the child of REV as a head to be
620 cancel the merge and leave the child of REV as a head to be
621 merged separately.
621 merged separately.
622
622
623 See :hg:`help dates` for a list of formats valid for -d/--date.
623 See :hg:`help dates` for a list of formats valid for -d/--date.
624
624
625 See :hg:`help revert` for a way to restore files to the state
625 See :hg:`help revert` for a way to restore files to the state
626 of another revision.
626 of another revision.
627
627
628 Returns 0 on success, 1 if nothing to backout or there are unresolved
628 Returns 0 on success, 1 if nothing to backout or there are unresolved
629 files.
629 files.
630 '''
630 '''
631 wlock = lock = None
631 wlock = lock = None
632 try:
632 try:
633 wlock = repo.wlock()
633 wlock = repo.wlock()
634 lock = repo.lock()
634 lock = repo.lock()
635 return _dobackout(ui, repo, node, rev, **opts)
635 return _dobackout(ui, repo, node, rev, **opts)
636 finally:
636 finally:
637 release(lock, wlock)
637 release(lock, wlock)
638
638
639 def _dobackout(ui, repo, node=None, rev=None, **opts):
639 def _dobackout(ui, repo, node=None, rev=None, **opts):
640 if opts.get('commit') and opts.get('no_commit'):
640 if opts.get('commit') and opts.get('no_commit'):
641 raise error.Abort(_("cannot use --commit with --no-commit"))
641 raise error.Abort(_("cannot use --commit with --no-commit"))
642 if opts.get('merge') and opts.get('no_commit'):
642 if opts.get('merge') and opts.get('no_commit'):
643 raise error.Abort(_("cannot use --merge with --no-commit"))
643 raise error.Abort(_("cannot use --merge with --no-commit"))
644
644
645 if rev and node:
645 if rev and node:
646 raise error.Abort(_("please specify just one revision"))
646 raise error.Abort(_("please specify just one revision"))
647
647
648 if not rev:
648 if not rev:
649 rev = node
649 rev = node
650
650
651 if not rev:
651 if not rev:
652 raise error.Abort(_("please specify a revision to backout"))
652 raise error.Abort(_("please specify a revision to backout"))
653
653
654 date = opts.get('date')
654 date = opts.get('date')
655 if date:
655 if date:
656 opts['date'] = util.parsedate(date)
656 opts['date'] = util.parsedate(date)
657
657
658 cmdutil.checkunfinished(repo)
658 cmdutil.checkunfinished(repo)
659 cmdutil.bailifchanged(repo)
659 cmdutil.bailifchanged(repo)
660 node = scmutil.revsingle(repo, rev).node()
660 node = scmutil.revsingle(repo, rev).node()
661
661
662 op1, op2 = repo.dirstate.parents()
662 op1, op2 = repo.dirstate.parents()
663 if not repo.changelog.isancestor(node, op1):
663 if not repo.changelog.isancestor(node, op1):
664 raise error.Abort(_('cannot backout change that is not an ancestor'))
664 raise error.Abort(_('cannot backout change that is not an ancestor'))
665
665
666 p1, p2 = repo.changelog.parents(node)
666 p1, p2 = repo.changelog.parents(node)
667 if p1 == nullid:
667 if p1 == nullid:
668 raise error.Abort(_('cannot backout a change with no parents'))
668 raise error.Abort(_('cannot backout a change with no parents'))
669 if p2 != nullid:
669 if p2 != nullid:
670 if not opts.get('parent'):
670 if not opts.get('parent'):
671 raise error.Abort(_('cannot backout a merge changeset'))
671 raise error.Abort(_('cannot backout a merge changeset'))
672 p = repo.lookup(opts['parent'])
672 p = repo.lookup(opts['parent'])
673 if p not in (p1, p2):
673 if p not in (p1, p2):
674 raise error.Abort(_('%s is not a parent of %s') %
674 raise error.Abort(_('%s is not a parent of %s') %
675 (short(p), short(node)))
675 (short(p), short(node)))
676 parent = p
676 parent = p
677 else:
677 else:
678 if opts.get('parent'):
678 if opts.get('parent'):
679 raise error.Abort(_('cannot use --parent on non-merge changeset'))
679 raise error.Abort(_('cannot use --parent on non-merge changeset'))
680 parent = p1
680 parent = p1
681
681
682 # the backout should appear on the same branch
682 # the backout should appear on the same branch
683 branch = repo.dirstate.branch()
683 branch = repo.dirstate.branch()
684 bheads = repo.branchheads(branch)
684 bheads = repo.branchheads(branch)
685 rctx = scmutil.revsingle(repo, hex(parent))
685 rctx = scmutil.revsingle(repo, hex(parent))
686 if not opts.get('merge') and op1 != node:
686 if not opts.get('merge') and op1 != node:
687 dsguard = cmdutil.dirstateguard(repo, 'backout')
687 dsguard = cmdutil.dirstateguard(repo, 'backout')
688 try:
688 try:
689 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
689 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
690 'backout')
690 'backout')
691 stats = mergemod.update(repo, parent, True, True, node, False)
691 stats = mergemod.update(repo, parent, True, True, node, False)
692 repo.setparents(op1, op2)
692 repo.setparents(op1, op2)
693 dsguard.close()
693 dsguard.close()
694 hg._showstats(repo, stats)
694 hg._showstats(repo, stats)
695 if stats[3]:
695 if stats[3]:
696 repo.ui.status(_("use 'hg resolve' to retry unresolved "
696 repo.ui.status(_("use 'hg resolve' to retry unresolved "
697 "file merges\n"))
697 "file merges\n"))
698 return 1
698 return 1
699 finally:
699 finally:
700 ui.setconfig('ui', 'forcemerge', '', '')
700 ui.setconfig('ui', 'forcemerge', '', '')
701 lockmod.release(dsguard)
701 lockmod.release(dsguard)
702 else:
702 else:
703 hg.clean(repo, node, show_stats=False)
703 hg.clean(repo, node, show_stats=False)
704 repo.dirstate.setbranch(branch)
704 repo.dirstate.setbranch(branch)
705 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
705 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
706
706
707 if opts.get('no_commit'):
707 if opts.get('no_commit'):
708 msg = _("changeset %s backed out, "
708 msg = _("changeset %s backed out, "
709 "don't forget to commit.\n")
709 "don't forget to commit.\n")
710 ui.status(msg % short(node))
710 ui.status(msg % short(node))
711 return 0
711 return 0
712
712
713 def commitfunc(ui, repo, message, match, opts):
713 def commitfunc(ui, repo, message, match, opts):
714 editform = 'backout'
714 editform = 'backout'
715 e = cmdutil.getcommiteditor(editform=editform, **opts)
715 e = cmdutil.getcommiteditor(editform=editform, **opts)
716 if not message:
716 if not message:
717 # we don't translate commit messages
717 # we don't translate commit messages
718 message = "Backed out changeset %s" % short(node)
718 message = "Backed out changeset %s" % short(node)
719 e = cmdutil.getcommiteditor(edit=True, editform=editform)
719 e = cmdutil.getcommiteditor(edit=True, editform=editform)
720 return repo.commit(message, opts.get('user'), opts.get('date'),
720 return repo.commit(message, opts.get('user'), opts.get('date'),
721 match, editor=e)
721 match, editor=e)
722 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
722 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
723 if not newnode:
723 if not newnode:
724 ui.status(_("nothing changed\n"))
724 ui.status(_("nothing changed\n"))
725 return 1
725 return 1
726 cmdutil.commitstatus(repo, newnode, branch, bheads)
726 cmdutil.commitstatus(repo, newnode, branch, bheads)
727
727
728 def nice(node):
728 def nice(node):
729 return '%d:%s' % (repo.changelog.rev(node), short(node))
729 return '%d:%s' % (repo.changelog.rev(node), short(node))
730 ui.status(_('changeset %s backs out changeset %s\n') %
730 ui.status(_('changeset %s backs out changeset %s\n') %
731 (nice(repo.changelog.tip()), nice(node)))
731 (nice(repo.changelog.tip()), nice(node)))
732 if opts.get('merge') and op1 != node:
732 if opts.get('merge') and op1 != node:
733 hg.clean(repo, op1, show_stats=False)
733 hg.clean(repo, op1, show_stats=False)
734 ui.status(_('merging with changeset %s\n')
734 ui.status(_('merging with changeset %s\n')
735 % nice(repo.changelog.tip()))
735 % nice(repo.changelog.tip()))
736 try:
736 try:
737 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
737 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
738 'backout')
738 'backout')
739 return hg.merge(repo, hex(repo.changelog.tip()))
739 return hg.merge(repo, hex(repo.changelog.tip()))
740 finally:
740 finally:
741 ui.setconfig('ui', 'forcemerge', '', '')
741 ui.setconfig('ui', 'forcemerge', '', '')
742 return 0
742 return 0
743
743
744 @command('bisect',
744 @command('bisect',
745 [('r', 'reset', False, _('reset bisect state')),
745 [('r', 'reset', False, _('reset bisect state')),
746 ('g', 'good', False, _('mark changeset good')),
746 ('g', 'good', False, _('mark changeset good')),
747 ('b', 'bad', False, _('mark changeset bad')),
747 ('b', 'bad', False, _('mark changeset bad')),
748 ('s', 'skip', False, _('skip testing changeset')),
748 ('s', 'skip', False, _('skip testing changeset')),
749 ('e', 'extend', False, _('extend the bisect range')),
749 ('e', 'extend', False, _('extend the bisect range')),
750 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
750 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
751 ('U', 'noupdate', False, _('do not update to target'))],
751 ('U', 'noupdate', False, _('do not update to target'))],
752 _("[-gbsr] [-U] [-c CMD] [REV]"))
752 _("[-gbsr] [-U] [-c CMD] [REV]"))
753 def bisect(ui, repo, rev=None, extra=None, command=None,
753 def bisect(ui, repo, rev=None, extra=None, command=None,
754 reset=None, good=None, bad=None, skip=None, extend=None,
754 reset=None, good=None, bad=None, skip=None, extend=None,
755 noupdate=None):
755 noupdate=None):
756 """subdivision search of changesets
756 """subdivision search of changesets
757
757
758 This command helps to find changesets which introduce problems. To
758 This command helps to find changesets which introduce problems. To
759 use, mark the earliest changeset you know exhibits the problem as
759 use, mark the earliest changeset you know exhibits the problem as
760 bad, then mark the latest changeset which is free from the problem
760 bad, then mark the latest changeset which is free from the problem
761 as good. Bisect will update your working directory to a revision
761 as good. Bisect will update your working directory to a revision
762 for testing (unless the -U/--noupdate option is specified). Once
762 for testing (unless the -U/--noupdate option is specified). Once
763 you have performed tests, mark the working directory as good or
763 you have performed tests, mark the working directory as good or
764 bad, and bisect will either update to another candidate changeset
764 bad, and bisect will either update to another candidate changeset
765 or announce that it has found the bad revision.
765 or announce that it has found the bad revision.
766
766
767 As a shortcut, you can also use the revision argument to mark a
767 As a shortcut, you can also use the revision argument to mark a
768 revision as good or bad without checking it out first.
768 revision as good or bad without checking it out first.
769
769
770 If you supply a command, it will be used for automatic bisection.
770 If you supply a command, it will be used for automatic bisection.
771 The environment variable HG_NODE will contain the ID of the
771 The environment variable HG_NODE will contain the ID of the
772 changeset being tested. The exit status of the command will be
772 changeset being tested. The exit status of the command will be
773 used to mark revisions as good or bad: status 0 means good, 125
773 used to mark revisions as good or bad: status 0 means good, 125
774 means to skip the revision, 127 (command not found) will abort the
774 means to skip the revision, 127 (command not found) will abort the
775 bisection, and any other non-zero exit status means the revision
775 bisection, and any other non-zero exit status means the revision
776 is bad.
776 is bad.
777
777
778 .. container:: verbose
778 .. container:: verbose
779
779
780 Some examples:
780 Some examples:
781
781
782 - start a bisection with known bad revision 34, and good revision 12::
782 - start a bisection with known bad revision 34, and good revision 12::
783
783
784 hg bisect --bad 34
784 hg bisect --bad 34
785 hg bisect --good 12
785 hg bisect --good 12
786
786
787 - advance the current bisection by marking current revision as good or
787 - advance the current bisection by marking current revision as good or
788 bad::
788 bad::
789
789
790 hg bisect --good
790 hg bisect --good
791 hg bisect --bad
791 hg bisect --bad
792
792
793 - mark the current revision, or a known revision, to be skipped (e.g. if
793 - mark the current revision, or a known revision, to be skipped (e.g. if
794 that revision is not usable because of another issue)::
794 that revision is not usable because of another issue)::
795
795
796 hg bisect --skip
796 hg bisect --skip
797 hg bisect --skip 23
797 hg bisect --skip 23
798
798
799 - skip all revisions that do not touch directories ``foo`` or ``bar``::
799 - skip all revisions that do not touch directories ``foo`` or ``bar``::
800
800
801 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
801 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
802
802
803 - forget the current bisection::
803 - forget the current bisection::
804
804
805 hg bisect --reset
805 hg bisect --reset
806
806
807 - use 'make && make tests' to automatically find the first broken
807 - use 'make && make tests' to automatically find the first broken
808 revision::
808 revision::
809
809
810 hg bisect --reset
810 hg bisect --reset
811 hg bisect --bad 34
811 hg bisect --bad 34
812 hg bisect --good 12
812 hg bisect --good 12
813 hg bisect --command "make && make tests"
813 hg bisect --command "make && make tests"
814
814
815 - see all changesets whose states are already known in the current
815 - see all changesets whose states are already known in the current
816 bisection::
816 bisection::
817
817
818 hg log -r "bisect(pruned)"
818 hg log -r "bisect(pruned)"
819
819
820 - see the changeset currently being bisected (especially useful
820 - see the changeset currently being bisected (especially useful
821 if running with -U/--noupdate)::
821 if running with -U/--noupdate)::
822
822
823 hg log -r "bisect(current)"
823 hg log -r "bisect(current)"
824
824
825 - see all changesets that took part in the current bisection::
825 - see all changesets that took part in the current bisection::
826
826
827 hg log -r "bisect(range)"
827 hg log -r "bisect(range)"
828
828
829 - you can even get a nice graph::
829 - you can even get a nice graph::
830
830
831 hg log --graph -r "bisect(range)"
831 hg log --graph -r "bisect(range)"
832
832
833 See :hg:`help revsets` for more about the `bisect()` keyword.
833 See :hg:`help revsets` for more about the `bisect()` keyword.
834
834
835 Returns 0 on success.
835 Returns 0 on success.
836 """
836 """
837 def extendbisectrange(nodes, good):
837 def extendbisectrange(nodes, good):
838 # bisect is incomplete when it ends on a merge node and
838 # bisect is incomplete when it ends on a merge node and
839 # one of the parent was not checked.
839 # one of the parent was not checked.
840 parents = repo[nodes[0]].parents()
840 parents = repo[nodes[0]].parents()
841 if len(parents) > 1:
841 if len(parents) > 1:
842 if good:
842 if good:
843 side = state['bad']
843 side = state['bad']
844 else:
844 else:
845 side = state['good']
845 side = state['good']
846 num = len(set(i.node() for i in parents) & set(side))
846 num = len(set(i.node() for i in parents) & set(side))
847 if num == 1:
847 if num == 1:
848 return parents[0].ancestor(parents[1])
848 return parents[0].ancestor(parents[1])
849 return None
849 return None
850
850
851 def print_result(nodes, good):
851 def print_result(nodes, good):
852 displayer = cmdutil.show_changeset(ui, repo, {})
852 displayer = cmdutil.show_changeset(ui, repo, {})
853 if len(nodes) == 1:
853 if len(nodes) == 1:
854 # narrowed it down to a single revision
854 # narrowed it down to a single revision
855 if good:
855 if good:
856 ui.write(_("The first good revision is:\n"))
856 ui.write(_("The first good revision is:\n"))
857 else:
857 else:
858 ui.write(_("The first bad revision is:\n"))
858 ui.write(_("The first bad revision is:\n"))
859 displayer.show(repo[nodes[0]])
859 displayer.show(repo[nodes[0]])
860 extendnode = extendbisectrange(nodes, good)
860 extendnode = extendbisectrange(nodes, good)
861 if extendnode is not None:
861 if extendnode is not None:
862 ui.write(_('Not all ancestors of this changeset have been'
862 ui.write(_('Not all ancestors of this changeset have been'
863 ' checked.\nUse bisect --extend to continue the '
863 ' checked.\nUse bisect --extend to continue the '
864 'bisection from\nthe common ancestor, %s.\n')
864 'bisection from\nthe common ancestor, %s.\n')
865 % extendnode)
865 % extendnode)
866 else:
866 else:
867 # multiple possible revisions
867 # multiple possible revisions
868 if good:
868 if good:
869 ui.write(_("Due to skipped revisions, the first "
869 ui.write(_("Due to skipped revisions, the first "
870 "good revision could be any of:\n"))
870 "good revision could be any of:\n"))
871 else:
871 else:
872 ui.write(_("Due to skipped revisions, the first "
872 ui.write(_("Due to skipped revisions, the first "
873 "bad revision could be any of:\n"))
873 "bad revision could be any of:\n"))
874 for n in nodes:
874 for n in nodes:
875 displayer.show(repo[n])
875 displayer.show(repo[n])
876 displayer.close()
876 displayer.close()
877
877
878 def check_state(state, interactive=True):
878 def check_state(state, interactive=True):
879 if not state['good'] or not state['bad']:
879 if not state['good'] or not state['bad']:
880 if (good or bad or skip or reset) and interactive:
880 if (good or bad or skip or reset) and interactive:
881 return
881 return
882 if not state['good']:
882 if not state['good']:
883 raise error.Abort(_('cannot bisect (no known good revisions)'))
883 raise error.Abort(_('cannot bisect (no known good revisions)'))
884 else:
884 else:
885 raise error.Abort(_('cannot bisect (no known bad revisions)'))
885 raise error.Abort(_('cannot bisect (no known bad revisions)'))
886 return True
886 return True
887
887
888 # backward compatibility
888 # backward compatibility
889 if rev in "good bad reset init".split():
889 if rev in "good bad reset init".split():
890 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
890 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
891 cmd, rev, extra = rev, extra, None
891 cmd, rev, extra = rev, extra, None
892 if cmd == "good":
892 if cmd == "good":
893 good = True
893 good = True
894 elif cmd == "bad":
894 elif cmd == "bad":
895 bad = True
895 bad = True
896 else:
896 else:
897 reset = True
897 reset = True
898 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
898 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
899 raise error.Abort(_('incompatible arguments'))
899 raise error.Abort(_('incompatible arguments'))
900
900
901 cmdutil.checkunfinished(repo)
901 cmdutil.checkunfinished(repo)
902
902
903 if reset:
903 if reset:
904 p = repo.join("bisect.state")
904 p = repo.join("bisect.state")
905 if os.path.exists(p):
905 if os.path.exists(p):
906 os.unlink(p)
906 os.unlink(p)
907 return
907 return
908
908
909 state = hbisect.load_state(repo)
909 state = hbisect.load_state(repo)
910
910
911 if command:
911 if command:
912 changesets = 1
912 changesets = 1
913 if noupdate:
913 if noupdate:
914 try:
914 try:
915 node = state['current'][0]
915 node = state['current'][0]
916 except LookupError:
916 except LookupError:
917 raise error.Abort(_('current bisect revision is unknown - '
917 raise error.Abort(_('current bisect revision is unknown - '
918 'start a new bisect to fix'))
918 'start a new bisect to fix'))
919 else:
919 else:
920 node, p2 = repo.dirstate.parents()
920 node, p2 = repo.dirstate.parents()
921 if p2 != nullid:
921 if p2 != nullid:
922 raise error.Abort(_('current bisect revision is a merge'))
922 raise error.Abort(_('current bisect revision is a merge'))
923 try:
923 try:
924 while changesets:
924 while changesets:
925 # update state
925 # update state
926 state['current'] = [node]
926 state['current'] = [node]
927 hbisect.save_state(repo, state)
927 hbisect.save_state(repo, state)
928 status = ui.system(command, environ={'HG_NODE': hex(node)})
928 status = ui.system(command, environ={'HG_NODE': hex(node)})
929 if status == 125:
929 if status == 125:
930 transition = "skip"
930 transition = "skip"
931 elif status == 0:
931 elif status == 0:
932 transition = "good"
932 transition = "good"
933 # status < 0 means process was killed
933 # status < 0 means process was killed
934 elif status == 127:
934 elif status == 127:
935 raise error.Abort(_("failed to execute %s") % command)
935 raise error.Abort(_("failed to execute %s") % command)
936 elif status < 0:
936 elif status < 0:
937 raise error.Abort(_("%s killed") % command)
937 raise error.Abort(_("%s killed") % command)
938 else:
938 else:
939 transition = "bad"
939 transition = "bad"
940 ctx = scmutil.revsingle(repo, rev, node)
940 ctx = scmutil.revsingle(repo, rev, node)
941 rev = None # clear for future iterations
941 rev = None # clear for future iterations
942 state[transition].append(ctx.node())
942 state[transition].append(ctx.node())
943 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
943 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
944 check_state(state, interactive=False)
944 check_state(state, interactive=False)
945 # bisect
945 # bisect
946 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
946 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
947 # update to next check
947 # update to next check
948 node = nodes[0]
948 node = nodes[0]
949 if not noupdate:
949 if not noupdate:
950 cmdutil.bailifchanged(repo)
950 cmdutil.bailifchanged(repo)
951 hg.clean(repo, node, show_stats=False)
951 hg.clean(repo, node, show_stats=False)
952 finally:
952 finally:
953 state['current'] = [node]
953 state['current'] = [node]
954 hbisect.save_state(repo, state)
954 hbisect.save_state(repo, state)
955 print_result(nodes, bgood)
955 print_result(nodes, bgood)
956 return
956 return
957
957
958 # update state
958 # update state
959
959
960 if rev:
960 if rev:
961 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
961 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
962 else:
962 else:
963 nodes = [repo.lookup('.')]
963 nodes = [repo.lookup('.')]
964
964
965 if good or bad or skip:
965 if good or bad or skip:
966 if good:
966 if good:
967 state['good'] += nodes
967 state['good'] += nodes
968 elif bad:
968 elif bad:
969 state['bad'] += nodes
969 state['bad'] += nodes
970 elif skip:
970 elif skip:
971 state['skip'] += nodes
971 state['skip'] += nodes
972 hbisect.save_state(repo, state)
972 hbisect.save_state(repo, state)
973
973
974 if not check_state(state):
974 if not check_state(state):
975 return
975 return
976
976
977 # actually bisect
977 # actually bisect
978 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
978 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
979 if extend:
979 if extend:
980 if not changesets:
980 if not changesets:
981 extendnode = extendbisectrange(nodes, good)
981 extendnode = extendbisectrange(nodes, good)
982 if extendnode is not None:
982 if extendnode is not None:
983 ui.write(_("Extending search to changeset %d:%s\n")
983 ui.write(_("Extending search to changeset %d:%s\n")
984 % (extendnode.rev(), extendnode))
984 % (extendnode.rev(), extendnode))
985 state['current'] = [extendnode.node()]
985 state['current'] = [extendnode.node()]
986 hbisect.save_state(repo, state)
986 hbisect.save_state(repo, state)
987 if noupdate:
987 if noupdate:
988 return
988 return
989 cmdutil.bailifchanged(repo)
989 cmdutil.bailifchanged(repo)
990 return hg.clean(repo, extendnode.node())
990 return hg.clean(repo, extendnode.node())
991 raise error.Abort(_("nothing to extend"))
991 raise error.Abort(_("nothing to extend"))
992
992
993 if changesets == 0:
993 if changesets == 0:
994 print_result(nodes, good)
994 print_result(nodes, good)
995 else:
995 else:
996 assert len(nodes) == 1 # only a single node can be tested next
996 assert len(nodes) == 1 # only a single node can be tested next
997 node = nodes[0]
997 node = nodes[0]
998 # compute the approximate number of remaining tests
998 # compute the approximate number of remaining tests
999 tests, size = 0, 2
999 tests, size = 0, 2
1000 while size <= changesets:
1000 while size <= changesets:
1001 tests, size = tests + 1, size * 2
1001 tests, size = tests + 1, size * 2
1002 rev = repo.changelog.rev(node)
1002 rev = repo.changelog.rev(node)
1003 ui.write(_("Testing changeset %d:%s "
1003 ui.write(_("Testing changeset %d:%s "
1004 "(%d changesets remaining, ~%d tests)\n")
1004 "(%d changesets remaining, ~%d tests)\n")
1005 % (rev, short(node), changesets, tests))
1005 % (rev, short(node), changesets, tests))
1006 state['current'] = [node]
1006 state['current'] = [node]
1007 hbisect.save_state(repo, state)
1007 hbisect.save_state(repo, state)
1008 if not noupdate:
1008 if not noupdate:
1009 cmdutil.bailifchanged(repo)
1009 cmdutil.bailifchanged(repo)
1010 return hg.clean(repo, node)
1010 return hg.clean(repo, node)
1011
1011
1012 @command('bookmarks|bookmark',
1012 @command('bookmarks|bookmark',
1013 [('f', 'force', False, _('force')),
1013 [('f', 'force', False, _('force')),
1014 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
1014 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
1015 ('d', 'delete', False, _('delete a given bookmark')),
1015 ('d', 'delete', False, _('delete a given bookmark')),
1016 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
1016 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
1017 ('i', 'inactive', False, _('mark a bookmark inactive')),
1017 ('i', 'inactive', False, _('mark a bookmark inactive')),
1018 ] + formatteropts,
1018 ] + formatteropts,
1019 _('hg bookmarks [OPTIONS]... [NAME]...'))
1019 _('hg bookmarks [OPTIONS]... [NAME]...'))
1020 def bookmark(ui, repo, *names, **opts):
1020 def bookmark(ui, repo, *names, **opts):
1021 '''create a new bookmark or list existing bookmarks
1021 '''create a new bookmark or list existing bookmarks
1022
1022
1023 Bookmarks are labels on changesets to help track lines of development.
1023 Bookmarks are labels on changesets to help track lines of development.
1024 Bookmarks are unversioned and can be moved, renamed and deleted.
1024 Bookmarks are unversioned and can be moved, renamed and deleted.
1025 Deleting or moving a bookmark has no effect on the associated changesets.
1025 Deleting or moving a bookmark has no effect on the associated changesets.
1026
1026
1027 Creating or updating to a bookmark causes it to be marked as 'active'.
1027 Creating or updating to a bookmark causes it to be marked as 'active'.
1028 The active bookmark is indicated with a '*'.
1028 The active bookmark is indicated with a '*'.
1029 When a commit is made, the active bookmark will advance to the new commit.
1029 When a commit is made, the active bookmark will advance to the new commit.
1030 A plain :hg:`update` will also advance an active bookmark, if possible.
1030 A plain :hg:`update` will also advance an active bookmark, if possible.
1031 Updating away from a bookmark will cause it to be deactivated.
1031 Updating away from a bookmark will cause it to be deactivated.
1032
1032
1033 Bookmarks can be pushed and pulled between repositories (see
1033 Bookmarks can be pushed and pulled between repositories (see
1034 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1034 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1035 diverged, a new 'divergent bookmark' of the form 'name@path' will
1035 diverged, a new 'divergent bookmark' of the form 'name@path' will
1036 be created. Using :hg:`merge` will resolve the divergence.
1036 be created. Using :hg:`merge` will resolve the divergence.
1037
1037
1038 A bookmark named '@' has the special property that :hg:`clone` will
1038 A bookmark named '@' has the special property that :hg:`clone` will
1039 check it out by default if it exists.
1039 check it out by default if it exists.
1040
1040
1041 .. container:: verbose
1041 .. container:: verbose
1042
1042
1043 Examples:
1043 Examples:
1044
1044
1045 - create an active bookmark for a new line of development::
1045 - create an active bookmark for a new line of development::
1046
1046
1047 hg book new-feature
1047 hg book new-feature
1048
1048
1049 - create an inactive bookmark as a place marker::
1049 - create an inactive bookmark as a place marker::
1050
1050
1051 hg book -i reviewed
1051 hg book -i reviewed
1052
1052
1053 - create an inactive bookmark on another changeset::
1053 - create an inactive bookmark on another changeset::
1054
1054
1055 hg book -r .^ tested
1055 hg book -r .^ tested
1056
1056
1057 - rename bookmark turkey to dinner::
1057 - rename bookmark turkey to dinner::
1058
1058
1059 hg book -m turkey dinner
1059 hg book -m turkey dinner
1060
1060
1061 - move the '@' bookmark from another branch::
1061 - move the '@' bookmark from another branch::
1062
1062
1063 hg book -f @
1063 hg book -f @
1064 '''
1064 '''
1065 force = opts.get('force')
1065 force = opts.get('force')
1066 rev = opts.get('rev')
1066 rev = opts.get('rev')
1067 delete = opts.get('delete')
1067 delete = opts.get('delete')
1068 rename = opts.get('rename')
1068 rename = opts.get('rename')
1069 inactive = opts.get('inactive')
1069 inactive = opts.get('inactive')
1070
1070
1071 def checkformat(mark):
1071 def checkformat(mark):
1072 mark = mark.strip()
1072 mark = mark.strip()
1073 if not mark:
1073 if not mark:
1074 raise error.Abort(_("bookmark names cannot consist entirely of "
1074 raise error.Abort(_("bookmark names cannot consist entirely of "
1075 "whitespace"))
1075 "whitespace"))
1076 scmutil.checknewlabel(repo, mark, 'bookmark')
1076 scmutil.checknewlabel(repo, mark, 'bookmark')
1077 return mark
1077 return mark
1078
1078
1079 def checkconflict(repo, mark, cur, force=False, target=None):
1079 def checkconflict(repo, mark, cur, force=False, target=None):
1080 if mark in marks and not force:
1080 if mark in marks and not force:
1081 if target:
1081 if target:
1082 if marks[mark] == target and target == cur:
1082 if marks[mark] == target and target == cur:
1083 # re-activating a bookmark
1083 # re-activating a bookmark
1084 return
1084 return
1085 anc = repo.changelog.ancestors([repo[target].rev()])
1085 anc = repo.changelog.ancestors([repo[target].rev()])
1086 bmctx = repo[marks[mark]]
1086 bmctx = repo[marks[mark]]
1087 divs = [repo[b].node() for b in marks
1087 divs = [repo[b].node() for b in marks
1088 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
1088 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
1089
1089
1090 # allow resolving a single divergent bookmark even if moving
1090 # allow resolving a single divergent bookmark even if moving
1091 # the bookmark across branches when a revision is specified
1091 # the bookmark across branches when a revision is specified
1092 # that contains a divergent bookmark
1092 # that contains a divergent bookmark
1093 if bmctx.rev() not in anc and target in divs:
1093 if bmctx.rev() not in anc and target in divs:
1094 bookmarks.deletedivergent(repo, [target], mark)
1094 bookmarks.deletedivergent(repo, [target], mark)
1095 return
1095 return
1096
1096
1097 deletefrom = [b for b in divs
1097 deletefrom = [b for b in divs
1098 if repo[b].rev() in anc or b == target]
1098 if repo[b].rev() in anc or b == target]
1099 bookmarks.deletedivergent(repo, deletefrom, mark)
1099 bookmarks.deletedivergent(repo, deletefrom, mark)
1100 if bookmarks.validdest(repo, bmctx, repo[target]):
1100 if bookmarks.validdest(repo, bmctx, repo[target]):
1101 ui.status(_("moving bookmark '%s' forward from %s\n") %
1101 ui.status(_("moving bookmark '%s' forward from %s\n") %
1102 (mark, short(bmctx.node())))
1102 (mark, short(bmctx.node())))
1103 return
1103 return
1104 raise error.Abort(_("bookmark '%s' already exists "
1104 raise error.Abort(_("bookmark '%s' already exists "
1105 "(use -f to force)") % mark)
1105 "(use -f to force)") % mark)
1106 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
1106 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
1107 and not force):
1107 and not force):
1108 raise error.Abort(
1108 raise error.Abort(
1109 _("a bookmark cannot have the name of an existing branch"))
1109 _("a bookmark cannot have the name of an existing branch"))
1110
1110
1111 if delete and rename:
1111 if delete and rename:
1112 raise error.Abort(_("--delete and --rename are incompatible"))
1112 raise error.Abort(_("--delete and --rename are incompatible"))
1113 if delete and rev:
1113 if delete and rev:
1114 raise error.Abort(_("--rev is incompatible with --delete"))
1114 raise error.Abort(_("--rev is incompatible with --delete"))
1115 if rename and rev:
1115 if rename and rev:
1116 raise error.Abort(_("--rev is incompatible with --rename"))
1116 raise error.Abort(_("--rev is incompatible with --rename"))
1117 if not names and (delete or rev):
1117 if not names and (delete or rev):
1118 raise error.Abort(_("bookmark name required"))
1118 raise error.Abort(_("bookmark name required"))
1119
1119
1120 if delete or rename or names or inactive:
1120 if delete or rename or names or inactive:
1121 wlock = lock = tr = None
1121 wlock = lock = tr = None
1122 try:
1122 try:
1123 wlock = repo.wlock()
1123 wlock = repo.wlock()
1124 lock = repo.lock()
1124 lock = repo.lock()
1125 cur = repo.changectx('.').node()
1125 cur = repo.changectx('.').node()
1126 marks = repo._bookmarks
1126 marks = repo._bookmarks
1127 if delete:
1127 if delete:
1128 tr = repo.transaction('bookmark')
1128 tr = repo.transaction('bookmark')
1129 for mark in names:
1129 for mark in names:
1130 if mark not in marks:
1130 if mark not in marks:
1131 raise error.Abort(_("bookmark '%s' does not exist") %
1131 raise error.Abort(_("bookmark '%s' does not exist") %
1132 mark)
1132 mark)
1133 if mark == repo._activebookmark:
1133 if mark == repo._activebookmark:
1134 bookmarks.deactivate(repo)
1134 bookmarks.deactivate(repo)
1135 del marks[mark]
1135 del marks[mark]
1136
1136
1137 elif rename:
1137 elif rename:
1138 tr = repo.transaction('bookmark')
1138 tr = repo.transaction('bookmark')
1139 if not names:
1139 if not names:
1140 raise error.Abort(_("new bookmark name required"))
1140 raise error.Abort(_("new bookmark name required"))
1141 elif len(names) > 1:
1141 elif len(names) > 1:
1142 raise error.Abort(_("only one new bookmark name allowed"))
1142 raise error.Abort(_("only one new bookmark name allowed"))
1143 mark = checkformat(names[0])
1143 mark = checkformat(names[0])
1144 if rename not in marks:
1144 if rename not in marks:
1145 raise error.Abort(_("bookmark '%s' does not exist")
1145 raise error.Abort(_("bookmark '%s' does not exist")
1146 % rename)
1146 % rename)
1147 checkconflict(repo, mark, cur, force)
1147 checkconflict(repo, mark, cur, force)
1148 marks[mark] = marks[rename]
1148 marks[mark] = marks[rename]
1149 if repo._activebookmark == rename and not inactive:
1149 if repo._activebookmark == rename and not inactive:
1150 bookmarks.activate(repo, mark)
1150 bookmarks.activate(repo, mark)
1151 del marks[rename]
1151 del marks[rename]
1152 elif names:
1152 elif names:
1153 tr = repo.transaction('bookmark')
1153 tr = repo.transaction('bookmark')
1154 newact = None
1154 newact = None
1155 for mark in names:
1155 for mark in names:
1156 mark = checkformat(mark)
1156 mark = checkformat(mark)
1157 if newact is None:
1157 if newact is None:
1158 newact = mark
1158 newact = mark
1159 if inactive and mark == repo._activebookmark:
1159 if inactive and mark == repo._activebookmark:
1160 bookmarks.deactivate(repo)
1160 bookmarks.deactivate(repo)
1161 return
1161 return
1162 tgt = cur
1162 tgt = cur
1163 if rev:
1163 if rev:
1164 tgt = scmutil.revsingle(repo, rev).node()
1164 tgt = scmutil.revsingle(repo, rev).node()
1165 checkconflict(repo, mark, cur, force, tgt)
1165 checkconflict(repo, mark, cur, force, tgt)
1166 marks[mark] = tgt
1166 marks[mark] = tgt
1167 if not inactive and cur == marks[newact] and not rev:
1167 if not inactive and cur == marks[newact] and not rev:
1168 bookmarks.activate(repo, newact)
1168 bookmarks.activate(repo, newact)
1169 elif cur != tgt and newact == repo._activebookmark:
1169 elif cur != tgt and newact == repo._activebookmark:
1170 bookmarks.deactivate(repo)
1170 bookmarks.deactivate(repo)
1171 elif inactive:
1171 elif inactive:
1172 if len(marks) == 0:
1172 if len(marks) == 0:
1173 ui.status(_("no bookmarks set\n"))
1173 ui.status(_("no bookmarks set\n"))
1174 elif not repo._activebookmark:
1174 elif not repo._activebookmark:
1175 ui.status(_("no active bookmark\n"))
1175 ui.status(_("no active bookmark\n"))
1176 else:
1176 else:
1177 bookmarks.deactivate(repo)
1177 bookmarks.deactivate(repo)
1178 if tr is not None:
1178 if tr is not None:
1179 marks.recordchange(tr)
1179 marks.recordchange(tr)
1180 tr.close()
1180 tr.close()
1181 finally:
1181 finally:
1182 lockmod.release(tr, lock, wlock)
1182 lockmod.release(tr, lock, wlock)
1183 else: # show bookmarks
1183 else: # show bookmarks
1184 fm = ui.formatter('bookmarks', opts)
1184 fm = ui.formatter('bookmarks', opts)
1185 hexfn = fm.hexfunc
1185 hexfn = fm.hexfunc
1186 marks = repo._bookmarks
1186 marks = repo._bookmarks
1187 if len(marks) == 0 and not fm:
1187 if len(marks) == 0 and not fm:
1188 ui.status(_("no bookmarks set\n"))
1188 ui.status(_("no bookmarks set\n"))
1189 for bmark, n in sorted(marks.iteritems()):
1189 for bmark, n in sorted(marks.iteritems()):
1190 active = repo._activebookmark
1190 active = repo._activebookmark
1191 if bmark == active:
1191 if bmark == active:
1192 prefix, label = '*', activebookmarklabel
1192 prefix, label = '*', activebookmarklabel
1193 else:
1193 else:
1194 prefix, label = ' ', ''
1194 prefix, label = ' ', ''
1195
1195
1196 fm.startitem()
1196 fm.startitem()
1197 if not ui.quiet:
1197 if not ui.quiet:
1198 fm.plain(' %s ' % prefix, label=label)
1198 fm.plain(' %s ' % prefix, label=label)
1199 fm.write('bookmark', '%s', bmark, label=label)
1199 fm.write('bookmark', '%s', bmark, label=label)
1200 pad = " " * (25 - encoding.colwidth(bmark))
1200 pad = " " * (25 - encoding.colwidth(bmark))
1201 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1201 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1202 repo.changelog.rev(n), hexfn(n), label=label)
1202 repo.changelog.rev(n), hexfn(n), label=label)
1203 fm.data(active=(bmark == active))
1203 fm.data(active=(bmark == active))
1204 fm.plain('\n')
1204 fm.plain('\n')
1205 fm.end()
1205 fm.end()
1206
1206
1207 @command('branch',
1207 @command('branch',
1208 [('f', 'force', None,
1208 [('f', 'force', None,
1209 _('set branch name even if it shadows an existing branch')),
1209 _('set branch name even if it shadows an existing branch')),
1210 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1210 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1211 _('[-fC] [NAME]'))
1211 _('[-fC] [NAME]'))
1212 def branch(ui, repo, label=None, **opts):
1212 def branch(ui, repo, label=None, **opts):
1213 """set or show the current branch name
1213 """set or show the current branch name
1214
1214
1215 .. note::
1215 .. note::
1216
1216
1217 Branch names are permanent and global. Use :hg:`bookmark` to create a
1217 Branch names are permanent and global. Use :hg:`bookmark` to create a
1218 light-weight bookmark instead. See :hg:`help glossary` for more
1218 light-weight bookmark instead. See :hg:`help glossary` for more
1219 information about named branches and bookmarks.
1219 information about named branches and bookmarks.
1220
1220
1221 With no argument, show the current branch name. With one argument,
1221 With no argument, show the current branch name. With one argument,
1222 set the working directory branch name (the branch will not exist
1222 set the working directory branch name (the branch will not exist
1223 in the repository until the next commit). Standard practice
1223 in the repository until the next commit). Standard practice
1224 recommends that primary development take place on the 'default'
1224 recommends that primary development take place on the 'default'
1225 branch.
1225 branch.
1226
1226
1227 Unless -f/--force is specified, branch will not let you set a
1227 Unless -f/--force is specified, branch will not let you set a
1228 branch name that already exists.
1228 branch name that already exists.
1229
1229
1230 Use -C/--clean to reset the working directory branch to that of
1230 Use -C/--clean to reset the working directory branch to that of
1231 the parent of the working directory, negating a previous branch
1231 the parent of the working directory, negating a previous branch
1232 change.
1232 change.
1233
1233
1234 Use the command :hg:`update` to switch to an existing branch. Use
1234 Use the command :hg:`update` to switch to an existing branch. Use
1235 :hg:`commit --close-branch` to mark this branch head as closed.
1235 :hg:`commit --close-branch` to mark this branch head as closed.
1236 When all heads of a branch are closed, the branch will be
1236 When all heads of a branch are closed, the branch will be
1237 considered closed.
1237 considered closed.
1238
1238
1239 Returns 0 on success.
1239 Returns 0 on success.
1240 """
1240 """
1241 if label:
1241 if label:
1242 label = label.strip()
1242 label = label.strip()
1243
1243
1244 if not opts.get('clean') and not label:
1244 if not opts.get('clean') and not label:
1245 ui.write("%s\n" % repo.dirstate.branch())
1245 ui.write("%s\n" % repo.dirstate.branch())
1246 return
1246 return
1247
1247
1248 with repo.wlock():
1248 with repo.wlock():
1249 if opts.get('clean'):
1249 if opts.get('clean'):
1250 label = repo[None].p1().branch()
1250 label = repo[None].p1().branch()
1251 repo.dirstate.setbranch(label)
1251 repo.dirstate.setbranch(label)
1252 ui.status(_('reset working directory to branch %s\n') % label)
1252 ui.status(_('reset working directory to branch %s\n') % label)
1253 elif label:
1253 elif label:
1254 if not opts.get('force') and label in repo.branchmap():
1254 if not opts.get('force') and label in repo.branchmap():
1255 if label not in [p.branch() for p in repo[None].parents()]:
1255 if label not in [p.branch() for p in repo[None].parents()]:
1256 raise error.Abort(_('a branch of the same name already'
1256 raise error.Abort(_('a branch of the same name already'
1257 ' exists'),
1257 ' exists'),
1258 # i18n: "it" refers to an existing branch
1258 # i18n: "it" refers to an existing branch
1259 hint=_("use 'hg update' to switch to it"))
1259 hint=_("use 'hg update' to switch to it"))
1260 scmutil.checknewlabel(repo, label, 'branch')
1260 scmutil.checknewlabel(repo, label, 'branch')
1261 repo.dirstate.setbranch(label)
1261 repo.dirstate.setbranch(label)
1262 ui.status(_('marked working directory as branch %s\n') % label)
1262 ui.status(_('marked working directory as branch %s\n') % label)
1263
1263
1264 # find any open named branches aside from default
1264 # find any open named branches aside from default
1265 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1265 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1266 if n != "default" and not c]
1266 if n != "default" and not c]
1267 if not others:
1267 if not others:
1268 ui.status(_('(branches are permanent and global, '
1268 ui.status(_('(branches are permanent and global, '
1269 'did you want a bookmark?)\n'))
1269 'did you want a bookmark?)\n'))
1270
1270
1271 @command('branches',
1271 @command('branches',
1272 [('a', 'active', False,
1272 [('a', 'active', False,
1273 _('show only branches that have unmerged heads (DEPRECATED)')),
1273 _('show only branches that have unmerged heads (DEPRECATED)')),
1274 ('c', 'closed', False, _('show normal and closed branches')),
1274 ('c', 'closed', False, _('show normal and closed branches')),
1275 ] + formatteropts,
1275 ] + formatteropts,
1276 _('[-c]'))
1276 _('[-c]'))
1277 def branches(ui, repo, active=False, closed=False, **opts):
1277 def branches(ui, repo, active=False, closed=False, **opts):
1278 """list repository named branches
1278 """list repository named branches
1279
1279
1280 List the repository's named branches, indicating which ones are
1280 List the repository's named branches, indicating which ones are
1281 inactive. If -c/--closed is specified, also list branches which have
1281 inactive. If -c/--closed is specified, also list branches which have
1282 been marked closed (see :hg:`commit --close-branch`).
1282 been marked closed (see :hg:`commit --close-branch`).
1283
1283
1284 Use the command :hg:`update` to switch to an existing branch.
1284 Use the command :hg:`update` to switch to an existing branch.
1285
1285
1286 Returns 0.
1286 Returns 0.
1287 """
1287 """
1288
1288
1289 fm = ui.formatter('branches', opts)
1289 fm = ui.formatter('branches', opts)
1290 hexfunc = fm.hexfunc
1290 hexfunc = fm.hexfunc
1291
1291
1292 allheads = set(repo.heads())
1292 allheads = set(repo.heads())
1293 branches = []
1293 branches = []
1294 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1294 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1295 isactive = not isclosed and bool(set(heads) & allheads)
1295 isactive = not isclosed and bool(set(heads) & allheads)
1296 branches.append((tag, repo[tip], isactive, not isclosed))
1296 branches.append((tag, repo[tip], isactive, not isclosed))
1297 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1297 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1298 reverse=True)
1298 reverse=True)
1299
1299
1300 for tag, ctx, isactive, isopen in branches:
1300 for tag, ctx, isactive, isopen in branches:
1301 if active and not isactive:
1301 if active and not isactive:
1302 continue
1302 continue
1303 if isactive:
1303 if isactive:
1304 label = 'branches.active'
1304 label = 'branches.active'
1305 notice = ''
1305 notice = ''
1306 elif not isopen:
1306 elif not isopen:
1307 if not closed:
1307 if not closed:
1308 continue
1308 continue
1309 label = 'branches.closed'
1309 label = 'branches.closed'
1310 notice = _(' (closed)')
1310 notice = _(' (closed)')
1311 else:
1311 else:
1312 label = 'branches.inactive'
1312 label = 'branches.inactive'
1313 notice = _(' (inactive)')
1313 notice = _(' (inactive)')
1314 current = (tag == repo.dirstate.branch())
1314 current = (tag == repo.dirstate.branch())
1315 if current:
1315 if current:
1316 label = 'branches.current'
1316 label = 'branches.current'
1317
1317
1318 fm.startitem()
1318 fm.startitem()
1319 fm.write('branch', '%s', tag, label=label)
1319 fm.write('branch', '%s', tag, label=label)
1320 rev = ctx.rev()
1320 rev = ctx.rev()
1321 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1321 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1322 fmt = ' ' * padsize + ' %d:%s'
1322 fmt = ' ' * padsize + ' %d:%s'
1323 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1323 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1324 label='log.changeset changeset.%s' % ctx.phasestr())
1324 label='log.changeset changeset.%s' % ctx.phasestr())
1325 fm.data(active=isactive, closed=not isopen, current=current)
1325 fm.data(active=isactive, closed=not isopen, current=current)
1326 if not ui.quiet:
1326 if not ui.quiet:
1327 fm.plain(notice)
1327 fm.plain(notice)
1328 fm.plain('\n')
1328 fm.plain('\n')
1329 fm.end()
1329 fm.end()
1330
1330
1331 @command('bundle',
1331 @command('bundle',
1332 [('f', 'force', None, _('run even when the destination is unrelated')),
1332 [('f', 'force', None, _('run even when the destination is unrelated')),
1333 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1333 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1334 _('REV')),
1334 _('REV')),
1335 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1335 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1336 _('BRANCH')),
1336 _('BRANCH')),
1337 ('', 'base', [],
1337 ('', 'base', [],
1338 _('a base changeset assumed to be available at the destination'),
1338 _('a base changeset assumed to be available at the destination'),
1339 _('REV')),
1339 _('REV')),
1340 ('a', 'all', None, _('bundle all changesets in the repository')),
1340 ('a', 'all', None, _('bundle all changesets in the repository')),
1341 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1341 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1342 ] + remoteopts,
1342 ] + remoteopts,
1343 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1343 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1344 def bundle(ui, repo, fname, dest=None, **opts):
1344 def bundle(ui, repo, fname, dest=None, **opts):
1345 """create a changegroup file
1345 """create a changegroup file
1346
1346
1347 Generate a changegroup file collecting changesets to be added
1347 Generate a changegroup file collecting changesets to be added
1348 to a repository.
1348 to a repository.
1349
1349
1350 To create a bundle containing all changesets, use -a/--all
1350 To create a bundle containing all changesets, use -a/--all
1351 (or --base null). Otherwise, hg assumes the destination will have
1351 (or --base null). Otherwise, hg assumes the destination will have
1352 all the nodes you specify with --base parameters. Otherwise, hg
1352 all the nodes you specify with --base parameters. Otherwise, hg
1353 will assume the repository has all the nodes in destination, or
1353 will assume the repository has all the nodes in destination, or
1354 default-push/default if no destination is specified.
1354 default-push/default if no destination is specified.
1355
1355
1356 You can change bundle format with the -t/--type option. You can
1356 You can change bundle format with the -t/--type option. You can
1357 specify a compression, a bundle version or both using a dash
1357 specify a compression, a bundle version or both using a dash
1358 (comp-version). The available compression methods are: none, bzip2,
1358 (comp-version). The available compression methods are: none, bzip2,
1359 and gzip (by default, bundles are compressed using bzip2). The
1359 and gzip (by default, bundles are compressed using bzip2). The
1360 available formats are: v1, v2 (default to most suitable).
1360 available formats are: v1, v2 (default to most suitable).
1361
1361
1362 The bundle file can then be transferred using conventional means
1362 The bundle file can then be transferred using conventional means
1363 and applied to another repository with the unbundle or pull
1363 and applied to another repository with the unbundle or pull
1364 command. This is useful when direct push and pull are not
1364 command. This is useful when direct push and pull are not
1365 available or when exporting an entire repository is undesirable.
1365 available or when exporting an entire repository is undesirable.
1366
1366
1367 Applying bundles preserves all changeset contents including
1367 Applying bundles preserves all changeset contents including
1368 permissions, copy/rename information, and revision history.
1368 permissions, copy/rename information, and revision history.
1369
1369
1370 Returns 0 on success, 1 if no changes found.
1370 Returns 0 on success, 1 if no changes found.
1371 """
1371 """
1372 revs = None
1372 revs = None
1373 if 'rev' in opts:
1373 if 'rev' in opts:
1374 revstrings = opts['rev']
1374 revstrings = opts['rev']
1375 revs = scmutil.revrange(repo, revstrings)
1375 revs = scmutil.revrange(repo, revstrings)
1376 if revstrings and not revs:
1376 if revstrings and not revs:
1377 raise error.Abort(_('no commits to bundle'))
1377 raise error.Abort(_('no commits to bundle'))
1378
1378
1379 bundletype = opts.get('type', 'bzip2').lower()
1379 bundletype = opts.get('type', 'bzip2').lower()
1380 try:
1380 try:
1381 bcompression, cgversion, params = exchange.parsebundlespec(
1381 bcompression, cgversion, params = exchange.parsebundlespec(
1382 repo, bundletype, strict=False)
1382 repo, bundletype, strict=False)
1383 except error.UnsupportedBundleSpecification as e:
1383 except error.UnsupportedBundleSpecification as e:
1384 raise error.Abort(str(e),
1384 raise error.Abort(str(e),
1385 hint=_('see "hg help bundle" for supported '
1385 hint=_('see "hg help bundle" for supported '
1386 'values for --type'))
1386 'values for --type'))
1387
1387
1388 # Packed bundles are a pseudo bundle format for now.
1388 # Packed bundles are a pseudo bundle format for now.
1389 if cgversion == 's1':
1389 if cgversion == 's1':
1390 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1390 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1391 hint=_("use 'hg debugcreatestreamclonebundle'"))
1391 hint=_("use 'hg debugcreatestreamclonebundle'"))
1392
1392
1393 if opts.get('all'):
1393 if opts.get('all'):
1394 if dest:
1394 if dest:
1395 raise error.Abort(_("--all is incompatible with specifying "
1395 raise error.Abort(_("--all is incompatible with specifying "
1396 "a destination"))
1396 "a destination"))
1397 if opts.get('base'):
1397 if opts.get('base'):
1398 ui.warn(_("ignoring --base because --all was specified\n"))
1398 ui.warn(_("ignoring --base because --all was specified\n"))
1399 base = ['null']
1399 base = ['null']
1400 else:
1400 else:
1401 base = scmutil.revrange(repo, opts.get('base'))
1401 base = scmutil.revrange(repo, opts.get('base'))
1402 # TODO: get desired bundlecaps from command line.
1402 # TODO: get desired bundlecaps from command line.
1403 bundlecaps = None
1403 bundlecaps = None
1404 if cgversion not in changegroup.supportedoutgoingversions(repo):
1404 if cgversion not in changegroup.supportedoutgoingversions(repo):
1405 raise error.Abort(_("repository does not support bundle version %s") %
1405 raise error.Abort(_("repository does not support bundle version %s") %
1406 cgversion)
1406 cgversion)
1407
1407
1408 if base:
1408 if base:
1409 if dest:
1409 if dest:
1410 raise error.Abort(_("--base is incompatible with specifying "
1410 raise error.Abort(_("--base is incompatible with specifying "
1411 "a destination"))
1411 "a destination"))
1412 common = [repo.lookup(rev) for rev in base]
1412 common = [repo.lookup(rev) for rev in base]
1413 heads = revs and map(repo.lookup, revs) or revs
1413 heads = revs and map(repo.lookup, revs) or revs
1414 cg = changegroup.getchangegroup(repo, 'bundle', heads=heads,
1414 cg = changegroup.getchangegroup(repo, 'bundle', heads=heads,
1415 common=common, bundlecaps=bundlecaps,
1415 common=common, bundlecaps=bundlecaps,
1416 version=cgversion)
1416 version=cgversion)
1417 outgoing = None
1417 outgoing = None
1418 else:
1418 else:
1419 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1419 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1420 dest, branches = hg.parseurl(dest, opts.get('branch'))
1420 dest, branches = hg.parseurl(dest, opts.get('branch'))
1421 other = hg.peer(repo, opts, dest)
1421 other = hg.peer(repo, opts, dest)
1422 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1422 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1423 heads = revs and map(repo.lookup, revs) or revs
1423 heads = revs and map(repo.lookup, revs) or revs
1424 outgoing = discovery.findcommonoutgoing(repo, other,
1424 outgoing = discovery.findcommonoutgoing(repo, other,
1425 onlyheads=heads,
1425 onlyheads=heads,
1426 force=opts.get('force'),
1426 force=opts.get('force'),
1427 portable=True)
1427 portable=True)
1428 cg = changegroup.getlocalchangegroup(repo, 'bundle', outgoing,
1428 cg = changegroup.getlocalchangegroup(repo, 'bundle', outgoing,
1429 bundlecaps, version=cgversion)
1429 bundlecaps, version=cgversion)
1430 if not cg:
1430 if not cg:
1431 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1431 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1432 return 1
1432 return 1
1433
1433
1434 if cgversion == '01': #bundle1
1434 if cgversion == '01': #bundle1
1435 if bcompression is None:
1435 if bcompression is None:
1436 bcompression = 'UN'
1436 bcompression = 'UN'
1437 bversion = 'HG10' + bcompression
1437 bversion = 'HG10' + bcompression
1438 bcompression = None
1438 bcompression = None
1439 else:
1439 else:
1440 assert cgversion == '02'
1440 assert cgversion == '02'
1441 bversion = 'HG20'
1441 bversion = 'HG20'
1442
1442
1443 bundle2.writebundle(ui, cg, fname, bversion, compression=bcompression)
1443 bundle2.writebundle(ui, cg, fname, bversion, compression=bcompression)
1444
1444
1445 @command('cat',
1445 @command('cat',
1446 [('o', 'output', '',
1446 [('o', 'output', '',
1447 _('print output to file with formatted name'), _('FORMAT')),
1447 _('print output to file with formatted name'), _('FORMAT')),
1448 ('r', 'rev', '', _('print the given revision'), _('REV')),
1448 ('r', 'rev', '', _('print the given revision'), _('REV')),
1449 ('', 'decode', None, _('apply any matching decode filter')),
1449 ('', 'decode', None, _('apply any matching decode filter')),
1450 ] + walkopts,
1450 ] + walkopts,
1451 _('[OPTION]... FILE...'),
1451 _('[OPTION]... FILE...'),
1452 inferrepo=True)
1452 inferrepo=True)
1453 def cat(ui, repo, file1, *pats, **opts):
1453 def cat(ui, repo, file1, *pats, **opts):
1454 """output the current or given revision of files
1454 """output the current or given revision of files
1455
1455
1456 Print the specified files as they were at the given revision. If
1456 Print the specified files as they were at the given revision. If
1457 no revision is given, the parent of the working directory is used.
1457 no revision is given, the parent of the working directory is used.
1458
1458
1459 Output may be to a file, in which case the name of the file is
1459 Output may be to a file, in which case the name of the file is
1460 given using a format string. The formatting rules as follows:
1460 given using a format string. The formatting rules as follows:
1461
1461
1462 :``%%``: literal "%" character
1462 :``%%``: literal "%" character
1463 :``%s``: basename of file being printed
1463 :``%s``: basename of file being printed
1464 :``%d``: dirname of file being printed, or '.' if in repository root
1464 :``%d``: dirname of file being printed, or '.' if in repository root
1465 :``%p``: root-relative path name of file being printed
1465 :``%p``: root-relative path name of file being printed
1466 :``%H``: changeset hash (40 hexadecimal digits)
1466 :``%H``: changeset hash (40 hexadecimal digits)
1467 :``%R``: changeset revision number
1467 :``%R``: changeset revision number
1468 :``%h``: short-form changeset hash (12 hexadecimal digits)
1468 :``%h``: short-form changeset hash (12 hexadecimal digits)
1469 :``%r``: zero-padded changeset revision number
1469 :``%r``: zero-padded changeset revision number
1470 :``%b``: basename of the exporting repository
1470 :``%b``: basename of the exporting repository
1471
1471
1472 Returns 0 on success.
1472 Returns 0 on success.
1473 """
1473 """
1474 ctx = scmutil.revsingle(repo, opts.get('rev'))
1474 ctx = scmutil.revsingle(repo, opts.get('rev'))
1475 m = scmutil.match(ctx, (file1,) + pats, opts)
1475 m = scmutil.match(ctx, (file1,) + pats, opts)
1476
1476
1477 return cmdutil.cat(ui, repo, ctx, m, '', **opts)
1477 return cmdutil.cat(ui, repo, ctx, m, '', **opts)
1478
1478
1479 @command('^clone',
1479 @command('^clone',
1480 [('U', 'noupdate', None, _('the clone will include an empty working '
1480 [('U', 'noupdate', None, _('the clone will include an empty working '
1481 'directory (only a repository)')),
1481 'directory (only a repository)')),
1482 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1482 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1483 _('REV')),
1483 _('REV')),
1484 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1484 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1485 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1485 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1486 ('', 'pull', None, _('use pull protocol to copy metadata')),
1486 ('', 'pull', None, _('use pull protocol to copy metadata')),
1487 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1487 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1488 ] + remoteopts,
1488 ] + remoteopts,
1489 _('[OPTION]... SOURCE [DEST]'),
1489 _('[OPTION]... SOURCE [DEST]'),
1490 norepo=True)
1490 norepo=True)
1491 def clone(ui, source, dest=None, **opts):
1491 def clone(ui, source, dest=None, **opts):
1492 """make a copy of an existing repository
1492 """make a copy of an existing repository
1493
1493
1494 Create a copy of an existing repository in a new directory.
1494 Create a copy of an existing repository in a new directory.
1495
1495
1496 If no destination directory name is specified, it defaults to the
1496 If no destination directory name is specified, it defaults to the
1497 basename of the source.
1497 basename of the source.
1498
1498
1499 The location of the source is added to the new repository's
1499 The location of the source is added to the new repository's
1500 ``.hg/hgrc`` file, as the default to be used for future pulls.
1500 ``.hg/hgrc`` file, as the default to be used for future pulls.
1501
1501
1502 Only local paths and ``ssh://`` URLs are supported as
1502 Only local paths and ``ssh://`` URLs are supported as
1503 destinations. For ``ssh://`` destinations, no working directory or
1503 destinations. For ``ssh://`` destinations, no working directory or
1504 ``.hg/hgrc`` will be created on the remote side.
1504 ``.hg/hgrc`` will be created on the remote side.
1505
1505
1506 If the source repository has a bookmark called '@' set, that
1506 If the source repository has a bookmark called '@' set, that
1507 revision will be checked out in the new repository by default.
1507 revision will be checked out in the new repository by default.
1508
1508
1509 To check out a particular version, use -u/--update, or
1509 To check out a particular version, use -u/--update, or
1510 -U/--noupdate to create a clone with no working directory.
1510 -U/--noupdate to create a clone with no working directory.
1511
1511
1512 To pull only a subset of changesets, specify one or more revisions
1512 To pull only a subset of changesets, specify one or more revisions
1513 identifiers with -r/--rev or branches with -b/--branch. The
1513 identifiers with -r/--rev or branches with -b/--branch. The
1514 resulting clone will contain only the specified changesets and
1514 resulting clone will contain only the specified changesets and
1515 their ancestors. These options (or 'clone src#rev dest') imply
1515 their ancestors. These options (or 'clone src#rev dest') imply
1516 --pull, even for local source repositories.
1516 --pull, even for local source repositories.
1517
1517
1518 .. note::
1518 .. note::
1519
1519
1520 Specifying a tag will include the tagged changeset but not the
1520 Specifying a tag will include the tagged changeset but not the
1521 changeset containing the tag.
1521 changeset containing the tag.
1522
1522
1523 .. container:: verbose
1523 .. container:: verbose
1524
1524
1525 For efficiency, hardlinks are used for cloning whenever the
1525 For efficiency, hardlinks are used for cloning whenever the
1526 source and destination are on the same filesystem (note this
1526 source and destination are on the same filesystem (note this
1527 applies only to the repository data, not to the working
1527 applies only to the repository data, not to the working
1528 directory). Some filesystems, such as AFS, implement hardlinking
1528 directory). Some filesystems, such as AFS, implement hardlinking
1529 incorrectly, but do not report errors. In these cases, use the
1529 incorrectly, but do not report errors. In these cases, use the
1530 --pull option to avoid hardlinking.
1530 --pull option to avoid hardlinking.
1531
1531
1532 In some cases, you can clone repositories and the working
1532 In some cases, you can clone repositories and the working
1533 directory using full hardlinks with ::
1533 directory using full hardlinks with ::
1534
1534
1535 $ cp -al REPO REPOCLONE
1535 $ cp -al REPO REPOCLONE
1536
1536
1537 This is the fastest way to clone, but it is not always safe. The
1537 This is the fastest way to clone, but it is not always safe. The
1538 operation is not atomic (making sure REPO is not modified during
1538 operation is not atomic (making sure REPO is not modified during
1539 the operation is up to you) and you have to make sure your
1539 the operation is up to you) and you have to make sure your
1540 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1540 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1541 so). Also, this is not compatible with certain extensions that
1541 so). Also, this is not compatible with certain extensions that
1542 place their metadata under the .hg directory, such as mq.
1542 place their metadata under the .hg directory, such as mq.
1543
1543
1544 Mercurial will update the working directory to the first applicable
1544 Mercurial will update the working directory to the first applicable
1545 revision from this list:
1545 revision from this list:
1546
1546
1547 a) null if -U or the source repository has no changesets
1547 a) null if -U or the source repository has no changesets
1548 b) if -u . and the source repository is local, the first parent of
1548 b) if -u . and the source repository is local, the first parent of
1549 the source repository's working directory
1549 the source repository's working directory
1550 c) the changeset specified with -u (if a branch name, this means the
1550 c) the changeset specified with -u (if a branch name, this means the
1551 latest head of that branch)
1551 latest head of that branch)
1552 d) the changeset specified with -r
1552 d) the changeset specified with -r
1553 e) the tipmost head specified with -b
1553 e) the tipmost head specified with -b
1554 f) the tipmost head specified with the url#branch source syntax
1554 f) the tipmost head specified with the url#branch source syntax
1555 g) the revision marked with the '@' bookmark, if present
1555 g) the revision marked with the '@' bookmark, if present
1556 h) the tipmost head of the default branch
1556 h) the tipmost head of the default branch
1557 i) tip
1557 i) tip
1558
1558
1559 When cloning from servers that support it, Mercurial may fetch
1559 When cloning from servers that support it, Mercurial may fetch
1560 pre-generated data from a server-advertised URL. When this is done,
1560 pre-generated data from a server-advertised URL. When this is done,
1561 hooks operating on incoming changesets and changegroups may fire twice,
1561 hooks operating on incoming changesets and changegroups may fire twice,
1562 once for the bundle fetched from the URL and another for any additional
1562 once for the bundle fetched from the URL and another for any additional
1563 data not fetched from this URL. In addition, if an error occurs, the
1563 data not fetched from this URL. In addition, if an error occurs, the
1564 repository may be rolled back to a partial clone. This behavior may
1564 repository may be rolled back to a partial clone. This behavior may
1565 change in future releases. See :hg:`help -e clonebundles` for more.
1565 change in future releases. See :hg:`help -e clonebundles` for more.
1566
1566
1567 Examples:
1567 Examples:
1568
1568
1569 - clone a remote repository to a new directory named hg/::
1569 - clone a remote repository to a new directory named hg/::
1570
1570
1571 hg clone http://selenic.com/hg
1571 hg clone http://selenic.com/hg
1572
1572
1573 - create a lightweight local clone::
1573 - create a lightweight local clone::
1574
1574
1575 hg clone project/ project-feature/
1575 hg clone project/ project-feature/
1576
1576
1577 - clone from an absolute path on an ssh server (note double-slash)::
1577 - clone from an absolute path on an ssh server (note double-slash)::
1578
1578
1579 hg clone ssh://user@server//home/projects/alpha/
1579 hg clone ssh://user@server//home/projects/alpha/
1580
1580
1581 - do a high-speed clone over a LAN while checking out a
1581 - do a high-speed clone over a LAN while checking out a
1582 specified version::
1582 specified version::
1583
1583
1584 hg clone --uncompressed http://server/repo -u 1.5
1584 hg clone --uncompressed http://server/repo -u 1.5
1585
1585
1586 - create a repository without changesets after a particular revision::
1586 - create a repository without changesets after a particular revision::
1587
1587
1588 hg clone -r 04e544 experimental/ good/
1588 hg clone -r 04e544 experimental/ good/
1589
1589
1590 - clone (and track) a particular named branch::
1590 - clone (and track) a particular named branch::
1591
1591
1592 hg clone http://selenic.com/hg#stable
1592 hg clone http://selenic.com/hg#stable
1593
1593
1594 See :hg:`help urls` for details on specifying URLs.
1594 See :hg:`help urls` for details on specifying URLs.
1595
1595
1596 Returns 0 on success.
1596 Returns 0 on success.
1597 """
1597 """
1598 if opts.get('noupdate') and opts.get('updaterev'):
1598 if opts.get('noupdate') and opts.get('updaterev'):
1599 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1599 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1600
1600
1601 r = hg.clone(ui, opts, source, dest,
1601 r = hg.clone(ui, opts, source, dest,
1602 pull=opts.get('pull'),
1602 pull=opts.get('pull'),
1603 stream=opts.get('uncompressed'),
1603 stream=opts.get('uncompressed'),
1604 rev=opts.get('rev'),
1604 rev=opts.get('rev'),
1605 update=opts.get('updaterev') or not opts.get('noupdate'),
1605 update=opts.get('updaterev') or not opts.get('noupdate'),
1606 branch=opts.get('branch'),
1606 branch=opts.get('branch'),
1607 shareopts=opts.get('shareopts'))
1607 shareopts=opts.get('shareopts'))
1608
1608
1609 return r is None
1609 return r is None
1610
1610
1611 @command('^commit|ci',
1611 @command('^commit|ci',
1612 [('A', 'addremove', None,
1612 [('A', 'addremove', None,
1613 _('mark new/missing files as added/removed before committing')),
1613 _('mark new/missing files as added/removed before committing')),
1614 ('', 'close-branch', None,
1614 ('', 'close-branch', None,
1615 _('mark a branch head as closed')),
1615 _('mark a branch head as closed')),
1616 ('', 'amend', None, _('amend the parent of the working directory')),
1616 ('', 'amend', None, _('amend the parent of the working directory')),
1617 ('s', 'secret', None, _('use the secret phase for committing')),
1617 ('s', 'secret', None, _('use the secret phase for committing')),
1618 ('e', 'edit', None, _('invoke editor on commit messages')),
1618 ('e', 'edit', None, _('invoke editor on commit messages')),
1619 ('i', 'interactive', None, _('use interactive mode')),
1619 ('i', 'interactive', None, _('use interactive mode')),
1620 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1620 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1621 _('[OPTION]... [FILE]...'),
1621 _('[OPTION]... [FILE]...'),
1622 inferrepo=True)
1622 inferrepo=True)
1623 def commit(ui, repo, *pats, **opts):
1623 def commit(ui, repo, *pats, **opts):
1624 """commit the specified files or all outstanding changes
1624 """commit the specified files or all outstanding changes
1625
1625
1626 Commit changes to the given files into the repository. Unlike a
1626 Commit changes to the given files into the repository. Unlike a
1627 centralized SCM, this operation is a local operation. See
1627 centralized SCM, this operation is a local operation. See
1628 :hg:`push` for a way to actively distribute your changes.
1628 :hg:`push` for a way to actively distribute your changes.
1629
1629
1630 If a list of files is omitted, all changes reported by :hg:`status`
1630 If a list of files is omitted, all changes reported by :hg:`status`
1631 will be committed.
1631 will be committed.
1632
1632
1633 If you are committing the result of a merge, do not provide any
1633 If you are committing the result of a merge, do not provide any
1634 filenames or -I/-X filters.
1634 filenames or -I/-X filters.
1635
1635
1636 If no commit message is specified, Mercurial starts your
1636 If no commit message is specified, Mercurial starts your
1637 configured editor where you can enter a message. In case your
1637 configured editor where you can enter a message. In case your
1638 commit fails, you will find a backup of your message in
1638 commit fails, you will find a backup of your message in
1639 ``.hg/last-message.txt``.
1639 ``.hg/last-message.txt``.
1640
1640
1641 The --close-branch flag can be used to mark the current branch
1641 The --close-branch flag can be used to mark the current branch
1642 head closed. When all heads of a branch are closed, the branch
1642 head closed. When all heads of a branch are closed, the branch
1643 will be considered closed and no longer listed.
1643 will be considered closed and no longer listed.
1644
1644
1645 The --amend flag can be used to amend the parent of the
1645 The --amend flag can be used to amend the parent of the
1646 working directory with a new commit that contains the changes
1646 working directory with a new commit that contains the changes
1647 in the parent in addition to those currently reported by :hg:`status`,
1647 in the parent in addition to those currently reported by :hg:`status`,
1648 if there are any. The old commit is stored in a backup bundle in
1648 if there are any. The old commit is stored in a backup bundle in
1649 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1649 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1650 on how to restore it).
1650 on how to restore it).
1651
1651
1652 Message, user and date are taken from the amended commit unless
1652 Message, user and date are taken from the amended commit unless
1653 specified. When a message isn't specified on the command line,
1653 specified. When a message isn't specified on the command line,
1654 the editor will open with the message of the amended commit.
1654 the editor will open with the message of the amended commit.
1655
1655
1656 It is not possible to amend public changesets (see :hg:`help phases`)
1656 It is not possible to amend public changesets (see :hg:`help phases`)
1657 or changesets that have children.
1657 or changesets that have children.
1658
1658
1659 See :hg:`help dates` for a list of formats valid for -d/--date.
1659 See :hg:`help dates` for a list of formats valid for -d/--date.
1660
1660
1661 Returns 0 on success, 1 if nothing changed.
1661 Returns 0 on success, 1 if nothing changed.
1662
1662
1663 .. container:: verbose
1663 .. container:: verbose
1664
1664
1665 Examples:
1665 Examples:
1666
1666
1667 - commit all files ending in .py::
1667 - commit all files ending in .py::
1668
1668
1669 hg commit --include "set:**.py"
1669 hg commit --include "set:**.py"
1670
1670
1671 - commit all non-binary files::
1671 - commit all non-binary files::
1672
1672
1673 hg commit --exclude "set:binary()"
1673 hg commit --exclude "set:binary()"
1674
1674
1675 - amend the current commit and set the date to now::
1675 - amend the current commit and set the date to now::
1676
1676
1677 hg commit --amend --date now
1677 hg commit --amend --date now
1678 """
1678 """
1679 wlock = lock = None
1679 wlock = lock = None
1680 try:
1680 try:
1681 wlock = repo.wlock()
1681 wlock = repo.wlock()
1682 lock = repo.lock()
1682 lock = repo.lock()
1683 return _docommit(ui, repo, *pats, **opts)
1683 return _docommit(ui, repo, *pats, **opts)
1684 finally:
1684 finally:
1685 release(lock, wlock)
1685 release(lock, wlock)
1686
1686
1687 def _docommit(ui, repo, *pats, **opts):
1687 def _docommit(ui, repo, *pats, **opts):
1688 if opts.get('interactive'):
1688 if opts.get('interactive'):
1689 opts.pop('interactive')
1689 opts.pop('interactive')
1690 cmdutil.dorecord(ui, repo, commit, None, False,
1690 cmdutil.dorecord(ui, repo, commit, None, False,
1691 cmdutil.recordfilter, *pats, **opts)
1691 cmdutil.recordfilter, *pats, **opts)
1692 return
1692 return
1693
1693
1694 if opts.get('subrepos'):
1694 if opts.get('subrepos'):
1695 if opts.get('amend'):
1695 if opts.get('amend'):
1696 raise error.Abort(_('cannot amend with --subrepos'))
1696 raise error.Abort(_('cannot amend with --subrepos'))
1697 # Let --subrepos on the command line override config setting.
1697 # Let --subrepos on the command line override config setting.
1698 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1698 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1699
1699
1700 cmdutil.checkunfinished(repo, commit=True)
1700 cmdutil.checkunfinished(repo, commit=True)
1701
1701
1702 branch = repo[None].branch()
1702 branch = repo[None].branch()
1703 bheads = repo.branchheads(branch)
1703 bheads = repo.branchheads(branch)
1704
1704
1705 extra = {}
1705 extra = {}
1706 if opts.get('close_branch'):
1706 if opts.get('close_branch'):
1707 extra['close'] = 1
1707 extra['close'] = 1
1708
1708
1709 if not bheads:
1709 if not bheads:
1710 raise error.Abort(_('can only close branch heads'))
1710 raise error.Abort(_('can only close branch heads'))
1711 elif opts.get('amend'):
1711 elif opts.get('amend'):
1712 if repo[None].parents()[0].p1().branch() != branch and \
1712 if repo[None].parents()[0].p1().branch() != branch and \
1713 repo[None].parents()[0].p2().branch() != branch:
1713 repo[None].parents()[0].p2().branch() != branch:
1714 raise error.Abort(_('can only close branch heads'))
1714 raise error.Abort(_('can only close branch heads'))
1715
1715
1716 if opts.get('amend'):
1716 if opts.get('amend'):
1717 if ui.configbool('ui', 'commitsubrepos'):
1717 if ui.configbool('ui', 'commitsubrepos'):
1718 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1718 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1719
1719
1720 old = repo['.']
1720 old = repo['.']
1721 if not old.mutable():
1721 if not old.mutable():
1722 raise error.Abort(_('cannot amend public changesets'))
1722 raise error.Abort(_('cannot amend public changesets'))
1723 if len(repo[None].parents()) > 1:
1723 if len(repo[None].parents()) > 1:
1724 raise error.Abort(_('cannot amend while merging'))
1724 raise error.Abort(_('cannot amend while merging'))
1725 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1725 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1726 if not allowunstable and old.children():
1726 if not allowunstable and old.children():
1727 raise error.Abort(_('cannot amend changeset with children'))
1727 raise error.Abort(_('cannot amend changeset with children'))
1728
1728
1729 # Currently histedit gets confused if an amend happens while histedit
1729 # Currently histedit gets confused if an amend happens while histedit
1730 # is in progress. Since we have a checkunfinished command, we are
1730 # is in progress. Since we have a checkunfinished command, we are
1731 # temporarily honoring it.
1731 # temporarily honoring it.
1732 #
1732 #
1733 # Note: eventually this guard will be removed. Please do not expect
1733 # Note: eventually this guard will be removed. Please do not expect
1734 # this behavior to remain.
1734 # this behavior to remain.
1735 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1735 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1736 cmdutil.checkunfinished(repo)
1736 cmdutil.checkunfinished(repo)
1737
1737
1738 # commitfunc is used only for temporary amend commit by cmdutil.amend
1738 # commitfunc is used only for temporary amend commit by cmdutil.amend
1739 def commitfunc(ui, repo, message, match, opts):
1739 def commitfunc(ui, repo, message, match, opts):
1740 return repo.commit(message,
1740 return repo.commit(message,
1741 opts.get('user') or old.user(),
1741 opts.get('user') or old.user(),
1742 opts.get('date') or old.date(),
1742 opts.get('date') or old.date(),
1743 match,
1743 match,
1744 extra=extra)
1744 extra=extra)
1745
1745
1746 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1746 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1747 if node == old.node():
1747 if node == old.node():
1748 ui.status(_("nothing changed\n"))
1748 ui.status(_("nothing changed\n"))
1749 return 1
1749 return 1
1750 else:
1750 else:
1751 def commitfunc(ui, repo, message, match, opts):
1751 def commitfunc(ui, repo, message, match, opts):
1752 backup = ui.backupconfig('phases', 'new-commit')
1752 backup = ui.backupconfig('phases', 'new-commit')
1753 baseui = repo.baseui
1753 baseui = repo.baseui
1754 basebackup = baseui.backupconfig('phases', 'new-commit')
1754 basebackup = baseui.backupconfig('phases', 'new-commit')
1755 try:
1755 try:
1756 if opts.get('secret'):
1756 if opts.get('secret'):
1757 ui.setconfig('phases', 'new-commit', 'secret', 'commit')
1757 ui.setconfig('phases', 'new-commit', 'secret', 'commit')
1758 # Propagate to subrepos
1758 # Propagate to subrepos
1759 baseui.setconfig('phases', 'new-commit', 'secret', 'commit')
1759 baseui.setconfig('phases', 'new-commit', 'secret', 'commit')
1760
1760
1761 editform = cmdutil.mergeeditform(repo[None], 'commit.normal')
1761 editform = cmdutil.mergeeditform(repo[None], 'commit.normal')
1762 editor = cmdutil.getcommiteditor(editform=editform, **opts)
1762 editor = cmdutil.getcommiteditor(editform=editform, **opts)
1763 return repo.commit(message, opts.get('user'), opts.get('date'),
1763 return repo.commit(message, opts.get('user'), opts.get('date'),
1764 match,
1764 match,
1765 editor=editor,
1765 editor=editor,
1766 extra=extra)
1766 extra=extra)
1767 finally:
1767 finally:
1768 ui.restoreconfig(backup)
1768 ui.restoreconfig(backup)
1769 repo.baseui.restoreconfig(basebackup)
1769 repo.baseui.restoreconfig(basebackup)
1770
1770
1771
1771
1772 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1772 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1773
1773
1774 if not node:
1774 if not node:
1775 stat = cmdutil.postcommitstatus(repo, pats, opts)
1775 stat = cmdutil.postcommitstatus(repo, pats, opts)
1776 if stat[3]:
1776 if stat[3]:
1777 ui.status(_("nothing changed (%d missing files, see "
1777 ui.status(_("nothing changed (%d missing files, see "
1778 "'hg status')\n") % len(stat[3]))
1778 "'hg status')\n") % len(stat[3]))
1779 else:
1779 else:
1780 ui.status(_("nothing changed\n"))
1780 ui.status(_("nothing changed\n"))
1781 return 1
1781 return 1
1782
1782
1783 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1783 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1784
1784
1785 @command('config|showconfig|debugconfig',
1785 @command('config|showconfig|debugconfig',
1786 [('u', 'untrusted', None, _('show untrusted configuration options')),
1786 [('u', 'untrusted', None, _('show untrusted configuration options')),
1787 ('e', 'edit', None, _('edit user config')),
1787 ('e', 'edit', None, _('edit user config')),
1788 ('l', 'local', None, _('edit repository config')),
1788 ('l', 'local', None, _('edit repository config')),
1789 ('g', 'global', None, _('edit global config'))],
1789 ('g', 'global', None, _('edit global config'))],
1790 _('[-u] [NAME]...'),
1790 _('[-u] [NAME]...'),
1791 optionalrepo=True)
1791 optionalrepo=True)
1792 def config(ui, repo, *values, **opts):
1792 def config(ui, repo, *values, **opts):
1793 """show combined config settings from all hgrc files
1793 """show combined config settings from all hgrc files
1794
1794
1795 With no arguments, print names and values of all config items.
1795 With no arguments, print names and values of all config items.
1796
1796
1797 With one argument of the form section.name, print just the value
1797 With one argument of the form section.name, print just the value
1798 of that config item.
1798 of that config item.
1799
1799
1800 With multiple arguments, print names and values of all config
1800 With multiple arguments, print names and values of all config
1801 items with matching section names.
1801 items with matching section names.
1802
1802
1803 With --edit, start an editor on the user-level config file. With
1803 With --edit, start an editor on the user-level config file. With
1804 --global, edit the system-wide config file. With --local, edit the
1804 --global, edit the system-wide config file. With --local, edit the
1805 repository-level config file.
1805 repository-level config file.
1806
1806
1807 With --debug, the source (filename and line number) is printed
1807 With --debug, the source (filename and line number) is printed
1808 for each config item.
1808 for each config item.
1809
1809
1810 See :hg:`help config` for more information about config files.
1810 See :hg:`help config` for more information about config files.
1811
1811
1812 Returns 0 on success, 1 if NAME does not exist.
1812 Returns 0 on success, 1 if NAME does not exist.
1813
1813
1814 """
1814 """
1815
1815
1816 if opts.get('edit') or opts.get('local') or opts.get('global'):
1816 if opts.get('edit') or opts.get('local') or opts.get('global'):
1817 if opts.get('local') and opts.get('global'):
1817 if opts.get('local') and opts.get('global'):
1818 raise error.Abort(_("can't use --local and --global together"))
1818 raise error.Abort(_("can't use --local and --global together"))
1819
1819
1820 if opts.get('local'):
1820 if opts.get('local'):
1821 if not repo:
1821 if not repo:
1822 raise error.Abort(_("can't use --local outside a repository"))
1822 raise error.Abort(_("can't use --local outside a repository"))
1823 paths = [repo.join('hgrc')]
1823 paths = [repo.join('hgrc')]
1824 elif opts.get('global'):
1824 elif opts.get('global'):
1825 paths = scmutil.systemrcpath()
1825 paths = scmutil.systemrcpath()
1826 else:
1826 else:
1827 paths = scmutil.userrcpath()
1827 paths = scmutil.userrcpath()
1828
1828
1829 for f in paths:
1829 for f in paths:
1830 if os.path.exists(f):
1830 if os.path.exists(f):
1831 break
1831 break
1832 else:
1832 else:
1833 if opts.get('global'):
1833 if opts.get('global'):
1834 samplehgrc = uimod.samplehgrcs['global']
1834 samplehgrc = uimod.samplehgrcs['global']
1835 elif opts.get('local'):
1835 elif opts.get('local'):
1836 samplehgrc = uimod.samplehgrcs['local']
1836 samplehgrc = uimod.samplehgrcs['local']
1837 else:
1837 else:
1838 samplehgrc = uimod.samplehgrcs['user']
1838 samplehgrc = uimod.samplehgrcs['user']
1839
1839
1840 f = paths[0]
1840 f = paths[0]
1841 fp = open(f, "w")
1841 fp = open(f, "w")
1842 fp.write(samplehgrc)
1842 fp.write(samplehgrc)
1843 fp.close()
1843 fp.close()
1844
1844
1845 editor = ui.geteditor()
1845 editor = ui.geteditor()
1846 ui.system("%s \"%s\"" % (editor, f),
1846 ui.system("%s \"%s\"" % (editor, f),
1847 onerr=error.Abort, errprefix=_("edit failed"))
1847 onerr=error.Abort, errprefix=_("edit failed"))
1848 return
1848 return
1849
1849
1850 for f in scmutil.rcpath():
1850 for f in scmutil.rcpath():
1851 ui.debug('read config from: %s\n' % f)
1851 ui.debug('read config from: %s\n' % f)
1852 untrusted = bool(opts.get('untrusted'))
1852 untrusted = bool(opts.get('untrusted'))
1853 if values:
1853 if values:
1854 sections = [v for v in values if '.' not in v]
1854 sections = [v for v in values if '.' not in v]
1855 items = [v for v in values if '.' in v]
1855 items = [v for v in values if '.' in v]
1856 if len(items) > 1 or items and sections:
1856 if len(items) > 1 or items and sections:
1857 raise error.Abort(_('only one config item permitted'))
1857 raise error.Abort(_('only one config item permitted'))
1858 matched = False
1858 matched = False
1859 for section, name, value in ui.walkconfig(untrusted=untrusted):
1859 for section, name, value in ui.walkconfig(untrusted=untrusted):
1860 value = str(value).replace('\n', '\\n')
1860 value = str(value).replace('\n', '\\n')
1861 sectname = section + '.' + name
1861 sectname = section + '.' + name
1862 if values:
1862 if values:
1863 for v in values:
1863 for v in values:
1864 if v == section:
1864 if v == section:
1865 ui.debug('%s: ' %
1865 ui.debug('%s: ' %
1866 ui.configsource(section, name, untrusted))
1866 ui.configsource(section, name, untrusted))
1867 ui.write('%s=%s\n' % (sectname, value))
1867 ui.write('%s=%s\n' % (sectname, value))
1868 matched = True
1868 matched = True
1869 elif v == sectname:
1869 elif v == sectname:
1870 ui.debug('%s: ' %
1870 ui.debug('%s: ' %
1871 ui.configsource(section, name, untrusted))
1871 ui.configsource(section, name, untrusted))
1872 ui.write(value, '\n')
1872 ui.write(value, '\n')
1873 matched = True
1873 matched = True
1874 else:
1874 else:
1875 ui.debug('%s: ' %
1875 ui.debug('%s: ' %
1876 ui.configsource(section, name, untrusted))
1876 ui.configsource(section, name, untrusted))
1877 ui.write('%s=%s\n' % (sectname, value))
1877 ui.write('%s=%s\n' % (sectname, value))
1878 matched = True
1878 matched = True
1879 if matched:
1879 if matched:
1880 return 0
1880 return 0
1881 return 1
1881 return 1
1882
1882
1883 @command('copy|cp',
1883 @command('copy|cp',
1884 [('A', 'after', None, _('record a copy that has already occurred')),
1884 [('A', 'after', None, _('record a copy that has already occurred')),
1885 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1885 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1886 ] + walkopts + dryrunopts,
1886 ] + walkopts + dryrunopts,
1887 _('[OPTION]... [SOURCE]... DEST'))
1887 _('[OPTION]... [SOURCE]... DEST'))
1888 def copy(ui, repo, *pats, **opts):
1888 def copy(ui, repo, *pats, **opts):
1889 """mark files as copied for the next commit
1889 """mark files as copied for the next commit
1890
1890
1891 Mark dest as having copies of source files. If dest is a
1891 Mark dest as having copies of source files. If dest is a
1892 directory, copies are put in that directory. If dest is a file,
1892 directory, copies are put in that directory. If dest is a file,
1893 the source must be a single file.
1893 the source must be a single file.
1894
1894
1895 By default, this command copies the contents of files as they
1895 By default, this command copies the contents of files as they
1896 exist in the working directory. If invoked with -A/--after, the
1896 exist in the working directory. If invoked with -A/--after, the
1897 operation is recorded, but no copying is performed.
1897 operation is recorded, but no copying is performed.
1898
1898
1899 This command takes effect with the next commit. To undo a copy
1899 This command takes effect with the next commit. To undo a copy
1900 before that, see :hg:`revert`.
1900 before that, see :hg:`revert`.
1901
1901
1902 Returns 0 on success, 1 if errors are encountered.
1902 Returns 0 on success, 1 if errors are encountered.
1903 """
1903 """
1904 with repo.wlock(False):
1904 with repo.wlock(False):
1905 return cmdutil.copy(ui, repo, pats, opts)
1905 return cmdutil.copy(ui, repo, pats, opts)
1906
1906
1907 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
1907 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
1908 def debugancestor(ui, repo, *args):
1908 def debugancestor(ui, repo, *args):
1909 """find the ancestor revision of two revisions in a given index"""
1909 """find the ancestor revision of two revisions in a given index"""
1910 if len(args) == 3:
1910 if len(args) == 3:
1911 index, rev1, rev2 = args
1911 index, rev1, rev2 = args
1912 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1912 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1913 lookup = r.lookup
1913 lookup = r.lookup
1914 elif len(args) == 2:
1914 elif len(args) == 2:
1915 if not repo:
1915 if not repo:
1916 raise error.Abort(_("there is no Mercurial repository here "
1916 raise error.Abort(_("there is no Mercurial repository here "
1917 "(.hg not found)"))
1917 "(.hg not found)"))
1918 rev1, rev2 = args
1918 rev1, rev2 = args
1919 r = repo.changelog
1919 r = repo.changelog
1920 lookup = repo.lookup
1920 lookup = repo.lookup
1921 else:
1921 else:
1922 raise error.Abort(_('either two or three arguments required'))
1922 raise error.Abort(_('either two or three arguments required'))
1923 a = r.ancestor(lookup(rev1), lookup(rev2))
1923 a = r.ancestor(lookup(rev1), lookup(rev2))
1924 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1924 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1925
1925
1926 @command('debugbuilddag',
1926 @command('debugbuilddag',
1927 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1927 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1928 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1928 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1929 ('n', 'new-file', None, _('add new file at each rev'))],
1929 ('n', 'new-file', None, _('add new file at each rev'))],
1930 _('[OPTION]... [TEXT]'))
1930 _('[OPTION]... [TEXT]'))
1931 def debugbuilddag(ui, repo, text=None,
1931 def debugbuilddag(ui, repo, text=None,
1932 mergeable_file=False,
1932 mergeable_file=False,
1933 overwritten_file=False,
1933 overwritten_file=False,
1934 new_file=False):
1934 new_file=False):
1935 """builds a repo with a given DAG from scratch in the current empty repo
1935 """builds a repo with a given DAG from scratch in the current empty repo
1936
1936
1937 The description of the DAG is read from stdin if not given on the
1937 The description of the DAG is read from stdin if not given on the
1938 command line.
1938 command line.
1939
1939
1940 Elements:
1940 Elements:
1941
1941
1942 - "+n" is a linear run of n nodes based on the current default parent
1942 - "+n" is a linear run of n nodes based on the current default parent
1943 - "." is a single node based on the current default parent
1943 - "." is a single node based on the current default parent
1944 - "$" resets the default parent to null (implied at the start);
1944 - "$" resets the default parent to null (implied at the start);
1945 otherwise the default parent is always the last node created
1945 otherwise the default parent is always the last node created
1946 - "<p" sets the default parent to the backref p
1946 - "<p" sets the default parent to the backref p
1947 - "*p" is a fork at parent p, which is a backref
1947 - "*p" is a fork at parent p, which is a backref
1948 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1948 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1949 - "/p2" is a merge of the preceding node and p2
1949 - "/p2" is a merge of the preceding node and p2
1950 - ":tag" defines a local tag for the preceding node
1950 - ":tag" defines a local tag for the preceding node
1951 - "@branch" sets the named branch for subsequent nodes
1951 - "@branch" sets the named branch for subsequent nodes
1952 - "#...\\n" is a comment up to the end of the line
1952 - "#...\\n" is a comment up to the end of the line
1953
1953
1954 Whitespace between the above elements is ignored.
1954 Whitespace between the above elements is ignored.
1955
1955
1956 A backref is either
1956 A backref is either
1957
1957
1958 - a number n, which references the node curr-n, where curr is the current
1958 - a number n, which references the node curr-n, where curr is the current
1959 node, or
1959 node, or
1960 - the name of a local tag you placed earlier using ":tag", or
1960 - the name of a local tag you placed earlier using ":tag", or
1961 - empty to denote the default parent.
1961 - empty to denote the default parent.
1962
1962
1963 All string valued-elements are either strictly alphanumeric, or must
1963 All string valued-elements are either strictly alphanumeric, or must
1964 be enclosed in double quotes ("..."), with "\\" as escape character.
1964 be enclosed in double quotes ("..."), with "\\" as escape character.
1965 """
1965 """
1966
1966
1967 if text is None:
1967 if text is None:
1968 ui.status(_("reading DAG from stdin\n"))
1968 ui.status(_("reading DAG from stdin\n"))
1969 text = ui.fin.read()
1969 text = ui.fin.read()
1970
1970
1971 cl = repo.changelog
1971 cl = repo.changelog
1972 if len(cl) > 0:
1972 if len(cl) > 0:
1973 raise error.Abort(_('repository is not empty'))
1973 raise error.Abort(_('repository is not empty'))
1974
1974
1975 # determine number of revs in DAG
1975 # determine number of revs in DAG
1976 total = 0
1976 total = 0
1977 for type, data in dagparser.parsedag(text):
1977 for type, data in dagparser.parsedag(text):
1978 if type == 'n':
1978 if type == 'n':
1979 total += 1
1979 total += 1
1980
1980
1981 if mergeable_file:
1981 if mergeable_file:
1982 linesperrev = 2
1982 linesperrev = 2
1983 # make a file with k lines per rev
1983 # make a file with k lines per rev
1984 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1984 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1985 initialmergedlines.append("")
1985 initialmergedlines.append("")
1986
1986
1987 tags = []
1987 tags = []
1988
1988
1989 lock = tr = None
1989 lock = tr = None
1990 try:
1990 try:
1991 lock = repo.lock()
1991 lock = repo.lock()
1992 tr = repo.transaction("builddag")
1992 tr = repo.transaction("builddag")
1993
1993
1994 at = -1
1994 at = -1
1995 atbranch = 'default'
1995 atbranch = 'default'
1996 nodeids = []
1996 nodeids = []
1997 id = 0
1997 id = 0
1998 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1998 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1999 for type, data in dagparser.parsedag(text):
1999 for type, data in dagparser.parsedag(text):
2000 if type == 'n':
2000 if type == 'n':
2001 ui.note(('node %s\n' % str(data)))
2001 ui.note(('node %s\n' % str(data)))
2002 id, ps = data
2002 id, ps = data
2003
2003
2004 files = []
2004 files = []
2005 fctxs = {}
2005 fctxs = {}
2006
2006
2007 p2 = None
2007 p2 = None
2008 if mergeable_file:
2008 if mergeable_file:
2009 fn = "mf"
2009 fn = "mf"
2010 p1 = repo[ps[0]]
2010 p1 = repo[ps[0]]
2011 if len(ps) > 1:
2011 if len(ps) > 1:
2012 p2 = repo[ps[1]]
2012 p2 = repo[ps[1]]
2013 pa = p1.ancestor(p2)
2013 pa = p1.ancestor(p2)
2014 base, local, other = [x[fn].data() for x in (pa, p1,
2014 base, local, other = [x[fn].data() for x in (pa, p1,
2015 p2)]
2015 p2)]
2016 m3 = simplemerge.Merge3Text(base, local, other)
2016 m3 = simplemerge.Merge3Text(base, local, other)
2017 ml = [l.strip() for l in m3.merge_lines()]
2017 ml = [l.strip() for l in m3.merge_lines()]
2018 ml.append("")
2018 ml.append("")
2019 elif at > 0:
2019 elif at > 0:
2020 ml = p1[fn].data().split("\n")
2020 ml = p1[fn].data().split("\n")
2021 else:
2021 else:
2022 ml = initialmergedlines
2022 ml = initialmergedlines
2023 ml[id * linesperrev] += " r%i" % id
2023 ml[id * linesperrev] += " r%i" % id
2024 mergedtext = "\n".join(ml)
2024 mergedtext = "\n".join(ml)
2025 files.append(fn)
2025 files.append(fn)
2026 fctxs[fn] = context.memfilectx(repo, fn, mergedtext)
2026 fctxs[fn] = context.memfilectx(repo, fn, mergedtext)
2027
2027
2028 if overwritten_file:
2028 if overwritten_file:
2029 fn = "of"
2029 fn = "of"
2030 files.append(fn)
2030 files.append(fn)
2031 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
2031 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
2032
2032
2033 if new_file:
2033 if new_file:
2034 fn = "nf%i" % id
2034 fn = "nf%i" % id
2035 files.append(fn)
2035 files.append(fn)
2036 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
2036 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
2037 if len(ps) > 1:
2037 if len(ps) > 1:
2038 if not p2:
2038 if not p2:
2039 p2 = repo[ps[1]]
2039 p2 = repo[ps[1]]
2040 for fn in p2:
2040 for fn in p2:
2041 if fn.startswith("nf"):
2041 if fn.startswith("nf"):
2042 files.append(fn)
2042 files.append(fn)
2043 fctxs[fn] = p2[fn]
2043 fctxs[fn] = p2[fn]
2044
2044
2045 def fctxfn(repo, cx, path):
2045 def fctxfn(repo, cx, path):
2046 return fctxs.get(path)
2046 return fctxs.get(path)
2047
2047
2048 if len(ps) == 0 or ps[0] < 0:
2048 if len(ps) == 0 or ps[0] < 0:
2049 pars = [None, None]
2049 pars = [None, None]
2050 elif len(ps) == 1:
2050 elif len(ps) == 1:
2051 pars = [nodeids[ps[0]], None]
2051 pars = [nodeids[ps[0]], None]
2052 else:
2052 else:
2053 pars = [nodeids[p] for p in ps]
2053 pars = [nodeids[p] for p in ps]
2054 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
2054 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
2055 date=(id, 0),
2055 date=(id, 0),
2056 user="debugbuilddag",
2056 user="debugbuilddag",
2057 extra={'branch': atbranch})
2057 extra={'branch': atbranch})
2058 nodeid = repo.commitctx(cx)
2058 nodeid = repo.commitctx(cx)
2059 nodeids.append(nodeid)
2059 nodeids.append(nodeid)
2060 at = id
2060 at = id
2061 elif type == 'l':
2061 elif type == 'l':
2062 id, name = data
2062 id, name = data
2063 ui.note(('tag %s\n' % name))
2063 ui.note(('tag %s\n' % name))
2064 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
2064 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
2065 elif type == 'a':
2065 elif type == 'a':
2066 ui.note(('branch %s\n' % data))
2066 ui.note(('branch %s\n' % data))
2067 atbranch = data
2067 atbranch = data
2068 ui.progress(_('building'), id, unit=_('revisions'), total=total)
2068 ui.progress(_('building'), id, unit=_('revisions'), total=total)
2069 tr.close()
2069 tr.close()
2070
2070
2071 if tags:
2071 if tags:
2072 repo.vfs.write("localtags", "".join(tags))
2072 repo.vfs.write("localtags", "".join(tags))
2073 finally:
2073 finally:
2074 ui.progress(_('building'), None)
2074 ui.progress(_('building'), None)
2075 release(tr, lock)
2075 release(tr, lock)
2076
2076
2077 @command('debugbundle',
2077 @command('debugbundle',
2078 [('a', 'all', None, _('show all details')),
2078 [('a', 'all', None, _('show all details')),
2079 ('', 'spec', None, _('print the bundlespec of the bundle'))],
2079 ('', 'spec', None, _('print the bundlespec of the bundle'))],
2080 _('FILE'),
2080 _('FILE'),
2081 norepo=True)
2081 norepo=True)
2082 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
2082 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
2083 """lists the contents of a bundle"""
2083 """lists the contents of a bundle"""
2084 with hg.openpath(ui, bundlepath) as f:
2084 with hg.openpath(ui, bundlepath) as f:
2085 if spec:
2085 if spec:
2086 spec = exchange.getbundlespec(ui, f)
2086 spec = exchange.getbundlespec(ui, f)
2087 ui.write('%s\n' % spec)
2087 ui.write('%s\n' % spec)
2088 return
2088 return
2089
2089
2090 gen = exchange.readbundle(ui, f, bundlepath)
2090 gen = exchange.readbundle(ui, f, bundlepath)
2091 if isinstance(gen, bundle2.unbundle20):
2091 if isinstance(gen, bundle2.unbundle20):
2092 return _debugbundle2(ui, gen, all=all, **opts)
2092 return _debugbundle2(ui, gen, all=all, **opts)
2093 _debugchangegroup(ui, gen, all=all, **opts)
2093 _debugchangegroup(ui, gen, all=all, **opts)
2094
2094
2095 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
2095 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
2096 indent_string = ' ' * indent
2096 indent_string = ' ' * indent
2097 if all:
2097 if all:
2098 ui.write("%sformat: id, p1, p2, cset, delta base, len(delta)\n"
2098 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
2099 % indent_string)
2099 % indent_string)
2100
2100
2101 def showchunks(named):
2101 def showchunks(named):
2102 ui.write("\n%s%s\n" % (indent_string, named))
2102 ui.write("\n%s%s\n" % (indent_string, named))
2103 chain = None
2103 chain = None
2104 while True:
2104 while True:
2105 chunkdata = gen.deltachunk(chain)
2105 chunkdata = gen.deltachunk(chain)
2106 if not chunkdata:
2106 if not chunkdata:
2107 break
2107 break
2108 node = chunkdata['node']
2108 node = chunkdata['node']
2109 p1 = chunkdata['p1']
2109 p1 = chunkdata['p1']
2110 p2 = chunkdata['p2']
2110 p2 = chunkdata['p2']
2111 cs = chunkdata['cs']
2111 cs = chunkdata['cs']
2112 deltabase = chunkdata['deltabase']
2112 deltabase = chunkdata['deltabase']
2113 delta = chunkdata['delta']
2113 delta = chunkdata['delta']
2114 ui.write("%s%s %s %s %s %s %s\n" %
2114 ui.write("%s%s %s %s %s %s %s\n" %
2115 (indent_string, hex(node), hex(p1), hex(p2),
2115 (indent_string, hex(node), hex(p1), hex(p2),
2116 hex(cs), hex(deltabase), len(delta)))
2116 hex(cs), hex(deltabase), len(delta)))
2117 chain = node
2117 chain = node
2118
2118
2119 chunkdata = gen.changelogheader()
2119 chunkdata = gen.changelogheader()
2120 showchunks("changelog")
2120 showchunks("changelog")
2121 chunkdata = gen.manifestheader()
2121 chunkdata = gen.manifestheader()
2122 showchunks("manifest")
2122 showchunks("manifest")
2123 while True:
2123 while True:
2124 chunkdata = gen.filelogheader()
2124 chunkdata = gen.filelogheader()
2125 if not chunkdata:
2125 if not chunkdata:
2126 break
2126 break
2127 fname = chunkdata['filename']
2127 fname = chunkdata['filename']
2128 showchunks(fname)
2128 showchunks(fname)
2129 else:
2129 else:
2130 if isinstance(gen, bundle2.unbundle20):
2130 if isinstance(gen, bundle2.unbundle20):
2131 raise error.Abort(_('use debugbundle2 for this file'))
2131 raise error.Abort(_('use debugbundle2 for this file'))
2132 chunkdata = gen.changelogheader()
2132 chunkdata = gen.changelogheader()
2133 chain = None
2133 chain = None
2134 while True:
2134 while True:
2135 chunkdata = gen.deltachunk(chain)
2135 chunkdata = gen.deltachunk(chain)
2136 if not chunkdata:
2136 if not chunkdata:
2137 break
2137 break
2138 node = chunkdata['node']
2138 node = chunkdata['node']
2139 ui.write("%s%s\n" % (indent_string, hex(node)))
2139 ui.write("%s%s\n" % (indent_string, hex(node)))
2140 chain = node
2140 chain = node
2141
2141
2142 def _debugbundle2(ui, gen, all=None, **opts):
2142 def _debugbundle2(ui, gen, all=None, **opts):
2143 """lists the contents of a bundle2"""
2143 """lists the contents of a bundle2"""
2144 if not isinstance(gen, bundle2.unbundle20):
2144 if not isinstance(gen, bundle2.unbundle20):
2145 raise error.Abort(_('not a bundle2 file'))
2145 raise error.Abort(_('not a bundle2 file'))
2146 ui.write(('Stream params: %s\n' % repr(gen.params)))
2146 ui.write(('Stream params: %s\n' % repr(gen.params)))
2147 for part in gen.iterparts():
2147 for part in gen.iterparts():
2148 ui.write('%s -- %r\n' % (part.type, repr(part.params)))
2148 ui.write('%s -- %r\n' % (part.type, repr(part.params)))
2149 if part.type == 'changegroup':
2149 if part.type == 'changegroup':
2150 version = part.params.get('version', '01')
2150 version = part.params.get('version', '01')
2151 cg = changegroup.getunbundler(version, part, 'UN')
2151 cg = changegroup.getunbundler(version, part, 'UN')
2152 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
2152 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
2153
2153
2154 @command('debugcreatestreamclonebundle', [], 'FILE')
2154 @command('debugcreatestreamclonebundle', [], 'FILE')
2155 def debugcreatestreamclonebundle(ui, repo, fname):
2155 def debugcreatestreamclonebundle(ui, repo, fname):
2156 """create a stream clone bundle file
2156 """create a stream clone bundle file
2157
2157
2158 Stream bundles are special bundles that are essentially archives of
2158 Stream bundles are special bundles that are essentially archives of
2159 revlog files. They are commonly used for cloning very quickly.
2159 revlog files. They are commonly used for cloning very quickly.
2160 """
2160 """
2161 requirements, gen = streamclone.generatebundlev1(repo)
2161 requirements, gen = streamclone.generatebundlev1(repo)
2162 changegroup.writechunks(ui, gen, fname)
2162 changegroup.writechunks(ui, gen, fname)
2163
2163
2164 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
2164 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
2165
2165
2166 @command('debugapplystreamclonebundle', [], 'FILE')
2166 @command('debugapplystreamclonebundle', [], 'FILE')
2167 def debugapplystreamclonebundle(ui, repo, fname):
2167 def debugapplystreamclonebundle(ui, repo, fname):
2168 """apply a stream clone bundle file"""
2168 """apply a stream clone bundle file"""
2169 f = hg.openpath(ui, fname)
2169 f = hg.openpath(ui, fname)
2170 gen = exchange.readbundle(ui, f, fname)
2170 gen = exchange.readbundle(ui, f, fname)
2171 gen.apply(repo)
2171 gen.apply(repo)
2172
2172
2173 @command('debugcheckstate', [], '')
2173 @command('debugcheckstate', [], '')
2174 def debugcheckstate(ui, repo):
2174 def debugcheckstate(ui, repo):
2175 """validate the correctness of the current dirstate"""
2175 """validate the correctness of the current dirstate"""
2176 parent1, parent2 = repo.dirstate.parents()
2176 parent1, parent2 = repo.dirstate.parents()
2177 m1 = repo[parent1].manifest()
2177 m1 = repo[parent1].manifest()
2178 m2 = repo[parent2].manifest()
2178 m2 = repo[parent2].manifest()
2179 errors = 0
2179 errors = 0
2180 for f in repo.dirstate:
2180 for f in repo.dirstate:
2181 state = repo.dirstate[f]
2181 state = repo.dirstate[f]
2182 if state in "nr" and f not in m1:
2182 if state in "nr" and f not in m1:
2183 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
2183 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
2184 errors += 1
2184 errors += 1
2185 if state in "a" and f in m1:
2185 if state in "a" and f in m1:
2186 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
2186 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
2187 errors += 1
2187 errors += 1
2188 if state in "m" and f not in m1 and f not in m2:
2188 if state in "m" and f not in m1 and f not in m2:
2189 ui.warn(_("%s in state %s, but not in either manifest\n") %
2189 ui.warn(_("%s in state %s, but not in either manifest\n") %
2190 (f, state))
2190 (f, state))
2191 errors += 1
2191 errors += 1
2192 for f in m1:
2192 for f in m1:
2193 state = repo.dirstate[f]
2193 state = repo.dirstate[f]
2194 if state not in "nrm":
2194 if state not in "nrm":
2195 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
2195 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
2196 errors += 1
2196 errors += 1
2197 if errors:
2197 if errors:
2198 error = _(".hg/dirstate inconsistent with current parent's manifest")
2198 error = _(".hg/dirstate inconsistent with current parent's manifest")
2199 raise error.Abort(error)
2199 raise error.Abort(error)
2200
2200
2201 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
2201 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
2202 def debugcommands(ui, cmd='', *args):
2202 def debugcommands(ui, cmd='', *args):
2203 """list all available commands and options"""
2203 """list all available commands and options"""
2204 for cmd, vals in sorted(table.iteritems()):
2204 for cmd, vals in sorted(table.iteritems()):
2205 cmd = cmd.split('|')[0].strip('^')
2205 cmd = cmd.split('|')[0].strip('^')
2206 opts = ', '.join([i[1] for i in vals[1]])
2206 opts = ', '.join([i[1] for i in vals[1]])
2207 ui.write('%s: %s\n' % (cmd, opts))
2207 ui.write('%s: %s\n' % (cmd, opts))
2208
2208
2209 @command('debugcomplete',
2209 @command('debugcomplete',
2210 [('o', 'options', None, _('show the command options'))],
2210 [('o', 'options', None, _('show the command options'))],
2211 _('[-o] CMD'),
2211 _('[-o] CMD'),
2212 norepo=True)
2212 norepo=True)
2213 def debugcomplete(ui, cmd='', **opts):
2213 def debugcomplete(ui, cmd='', **opts):
2214 """returns the completion list associated with the given command"""
2214 """returns the completion list associated with the given command"""
2215
2215
2216 if opts.get('options'):
2216 if opts.get('options'):
2217 options = []
2217 options = []
2218 otables = [globalopts]
2218 otables = [globalopts]
2219 if cmd:
2219 if cmd:
2220 aliases, entry = cmdutil.findcmd(cmd, table, False)
2220 aliases, entry = cmdutil.findcmd(cmd, table, False)
2221 otables.append(entry[1])
2221 otables.append(entry[1])
2222 for t in otables:
2222 for t in otables:
2223 for o in t:
2223 for o in t:
2224 if "(DEPRECATED)" in o[3]:
2224 if "(DEPRECATED)" in o[3]:
2225 continue
2225 continue
2226 if o[0]:
2226 if o[0]:
2227 options.append('-%s' % o[0])
2227 options.append('-%s' % o[0])
2228 options.append('--%s' % o[1])
2228 options.append('--%s' % o[1])
2229 ui.write("%s\n" % "\n".join(options))
2229 ui.write("%s\n" % "\n".join(options))
2230 return
2230 return
2231
2231
2232 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2232 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2233 if ui.verbose:
2233 if ui.verbose:
2234 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
2234 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
2235 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
2235 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
2236
2236
2237 @command('debugdag',
2237 @command('debugdag',
2238 [('t', 'tags', None, _('use tags as labels')),
2238 [('t', 'tags', None, _('use tags as labels')),
2239 ('b', 'branches', None, _('annotate with branch names')),
2239 ('b', 'branches', None, _('annotate with branch names')),
2240 ('', 'dots', None, _('use dots for runs')),
2240 ('', 'dots', None, _('use dots for runs')),
2241 ('s', 'spaces', None, _('separate elements by spaces'))],
2241 ('s', 'spaces', None, _('separate elements by spaces'))],
2242 _('[OPTION]... [FILE [REV]...]'),
2242 _('[OPTION]... [FILE [REV]...]'),
2243 optionalrepo=True)
2243 optionalrepo=True)
2244 def debugdag(ui, repo, file_=None, *revs, **opts):
2244 def debugdag(ui, repo, file_=None, *revs, **opts):
2245 """format the changelog or an index DAG as a concise textual description
2245 """format the changelog or an index DAG as a concise textual description
2246
2246
2247 If you pass a revlog index, the revlog's DAG is emitted. If you list
2247 If you pass a revlog index, the revlog's DAG is emitted. If you list
2248 revision numbers, they get labeled in the output as rN.
2248 revision numbers, they get labeled in the output as rN.
2249
2249
2250 Otherwise, the changelog DAG of the current repo is emitted.
2250 Otherwise, the changelog DAG of the current repo is emitted.
2251 """
2251 """
2252 spaces = opts.get('spaces')
2252 spaces = opts.get('spaces')
2253 dots = opts.get('dots')
2253 dots = opts.get('dots')
2254 if file_:
2254 if file_:
2255 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
2255 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
2256 revs = set((int(r) for r in revs))
2256 revs = set((int(r) for r in revs))
2257 def events():
2257 def events():
2258 for r in rlog:
2258 for r in rlog:
2259 yield 'n', (r, list(p for p in rlog.parentrevs(r)
2259 yield 'n', (r, list(p for p in rlog.parentrevs(r)
2260 if p != -1))
2260 if p != -1))
2261 if r in revs:
2261 if r in revs:
2262 yield 'l', (r, "r%i" % r)
2262 yield 'l', (r, "r%i" % r)
2263 elif repo:
2263 elif repo:
2264 cl = repo.changelog
2264 cl = repo.changelog
2265 tags = opts.get('tags')
2265 tags = opts.get('tags')
2266 branches = opts.get('branches')
2266 branches = opts.get('branches')
2267 if tags:
2267 if tags:
2268 labels = {}
2268 labels = {}
2269 for l, n in repo.tags().items():
2269 for l, n in repo.tags().items():
2270 labels.setdefault(cl.rev(n), []).append(l)
2270 labels.setdefault(cl.rev(n), []).append(l)
2271 def events():
2271 def events():
2272 b = "default"
2272 b = "default"
2273 for r in cl:
2273 for r in cl:
2274 if branches:
2274 if branches:
2275 newb = cl.read(cl.node(r))[5]['branch']
2275 newb = cl.read(cl.node(r))[5]['branch']
2276 if newb != b:
2276 if newb != b:
2277 yield 'a', newb
2277 yield 'a', newb
2278 b = newb
2278 b = newb
2279 yield 'n', (r, list(p for p in cl.parentrevs(r)
2279 yield 'n', (r, list(p for p in cl.parentrevs(r)
2280 if p != -1))
2280 if p != -1))
2281 if tags:
2281 if tags:
2282 ls = labels.get(r)
2282 ls = labels.get(r)
2283 if ls:
2283 if ls:
2284 for l in ls:
2284 for l in ls:
2285 yield 'l', (r, l)
2285 yield 'l', (r, l)
2286 else:
2286 else:
2287 raise error.Abort(_('need repo for changelog dag'))
2287 raise error.Abort(_('need repo for changelog dag'))
2288
2288
2289 for line in dagparser.dagtextlines(events(),
2289 for line in dagparser.dagtextlines(events(),
2290 addspaces=spaces,
2290 addspaces=spaces,
2291 wraplabels=True,
2291 wraplabels=True,
2292 wrapannotations=True,
2292 wrapannotations=True,
2293 wrapnonlinear=dots,
2293 wrapnonlinear=dots,
2294 usedots=dots,
2294 usedots=dots,
2295 maxlinewidth=70):
2295 maxlinewidth=70):
2296 ui.write(line)
2296 ui.write(line)
2297 ui.write("\n")
2297 ui.write("\n")
2298
2298
2299 @command('debugdata', debugrevlogopts, _('-c|-m|FILE REV'))
2299 @command('debugdata', debugrevlogopts, _('-c|-m|FILE REV'))
2300 def debugdata(ui, repo, file_, rev=None, **opts):
2300 def debugdata(ui, repo, file_, rev=None, **opts):
2301 """dump the contents of a data file revision"""
2301 """dump the contents of a data file revision"""
2302 if opts.get('changelog') or opts.get('manifest'):
2302 if opts.get('changelog') or opts.get('manifest'):
2303 file_, rev = None, file_
2303 file_, rev = None, file_
2304 elif rev is None:
2304 elif rev is None:
2305 raise error.CommandError('debugdata', _('invalid arguments'))
2305 raise error.CommandError('debugdata', _('invalid arguments'))
2306 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
2306 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
2307 try:
2307 try:
2308 ui.write(r.revision(r.lookup(rev)))
2308 ui.write(r.revision(r.lookup(rev)))
2309 except KeyError:
2309 except KeyError:
2310 raise error.Abort(_('invalid revision identifier %s') % rev)
2310 raise error.Abort(_('invalid revision identifier %s') % rev)
2311
2311
2312 @command('debugdate',
2312 @command('debugdate',
2313 [('e', 'extended', None, _('try extended date formats'))],
2313 [('e', 'extended', None, _('try extended date formats'))],
2314 _('[-e] DATE [RANGE]'),
2314 _('[-e] DATE [RANGE]'),
2315 norepo=True, optionalrepo=True)
2315 norepo=True, optionalrepo=True)
2316 def debugdate(ui, date, range=None, **opts):
2316 def debugdate(ui, date, range=None, **opts):
2317 """parse and display a date"""
2317 """parse and display a date"""
2318 if opts["extended"]:
2318 if opts["extended"]:
2319 d = util.parsedate(date, util.extendeddateformats)
2319 d = util.parsedate(date, util.extendeddateformats)
2320 else:
2320 else:
2321 d = util.parsedate(date)
2321 d = util.parsedate(date)
2322 ui.write(("internal: %s %s\n") % d)
2322 ui.write(("internal: %s %s\n") % d)
2323 ui.write(("standard: %s\n") % util.datestr(d))
2323 ui.write(("standard: %s\n") % util.datestr(d))
2324 if range:
2324 if range:
2325 m = util.matchdate(range)
2325 m = util.matchdate(range)
2326 ui.write(("match: %s\n") % m(d[0]))
2326 ui.write(("match: %s\n") % m(d[0]))
2327
2327
2328 @command('debugdiscovery',
2328 @command('debugdiscovery',
2329 [('', 'old', None, _('use old-style discovery')),
2329 [('', 'old', None, _('use old-style discovery')),
2330 ('', 'nonheads', None,
2330 ('', 'nonheads', None,
2331 _('use old-style discovery with non-heads included')),
2331 _('use old-style discovery with non-heads included')),
2332 ] + remoteopts,
2332 ] + remoteopts,
2333 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
2333 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
2334 def debugdiscovery(ui, repo, remoteurl="default", **opts):
2334 def debugdiscovery(ui, repo, remoteurl="default", **opts):
2335 """runs the changeset discovery protocol in isolation"""
2335 """runs the changeset discovery protocol in isolation"""
2336 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
2336 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
2337 opts.get('branch'))
2337 opts.get('branch'))
2338 remote = hg.peer(repo, opts, remoteurl)
2338 remote = hg.peer(repo, opts, remoteurl)
2339 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
2339 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
2340
2340
2341 # make sure tests are repeatable
2341 # make sure tests are repeatable
2342 random.seed(12323)
2342 random.seed(12323)
2343
2343
2344 def doit(localheads, remoteheads, remote=remote):
2344 def doit(localheads, remoteheads, remote=remote):
2345 if opts.get('old'):
2345 if opts.get('old'):
2346 if localheads:
2346 if localheads:
2347 raise error.Abort('cannot use localheads with old style '
2347 raise error.Abort('cannot use localheads with old style '
2348 'discovery')
2348 'discovery')
2349 if not util.safehasattr(remote, 'branches'):
2349 if not util.safehasattr(remote, 'branches'):
2350 # enable in-client legacy support
2350 # enable in-client legacy support
2351 remote = localrepo.locallegacypeer(remote.local())
2351 remote = localrepo.locallegacypeer(remote.local())
2352 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
2352 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
2353 force=True)
2353 force=True)
2354 common = set(common)
2354 common = set(common)
2355 if not opts.get('nonheads'):
2355 if not opts.get('nonheads'):
2356 ui.write(("unpruned common: %s\n") %
2356 ui.write(("unpruned common: %s\n") %
2357 " ".join(sorted(short(n) for n in common)))
2357 " ".join(sorted(short(n) for n in common)))
2358 dag = dagutil.revlogdag(repo.changelog)
2358 dag = dagutil.revlogdag(repo.changelog)
2359 all = dag.ancestorset(dag.internalizeall(common))
2359 all = dag.ancestorset(dag.internalizeall(common))
2360 common = dag.externalizeall(dag.headsetofconnecteds(all))
2360 common = dag.externalizeall(dag.headsetofconnecteds(all))
2361 else:
2361 else:
2362 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
2362 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
2363 common = set(common)
2363 common = set(common)
2364 rheads = set(hds)
2364 rheads = set(hds)
2365 lheads = set(repo.heads())
2365 lheads = set(repo.heads())
2366 ui.write(("common heads: %s\n") %
2366 ui.write(("common heads: %s\n") %
2367 " ".join(sorted(short(n) for n in common)))
2367 " ".join(sorted(short(n) for n in common)))
2368 if lheads <= common:
2368 if lheads <= common:
2369 ui.write(("local is subset\n"))
2369 ui.write(("local is subset\n"))
2370 elif rheads <= common:
2370 elif rheads <= common:
2371 ui.write(("remote is subset\n"))
2371 ui.write(("remote is subset\n"))
2372
2372
2373 serverlogs = opts.get('serverlog')
2373 serverlogs = opts.get('serverlog')
2374 if serverlogs:
2374 if serverlogs:
2375 for filename in serverlogs:
2375 for filename in serverlogs:
2376 with open(filename, 'r') as logfile:
2376 with open(filename, 'r') as logfile:
2377 line = logfile.readline()
2377 line = logfile.readline()
2378 while line:
2378 while line:
2379 parts = line.strip().split(';')
2379 parts = line.strip().split(';')
2380 op = parts[1]
2380 op = parts[1]
2381 if op == 'cg':
2381 if op == 'cg':
2382 pass
2382 pass
2383 elif op == 'cgss':
2383 elif op == 'cgss':
2384 doit(parts[2].split(' '), parts[3].split(' '))
2384 doit(parts[2].split(' '), parts[3].split(' '))
2385 elif op == 'unb':
2385 elif op == 'unb':
2386 doit(parts[3].split(' '), parts[2].split(' '))
2386 doit(parts[3].split(' '), parts[2].split(' '))
2387 line = logfile.readline()
2387 line = logfile.readline()
2388 else:
2388 else:
2389 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
2389 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
2390 opts.get('remote_head'))
2390 opts.get('remote_head'))
2391 localrevs = opts.get('local_head')
2391 localrevs = opts.get('local_head')
2392 doit(localrevs, remoterevs)
2392 doit(localrevs, remoterevs)
2393
2393
2394 @command('debugextensions', formatteropts, [], norepo=True)
2394 @command('debugextensions', formatteropts, [], norepo=True)
2395 def debugextensions(ui, **opts):
2395 def debugextensions(ui, **opts):
2396 '''show information about active extensions'''
2396 '''show information about active extensions'''
2397 exts = extensions.extensions(ui)
2397 exts = extensions.extensions(ui)
2398 fm = ui.formatter('debugextensions', opts)
2398 fm = ui.formatter('debugextensions', opts)
2399 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
2399 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
2400 extsource = extmod.__file__
2400 extsource = extmod.__file__
2401 exttestedwith = getattr(extmod, 'testedwith', None)
2401 exttestedwith = getattr(extmod, 'testedwith', None)
2402 if exttestedwith is not None:
2402 if exttestedwith is not None:
2403 exttestedwith = exttestedwith.split()
2403 exttestedwith = exttestedwith.split()
2404 extbuglink = getattr(extmod, 'buglink', None)
2404 extbuglink = getattr(extmod, 'buglink', None)
2405
2405
2406 fm.startitem()
2406 fm.startitem()
2407
2407
2408 if ui.quiet or ui.verbose:
2408 if ui.quiet or ui.verbose:
2409 fm.write('name', '%s\n', extname)
2409 fm.write('name', '%s\n', extname)
2410 else:
2410 else:
2411 fm.write('name', '%s', extname)
2411 fm.write('name', '%s', extname)
2412 if not exttestedwith:
2412 if not exttestedwith:
2413 fm.plain(_(' (untested!)\n'))
2413 fm.plain(_(' (untested!)\n'))
2414 else:
2414 else:
2415 if exttestedwith == ['internal'] or \
2415 if exttestedwith == ['internal'] or \
2416 util.version() in exttestedwith:
2416 util.version() in exttestedwith:
2417 fm.plain('\n')
2417 fm.plain('\n')
2418 else:
2418 else:
2419 lasttestedversion = exttestedwith[-1]
2419 lasttestedversion = exttestedwith[-1]
2420 fm.plain(' (%s!)\n' % lasttestedversion)
2420 fm.plain(' (%s!)\n' % lasttestedversion)
2421
2421
2422 fm.condwrite(ui.verbose and extsource, 'source',
2422 fm.condwrite(ui.verbose and extsource, 'source',
2423 _(' location: %s\n'), extsource or "")
2423 _(' location: %s\n'), extsource or "")
2424
2424
2425 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
2425 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
2426 _(' tested with: %s\n'), ' '.join(exttestedwith or []))
2426 _(' tested with: %s\n'), ' '.join(exttestedwith or []))
2427
2427
2428 fm.condwrite(ui.verbose and extbuglink, 'buglink',
2428 fm.condwrite(ui.verbose and extbuglink, 'buglink',
2429 _(' bug reporting: %s\n'), extbuglink or "")
2429 _(' bug reporting: %s\n'), extbuglink or "")
2430
2430
2431 fm.end()
2431 fm.end()
2432
2432
2433 @command('debugfileset',
2433 @command('debugfileset',
2434 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
2434 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
2435 _('[-r REV] FILESPEC'))
2435 _('[-r REV] FILESPEC'))
2436 def debugfileset(ui, repo, expr, **opts):
2436 def debugfileset(ui, repo, expr, **opts):
2437 '''parse and apply a fileset specification'''
2437 '''parse and apply a fileset specification'''
2438 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2438 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2439 if ui.verbose:
2439 if ui.verbose:
2440 tree = fileset.parse(expr)
2440 tree = fileset.parse(expr)
2441 ui.note(fileset.prettyformat(tree), "\n")
2441 ui.note(fileset.prettyformat(tree), "\n")
2442
2442
2443 for f in ctx.getfileset(expr):
2443 for f in ctx.getfileset(expr):
2444 ui.write("%s\n" % f)
2444 ui.write("%s\n" % f)
2445
2445
2446 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
2446 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
2447 def debugfsinfo(ui, path="."):
2447 def debugfsinfo(ui, path="."):
2448 """show information detected about current filesystem"""
2448 """show information detected about current filesystem"""
2449 util.writefile('.debugfsinfo', '')
2449 util.writefile('.debugfsinfo', '')
2450 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
2450 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
2451 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
2451 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
2452 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
2452 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
2453 ui.write(('case-sensitive: %s\n') % (util.checkcase('.debugfsinfo')
2453 ui.write(('case-sensitive: %s\n') % (util.checkcase('.debugfsinfo')
2454 and 'yes' or 'no'))
2454 and 'yes' or 'no'))
2455 os.unlink('.debugfsinfo')
2455 os.unlink('.debugfsinfo')
2456
2456
2457 @command('debuggetbundle',
2457 @command('debuggetbundle',
2458 [('H', 'head', [], _('id of head node'), _('ID')),
2458 [('H', 'head', [], _('id of head node'), _('ID')),
2459 ('C', 'common', [], _('id of common node'), _('ID')),
2459 ('C', 'common', [], _('id of common node'), _('ID')),
2460 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
2460 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
2461 _('REPO FILE [-H|-C ID]...'),
2461 _('REPO FILE [-H|-C ID]...'),
2462 norepo=True)
2462 norepo=True)
2463 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
2463 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
2464 """retrieves a bundle from a repo
2464 """retrieves a bundle from a repo
2465
2465
2466 Every ID must be a full-length hex node id string. Saves the bundle to the
2466 Every ID must be a full-length hex node id string. Saves the bundle to the
2467 given file.
2467 given file.
2468 """
2468 """
2469 repo = hg.peer(ui, opts, repopath)
2469 repo = hg.peer(ui, opts, repopath)
2470 if not repo.capable('getbundle'):
2470 if not repo.capable('getbundle'):
2471 raise error.Abort("getbundle() not supported by target repository")
2471 raise error.Abort("getbundle() not supported by target repository")
2472 args = {}
2472 args = {}
2473 if common:
2473 if common:
2474 args['common'] = [bin(s) for s in common]
2474 args['common'] = [bin(s) for s in common]
2475 if head:
2475 if head:
2476 args['heads'] = [bin(s) for s in head]
2476 args['heads'] = [bin(s) for s in head]
2477 # TODO: get desired bundlecaps from command line.
2477 # TODO: get desired bundlecaps from command line.
2478 args['bundlecaps'] = None
2478 args['bundlecaps'] = None
2479 bundle = repo.getbundle('debug', **args)
2479 bundle = repo.getbundle('debug', **args)
2480
2480
2481 bundletype = opts.get('type', 'bzip2').lower()
2481 bundletype = opts.get('type', 'bzip2').lower()
2482 btypes = {'none': 'HG10UN',
2482 btypes = {'none': 'HG10UN',
2483 'bzip2': 'HG10BZ',
2483 'bzip2': 'HG10BZ',
2484 'gzip': 'HG10GZ',
2484 'gzip': 'HG10GZ',
2485 'bundle2': 'HG20'}
2485 'bundle2': 'HG20'}
2486 bundletype = btypes.get(bundletype)
2486 bundletype = btypes.get(bundletype)
2487 if bundletype not in bundle2.bundletypes:
2487 if bundletype not in bundle2.bundletypes:
2488 raise error.Abort(_('unknown bundle type specified with --type'))
2488 raise error.Abort(_('unknown bundle type specified with --type'))
2489 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
2489 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
2490
2490
2491 @command('debugignore', [], '[FILE]')
2491 @command('debugignore', [], '[FILE]')
2492 def debugignore(ui, repo, *files, **opts):
2492 def debugignore(ui, repo, *files, **opts):
2493 """display the combined ignore pattern and information about ignored files
2493 """display the combined ignore pattern and information about ignored files
2494
2494
2495 With no argument display the combined ignore pattern.
2495 With no argument display the combined ignore pattern.
2496
2496
2497 Given space separated file names, shows if the given file is ignored and
2497 Given space separated file names, shows if the given file is ignored and
2498 if so, show the ignore rule (file and line number) that matched it.
2498 if so, show the ignore rule (file and line number) that matched it.
2499 """
2499 """
2500 ignore = repo.dirstate._ignore
2500 ignore = repo.dirstate._ignore
2501 if not files:
2501 if not files:
2502 # Show all the patterns
2502 # Show all the patterns
2503 includepat = getattr(ignore, 'includepat', None)
2503 includepat = getattr(ignore, 'includepat', None)
2504 if includepat is not None:
2504 if includepat is not None:
2505 ui.write("%s\n" % includepat)
2505 ui.write("%s\n" % includepat)
2506 else:
2506 else:
2507 raise error.Abort(_("no ignore patterns found"))
2507 raise error.Abort(_("no ignore patterns found"))
2508 else:
2508 else:
2509 for f in files:
2509 for f in files:
2510 nf = util.normpath(f)
2510 nf = util.normpath(f)
2511 ignored = None
2511 ignored = None
2512 ignoredata = None
2512 ignoredata = None
2513 if nf != '.':
2513 if nf != '.':
2514 if ignore(nf):
2514 if ignore(nf):
2515 ignored = nf
2515 ignored = nf
2516 ignoredata = repo.dirstate._ignorefileandline(nf)
2516 ignoredata = repo.dirstate._ignorefileandline(nf)
2517 else:
2517 else:
2518 for p in util.finddirs(nf):
2518 for p in util.finddirs(nf):
2519 if ignore(p):
2519 if ignore(p):
2520 ignored = p
2520 ignored = p
2521 ignoredata = repo.dirstate._ignorefileandline(p)
2521 ignoredata = repo.dirstate._ignorefileandline(p)
2522 break
2522 break
2523 if ignored:
2523 if ignored:
2524 if ignored == nf:
2524 if ignored == nf:
2525 ui.write(_("%s is ignored\n") % f)
2525 ui.write(_("%s is ignored\n") % f)
2526 else:
2526 else:
2527 ui.write(_("%s is ignored because of "
2527 ui.write(_("%s is ignored because of "
2528 "containing folder %s\n")
2528 "containing folder %s\n")
2529 % (f, ignored))
2529 % (f, ignored))
2530 ignorefile, lineno, line = ignoredata
2530 ignorefile, lineno, line = ignoredata
2531 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
2531 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
2532 % (ignorefile, lineno, line))
2532 % (ignorefile, lineno, line))
2533 else:
2533 else:
2534 ui.write(_("%s is not ignored\n") % f)
2534 ui.write(_("%s is not ignored\n") % f)
2535
2535
2536 @command('debugindex', debugrevlogopts +
2536 @command('debugindex', debugrevlogopts +
2537 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2537 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2538 _('[-f FORMAT] -c|-m|FILE'),
2538 _('[-f FORMAT] -c|-m|FILE'),
2539 optionalrepo=True)
2539 optionalrepo=True)
2540 def debugindex(ui, repo, file_=None, **opts):
2540 def debugindex(ui, repo, file_=None, **opts):
2541 """dump the contents of an index file"""
2541 """dump the contents of an index file"""
2542 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
2542 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
2543 format = opts.get('format', 0)
2543 format = opts.get('format', 0)
2544 if format not in (0, 1):
2544 if format not in (0, 1):
2545 raise error.Abort(_("unknown format %d") % format)
2545 raise error.Abort(_("unknown format %d") % format)
2546
2546
2547 generaldelta = r.version & revlog.REVLOGGENERALDELTA
2547 generaldelta = r.version & revlog.REVLOGGENERALDELTA
2548 if generaldelta:
2548 if generaldelta:
2549 basehdr = ' delta'
2549 basehdr = ' delta'
2550 else:
2550 else:
2551 basehdr = ' base'
2551 basehdr = ' base'
2552
2552
2553 if ui.debugflag:
2553 if ui.debugflag:
2554 shortfn = hex
2554 shortfn = hex
2555 else:
2555 else:
2556 shortfn = short
2556 shortfn = short
2557
2557
2558 # There might not be anything in r, so have a sane default
2558 # There might not be anything in r, so have a sane default
2559 idlen = 12
2559 idlen = 12
2560 for i in r:
2560 for i in r:
2561 idlen = len(shortfn(r.node(i)))
2561 idlen = len(shortfn(r.node(i)))
2562 break
2562 break
2563
2563
2564 if format == 0:
2564 if format == 0:
2565 ui.write(" rev offset length " + basehdr + " linkrev"
2565 ui.write((" rev offset length " + basehdr + " linkrev"
2566 " %s %s p2\n" % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
2566 " %s %s p2\n") % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
2567 elif format == 1:
2567 elif format == 1:
2568 ui.write(" rev flag offset length"
2568 ui.write((" rev flag offset length"
2569 " size " + basehdr + " link p1 p2"
2569 " size " + basehdr + " link p1 p2"
2570 " %s\n" % "nodeid".rjust(idlen))
2570 " %s\n") % "nodeid".rjust(idlen))
2571
2571
2572 for i in r:
2572 for i in r:
2573 node = r.node(i)
2573 node = r.node(i)
2574 if generaldelta:
2574 if generaldelta:
2575 base = r.deltaparent(i)
2575 base = r.deltaparent(i)
2576 else:
2576 else:
2577 base = r.chainbase(i)
2577 base = r.chainbase(i)
2578 if format == 0:
2578 if format == 0:
2579 try:
2579 try:
2580 pp = r.parents(node)
2580 pp = r.parents(node)
2581 except Exception:
2581 except Exception:
2582 pp = [nullid, nullid]
2582 pp = [nullid, nullid]
2583 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
2583 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
2584 i, r.start(i), r.length(i), base, r.linkrev(i),
2584 i, r.start(i), r.length(i), base, r.linkrev(i),
2585 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
2585 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
2586 elif format == 1:
2586 elif format == 1:
2587 pr = r.parentrevs(i)
2587 pr = r.parentrevs(i)
2588 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
2588 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
2589 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2589 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2590 base, r.linkrev(i), pr[0], pr[1], shortfn(node)))
2590 base, r.linkrev(i), pr[0], pr[1], shortfn(node)))
2591
2591
2592 @command('debugindexdot', debugrevlogopts,
2592 @command('debugindexdot', debugrevlogopts,
2593 _('-c|-m|FILE'), optionalrepo=True)
2593 _('-c|-m|FILE'), optionalrepo=True)
2594 def debugindexdot(ui, repo, file_=None, **opts):
2594 def debugindexdot(ui, repo, file_=None, **opts):
2595 """dump an index DAG as a graphviz dot file"""
2595 """dump an index DAG as a graphviz dot file"""
2596 r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts)
2596 r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts)
2597 ui.write(("digraph G {\n"))
2597 ui.write(("digraph G {\n"))
2598 for i in r:
2598 for i in r:
2599 node = r.node(i)
2599 node = r.node(i)
2600 pp = r.parents(node)
2600 pp = r.parents(node)
2601 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
2601 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
2602 if pp[1] != nullid:
2602 if pp[1] != nullid:
2603 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
2603 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
2604 ui.write("}\n")
2604 ui.write("}\n")
2605
2605
2606 @command('debugdeltachain',
2606 @command('debugdeltachain',
2607 debugrevlogopts + formatteropts,
2607 debugrevlogopts + formatteropts,
2608 _('-c|-m|FILE'),
2608 _('-c|-m|FILE'),
2609 optionalrepo=True)
2609 optionalrepo=True)
2610 def debugdeltachain(ui, repo, file_=None, **opts):
2610 def debugdeltachain(ui, repo, file_=None, **opts):
2611 """dump information about delta chains in a revlog
2611 """dump information about delta chains in a revlog
2612
2612
2613 Output can be templatized. Available template keywords are:
2613 Output can be templatized. Available template keywords are:
2614
2614
2615 rev revision number
2615 rev revision number
2616 chainid delta chain identifier (numbered by unique base)
2616 chainid delta chain identifier (numbered by unique base)
2617 chainlen delta chain length to this revision
2617 chainlen delta chain length to this revision
2618 prevrev previous revision in delta chain
2618 prevrev previous revision in delta chain
2619 deltatype role of delta / how it was computed
2619 deltatype role of delta / how it was computed
2620 compsize compressed size of revision
2620 compsize compressed size of revision
2621 uncompsize uncompressed size of revision
2621 uncompsize uncompressed size of revision
2622 chainsize total size of compressed revisions in chain
2622 chainsize total size of compressed revisions in chain
2623 chainratio total chain size divided by uncompressed revision size
2623 chainratio total chain size divided by uncompressed revision size
2624 (new delta chains typically start at ratio 2.00)
2624 (new delta chains typically start at ratio 2.00)
2625 lindist linear distance from base revision in delta chain to end
2625 lindist linear distance from base revision in delta chain to end
2626 of this revision
2626 of this revision
2627 extradist total size of revisions not part of this delta chain from
2627 extradist total size of revisions not part of this delta chain from
2628 base of delta chain to end of this revision; a measurement
2628 base of delta chain to end of this revision; a measurement
2629 of how much extra data we need to read/seek across to read
2629 of how much extra data we need to read/seek across to read
2630 the delta chain for this revision
2630 the delta chain for this revision
2631 extraratio extradist divided by chainsize; another representation of
2631 extraratio extradist divided by chainsize; another representation of
2632 how much unrelated data is needed to load this delta chain
2632 how much unrelated data is needed to load this delta chain
2633 """
2633 """
2634 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
2634 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
2635 index = r.index
2635 index = r.index
2636 generaldelta = r.version & revlog.REVLOGGENERALDELTA
2636 generaldelta = r.version & revlog.REVLOGGENERALDELTA
2637
2637
2638 def revinfo(rev):
2638 def revinfo(rev):
2639 e = index[rev]
2639 e = index[rev]
2640 compsize = e[1]
2640 compsize = e[1]
2641 uncompsize = e[2]
2641 uncompsize = e[2]
2642 chainsize = 0
2642 chainsize = 0
2643
2643
2644 if generaldelta:
2644 if generaldelta:
2645 if e[3] == e[5]:
2645 if e[3] == e[5]:
2646 deltatype = 'p1'
2646 deltatype = 'p1'
2647 elif e[3] == e[6]:
2647 elif e[3] == e[6]:
2648 deltatype = 'p2'
2648 deltatype = 'p2'
2649 elif e[3] == rev - 1:
2649 elif e[3] == rev - 1:
2650 deltatype = 'prev'
2650 deltatype = 'prev'
2651 elif e[3] == rev:
2651 elif e[3] == rev:
2652 deltatype = 'base'
2652 deltatype = 'base'
2653 else:
2653 else:
2654 deltatype = 'other'
2654 deltatype = 'other'
2655 else:
2655 else:
2656 if e[3] == rev:
2656 if e[3] == rev:
2657 deltatype = 'base'
2657 deltatype = 'base'
2658 else:
2658 else:
2659 deltatype = 'prev'
2659 deltatype = 'prev'
2660
2660
2661 chain = r._deltachain(rev)[0]
2661 chain = r._deltachain(rev)[0]
2662 for iterrev in chain:
2662 for iterrev in chain:
2663 e = index[iterrev]
2663 e = index[iterrev]
2664 chainsize += e[1]
2664 chainsize += e[1]
2665
2665
2666 return compsize, uncompsize, deltatype, chain, chainsize
2666 return compsize, uncompsize, deltatype, chain, chainsize
2667
2667
2668 fm = ui.formatter('debugdeltachain', opts)
2668 fm = ui.formatter('debugdeltachain', opts)
2669
2669
2670 fm.plain(' rev chain# chainlen prev delta '
2670 fm.plain(' rev chain# chainlen prev delta '
2671 'size rawsize chainsize ratio lindist extradist '
2671 'size rawsize chainsize ratio lindist extradist '
2672 'extraratio\n')
2672 'extraratio\n')
2673
2673
2674 chainbases = {}
2674 chainbases = {}
2675 for rev in r:
2675 for rev in r:
2676 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
2676 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
2677 chainbase = chain[0]
2677 chainbase = chain[0]
2678 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
2678 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
2679 basestart = r.start(chainbase)
2679 basestart = r.start(chainbase)
2680 revstart = r.start(rev)
2680 revstart = r.start(rev)
2681 lineardist = revstart + comp - basestart
2681 lineardist = revstart + comp - basestart
2682 extradist = lineardist - chainsize
2682 extradist = lineardist - chainsize
2683 try:
2683 try:
2684 prevrev = chain[-2]
2684 prevrev = chain[-2]
2685 except IndexError:
2685 except IndexError:
2686 prevrev = -1
2686 prevrev = -1
2687
2687
2688 chainratio = float(chainsize) / float(uncomp)
2688 chainratio = float(chainsize) / float(uncomp)
2689 extraratio = float(extradist) / float(chainsize)
2689 extraratio = float(extradist) / float(chainsize)
2690
2690
2691 fm.startitem()
2691 fm.startitem()
2692 fm.write('rev chainid chainlen prevrev deltatype compsize '
2692 fm.write('rev chainid chainlen prevrev deltatype compsize '
2693 'uncompsize chainsize chainratio lindist extradist '
2693 'uncompsize chainsize chainratio lindist extradist '
2694 'extraratio',
2694 'extraratio',
2695 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f\n',
2695 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f\n',
2696 rev, chainid, len(chain), prevrev, deltatype, comp,
2696 rev, chainid, len(chain), prevrev, deltatype, comp,
2697 uncomp, chainsize, chainratio, lineardist, extradist,
2697 uncomp, chainsize, chainratio, lineardist, extradist,
2698 extraratio,
2698 extraratio,
2699 rev=rev, chainid=chainid, chainlen=len(chain),
2699 rev=rev, chainid=chainid, chainlen=len(chain),
2700 prevrev=prevrev, deltatype=deltatype, compsize=comp,
2700 prevrev=prevrev, deltatype=deltatype, compsize=comp,
2701 uncompsize=uncomp, chainsize=chainsize,
2701 uncompsize=uncomp, chainsize=chainsize,
2702 chainratio=chainratio, lindist=lineardist,
2702 chainratio=chainratio, lindist=lineardist,
2703 extradist=extradist, extraratio=extraratio)
2703 extradist=extradist, extraratio=extraratio)
2704
2704
2705 fm.end()
2705 fm.end()
2706
2706
2707 @command('debuginstall', [] + formatteropts, '', norepo=True)
2707 @command('debuginstall', [] + formatteropts, '', norepo=True)
2708 def debuginstall(ui, **opts):
2708 def debuginstall(ui, **opts):
2709 '''test Mercurial installation
2709 '''test Mercurial installation
2710
2710
2711 Returns 0 on success.
2711 Returns 0 on success.
2712 '''
2712 '''
2713
2713
2714 def writetemp(contents):
2714 def writetemp(contents):
2715 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
2715 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
2716 f = os.fdopen(fd, "wb")
2716 f = os.fdopen(fd, "wb")
2717 f.write(contents)
2717 f.write(contents)
2718 f.close()
2718 f.close()
2719 return name
2719 return name
2720
2720
2721 problems = 0
2721 problems = 0
2722
2722
2723 fm = ui.formatter('debuginstall', opts)
2723 fm = ui.formatter('debuginstall', opts)
2724 fm.startitem()
2724 fm.startitem()
2725
2725
2726 # encoding
2726 # encoding
2727 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
2727 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
2728 err = None
2728 err = None
2729 try:
2729 try:
2730 encoding.fromlocal("test")
2730 encoding.fromlocal("test")
2731 except error.Abort as inst:
2731 except error.Abort as inst:
2732 err = inst
2732 err = inst
2733 problems += 1
2733 problems += 1
2734 fm.condwrite(err, 'encodingerror', _(" %s\n"
2734 fm.condwrite(err, 'encodingerror', _(" %s\n"
2735 " (check that your locale is properly set)\n"), err)
2735 " (check that your locale is properly set)\n"), err)
2736
2736
2737 # Python
2737 # Python
2738 fm.write('pythonexe', _("checking Python executable (%s)\n"),
2738 fm.write('pythonexe', _("checking Python executable (%s)\n"),
2739 sys.executable)
2739 sys.executable)
2740 fm.write('pythonver', _("checking Python version (%s)\n"),
2740 fm.write('pythonver', _("checking Python version (%s)\n"),
2741 ("%s.%s.%s" % sys.version_info[:3]))
2741 ("%s.%s.%s" % sys.version_info[:3]))
2742 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
2742 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
2743 os.path.dirname(os.__file__))
2743 os.path.dirname(os.__file__))
2744
2744
2745 # hg version
2745 # hg version
2746 hgver = util.version()
2746 hgver = util.version()
2747 fm.write('hgver', _("checking Mercurial version (%s)\n"),
2747 fm.write('hgver', _("checking Mercurial version (%s)\n"),
2748 hgver.split('+')[0])
2748 hgver.split('+')[0])
2749 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
2749 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
2750 '+'.join(hgver.split('+')[1:]))
2750 '+'.join(hgver.split('+')[1:]))
2751
2751
2752 # compiled modules
2752 # compiled modules
2753 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
2753 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
2754 policy.policy)
2754 policy.policy)
2755 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
2755 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
2756 os.path.dirname(__file__))
2756 os.path.dirname(__file__))
2757
2757
2758 err = None
2758 err = None
2759 try:
2759 try:
2760 from . import (
2760 from . import (
2761 base85,
2761 base85,
2762 bdiff,
2762 bdiff,
2763 mpatch,
2763 mpatch,
2764 osutil,
2764 osutil,
2765 )
2765 )
2766 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2766 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2767 except Exception as inst:
2767 except Exception as inst:
2768 err = inst
2768 err = inst
2769 problems += 1
2769 problems += 1
2770 fm.condwrite(err, 'extensionserror', " %s\n", err)
2770 fm.condwrite(err, 'extensionserror', " %s\n", err)
2771
2771
2772 # templates
2772 # templates
2773 p = templater.templatepaths()
2773 p = templater.templatepaths()
2774 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
2774 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
2775 fm.condwrite(not p, '', _(" no template directories found\n"))
2775 fm.condwrite(not p, '', _(" no template directories found\n"))
2776 if p:
2776 if p:
2777 m = templater.templatepath("map-cmdline.default")
2777 m = templater.templatepath("map-cmdline.default")
2778 if m:
2778 if m:
2779 # template found, check if it is working
2779 # template found, check if it is working
2780 err = None
2780 err = None
2781 try:
2781 try:
2782 templater.templater.frommapfile(m)
2782 templater.templater.frommapfile(m)
2783 except Exception as inst:
2783 except Exception as inst:
2784 err = inst
2784 err = inst
2785 p = None
2785 p = None
2786 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
2786 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
2787 else:
2787 else:
2788 p = None
2788 p = None
2789 fm.condwrite(p, 'defaulttemplate',
2789 fm.condwrite(p, 'defaulttemplate',
2790 _("checking default template (%s)\n"), m)
2790 _("checking default template (%s)\n"), m)
2791 fm.condwrite(not m, 'defaulttemplatenotfound',
2791 fm.condwrite(not m, 'defaulttemplatenotfound',
2792 _(" template '%s' not found\n"), "default")
2792 _(" template '%s' not found\n"), "default")
2793 if not p:
2793 if not p:
2794 problems += 1
2794 problems += 1
2795 fm.condwrite(not p, '',
2795 fm.condwrite(not p, '',
2796 _(" (templates seem to have been installed incorrectly)\n"))
2796 _(" (templates seem to have been installed incorrectly)\n"))
2797
2797
2798 # editor
2798 # editor
2799 editor = ui.geteditor()
2799 editor = ui.geteditor()
2800 editor = util.expandpath(editor)
2800 editor = util.expandpath(editor)
2801 fm.write('editor', _("checking commit editor... (%s)\n"), editor)
2801 fm.write('editor', _("checking commit editor... (%s)\n"), editor)
2802 cmdpath = util.findexe(shlex.split(editor)[0])
2802 cmdpath = util.findexe(shlex.split(editor)[0])
2803 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
2803 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
2804 _(" No commit editor set and can't find %s in PATH\n"
2804 _(" No commit editor set and can't find %s in PATH\n"
2805 " (specify a commit editor in your configuration"
2805 " (specify a commit editor in your configuration"
2806 " file)\n"), not cmdpath and editor == 'vi' and editor)
2806 " file)\n"), not cmdpath and editor == 'vi' and editor)
2807 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
2807 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
2808 _(" Can't find editor '%s' in PATH\n"
2808 _(" Can't find editor '%s' in PATH\n"
2809 " (specify a commit editor in your configuration"
2809 " (specify a commit editor in your configuration"
2810 " file)\n"), not cmdpath and editor)
2810 " file)\n"), not cmdpath and editor)
2811 if not cmdpath and editor != 'vi':
2811 if not cmdpath and editor != 'vi':
2812 problems += 1
2812 problems += 1
2813
2813
2814 # check username
2814 # check username
2815 username = None
2815 username = None
2816 err = None
2816 err = None
2817 try:
2817 try:
2818 username = ui.username()
2818 username = ui.username()
2819 except error.Abort as e:
2819 except error.Abort as e:
2820 err = e
2820 err = e
2821 problems += 1
2821 problems += 1
2822
2822
2823 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
2823 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
2824 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
2824 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
2825 " (specify a username in your configuration file)\n"), err)
2825 " (specify a username in your configuration file)\n"), err)
2826
2826
2827 fm.condwrite(not problems, '',
2827 fm.condwrite(not problems, '',
2828 _("no problems detected\n"))
2828 _("no problems detected\n"))
2829 if not problems:
2829 if not problems:
2830 fm.data(problems=problems)
2830 fm.data(problems=problems)
2831 fm.condwrite(problems, 'problems',
2831 fm.condwrite(problems, 'problems',
2832 _("%s problems detected,"
2832 _("%s problems detected,"
2833 " please check your install!\n"), problems)
2833 " please check your install!\n"), problems)
2834 fm.end()
2834 fm.end()
2835
2835
2836 return problems
2836 return problems
2837
2837
2838 @command('debugknown', [], _('REPO ID...'), norepo=True)
2838 @command('debugknown', [], _('REPO ID...'), norepo=True)
2839 def debugknown(ui, repopath, *ids, **opts):
2839 def debugknown(ui, repopath, *ids, **opts):
2840 """test whether node ids are known to a repo
2840 """test whether node ids are known to a repo
2841
2841
2842 Every ID must be a full-length hex node id string. Returns a list of 0s
2842 Every ID must be a full-length hex node id string. Returns a list of 0s
2843 and 1s indicating unknown/known.
2843 and 1s indicating unknown/known.
2844 """
2844 """
2845 repo = hg.peer(ui, opts, repopath)
2845 repo = hg.peer(ui, opts, repopath)
2846 if not repo.capable('known'):
2846 if not repo.capable('known'):
2847 raise error.Abort("known() not supported by target repository")
2847 raise error.Abort("known() not supported by target repository")
2848 flags = repo.known([bin(s) for s in ids])
2848 flags = repo.known([bin(s) for s in ids])
2849 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2849 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2850
2850
2851 @command('debuglabelcomplete', [], _('LABEL...'))
2851 @command('debuglabelcomplete', [], _('LABEL...'))
2852 def debuglabelcomplete(ui, repo, *args):
2852 def debuglabelcomplete(ui, repo, *args):
2853 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
2853 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
2854 debugnamecomplete(ui, repo, *args)
2854 debugnamecomplete(ui, repo, *args)
2855
2855
2856 @command('debugmergestate', [], '')
2856 @command('debugmergestate', [], '')
2857 def debugmergestate(ui, repo, *args):
2857 def debugmergestate(ui, repo, *args):
2858 """print merge state
2858 """print merge state
2859
2859
2860 Use --verbose to print out information about whether v1 or v2 merge state
2860 Use --verbose to print out information about whether v1 or v2 merge state
2861 was chosen."""
2861 was chosen."""
2862 def _hashornull(h):
2862 def _hashornull(h):
2863 if h == nullhex:
2863 if h == nullhex:
2864 return 'null'
2864 return 'null'
2865 else:
2865 else:
2866 return h
2866 return h
2867
2867
2868 def printrecords(version):
2868 def printrecords(version):
2869 ui.write(('* version %s records\n') % version)
2869 ui.write(('* version %s records\n') % version)
2870 if version == 1:
2870 if version == 1:
2871 records = v1records
2871 records = v1records
2872 else:
2872 else:
2873 records = v2records
2873 records = v2records
2874
2874
2875 for rtype, record in records:
2875 for rtype, record in records:
2876 # pretty print some record types
2876 # pretty print some record types
2877 if rtype == 'L':
2877 if rtype == 'L':
2878 ui.write(('local: %s\n') % record)
2878 ui.write(('local: %s\n') % record)
2879 elif rtype == 'O':
2879 elif rtype == 'O':
2880 ui.write(('other: %s\n') % record)
2880 ui.write(('other: %s\n') % record)
2881 elif rtype == 'm':
2881 elif rtype == 'm':
2882 driver, mdstate = record.split('\0', 1)
2882 driver, mdstate = record.split('\0', 1)
2883 ui.write(('merge driver: %s (state "%s")\n')
2883 ui.write(('merge driver: %s (state "%s")\n')
2884 % (driver, mdstate))
2884 % (driver, mdstate))
2885 elif rtype in 'FDC':
2885 elif rtype in 'FDC':
2886 r = record.split('\0')
2886 r = record.split('\0')
2887 f, state, hash, lfile, afile, anode, ofile = r[0:7]
2887 f, state, hash, lfile, afile, anode, ofile = r[0:7]
2888 if version == 1:
2888 if version == 1:
2889 onode = 'not stored in v1 format'
2889 onode = 'not stored in v1 format'
2890 flags = r[7]
2890 flags = r[7]
2891 else:
2891 else:
2892 onode, flags = r[7:9]
2892 onode, flags = r[7:9]
2893 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
2893 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
2894 % (f, rtype, state, _hashornull(hash)))
2894 % (f, rtype, state, _hashornull(hash)))
2895 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
2895 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
2896 ui.write((' ancestor path: %s (node %s)\n')
2896 ui.write((' ancestor path: %s (node %s)\n')
2897 % (afile, _hashornull(anode)))
2897 % (afile, _hashornull(anode)))
2898 ui.write((' other path: %s (node %s)\n')
2898 ui.write((' other path: %s (node %s)\n')
2899 % (ofile, _hashornull(onode)))
2899 % (ofile, _hashornull(onode)))
2900 elif rtype == 'f':
2900 elif rtype == 'f':
2901 filename, rawextras = record.split('\0', 1)
2901 filename, rawextras = record.split('\0', 1)
2902 extras = rawextras.split('\0')
2902 extras = rawextras.split('\0')
2903 i = 0
2903 i = 0
2904 extrastrings = []
2904 extrastrings = []
2905 while i < len(extras):
2905 while i < len(extras):
2906 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
2906 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
2907 i += 2
2907 i += 2
2908
2908
2909 ui.write(('file extras: %s (%s)\n')
2909 ui.write(('file extras: %s (%s)\n')
2910 % (filename, ', '.join(extrastrings)))
2910 % (filename, ', '.join(extrastrings)))
2911 elif rtype == 'l':
2911 elif rtype == 'l':
2912 labels = record.split('\0', 2)
2912 labels = record.split('\0', 2)
2913 labels = [l for l in labels if len(l) > 0]
2913 labels = [l for l in labels if len(l) > 0]
2914 ui.write(('labels:\n'))
2914 ui.write(('labels:\n'))
2915 ui.write((' local: %s\n' % labels[0]))
2915 ui.write((' local: %s\n' % labels[0]))
2916 ui.write((' other: %s\n' % labels[1]))
2916 ui.write((' other: %s\n' % labels[1]))
2917 if len(labels) > 2:
2917 if len(labels) > 2:
2918 ui.write((' base: %s\n' % labels[2]))
2918 ui.write((' base: %s\n' % labels[2]))
2919 else:
2919 else:
2920 ui.write(('unrecognized entry: %s\t%s\n')
2920 ui.write(('unrecognized entry: %s\t%s\n')
2921 % (rtype, record.replace('\0', '\t')))
2921 % (rtype, record.replace('\0', '\t')))
2922
2922
2923 # Avoid mergestate.read() since it may raise an exception for unsupported
2923 # Avoid mergestate.read() since it may raise an exception for unsupported
2924 # merge state records. We shouldn't be doing this, but this is OK since this
2924 # merge state records. We shouldn't be doing this, but this is OK since this
2925 # command is pretty low-level.
2925 # command is pretty low-level.
2926 ms = mergemod.mergestate(repo)
2926 ms = mergemod.mergestate(repo)
2927
2927
2928 # sort so that reasonable information is on top
2928 # sort so that reasonable information is on top
2929 v1records = ms._readrecordsv1()
2929 v1records = ms._readrecordsv1()
2930 v2records = ms._readrecordsv2()
2930 v2records = ms._readrecordsv2()
2931 order = 'LOml'
2931 order = 'LOml'
2932 def key(r):
2932 def key(r):
2933 idx = order.find(r[0])
2933 idx = order.find(r[0])
2934 if idx == -1:
2934 if idx == -1:
2935 return (1, r[1])
2935 return (1, r[1])
2936 else:
2936 else:
2937 return (0, idx)
2937 return (0, idx)
2938 v1records.sort(key=key)
2938 v1records.sort(key=key)
2939 v2records.sort(key=key)
2939 v2records.sort(key=key)
2940
2940
2941 if not v1records and not v2records:
2941 if not v1records and not v2records:
2942 ui.write(('no merge state found\n'))
2942 ui.write(('no merge state found\n'))
2943 elif not v2records:
2943 elif not v2records:
2944 ui.note(('no version 2 merge state\n'))
2944 ui.note(('no version 2 merge state\n'))
2945 printrecords(1)
2945 printrecords(1)
2946 elif ms._v1v2match(v1records, v2records):
2946 elif ms._v1v2match(v1records, v2records):
2947 ui.note(('v1 and v2 states match: using v2\n'))
2947 ui.note(('v1 and v2 states match: using v2\n'))
2948 printrecords(2)
2948 printrecords(2)
2949 else:
2949 else:
2950 ui.note(('v1 and v2 states mismatch: using v1\n'))
2950 ui.note(('v1 and v2 states mismatch: using v1\n'))
2951 printrecords(1)
2951 printrecords(1)
2952 if ui.verbose:
2952 if ui.verbose:
2953 printrecords(2)
2953 printrecords(2)
2954
2954
2955 @command('debugnamecomplete', [], _('NAME...'))
2955 @command('debugnamecomplete', [], _('NAME...'))
2956 def debugnamecomplete(ui, repo, *args):
2956 def debugnamecomplete(ui, repo, *args):
2957 '''complete "names" - tags, open branch names, bookmark names'''
2957 '''complete "names" - tags, open branch names, bookmark names'''
2958
2958
2959 names = set()
2959 names = set()
2960 # since we previously only listed open branches, we will handle that
2960 # since we previously only listed open branches, we will handle that
2961 # specially (after this for loop)
2961 # specially (after this for loop)
2962 for name, ns in repo.names.iteritems():
2962 for name, ns in repo.names.iteritems():
2963 if name != 'branches':
2963 if name != 'branches':
2964 names.update(ns.listnames(repo))
2964 names.update(ns.listnames(repo))
2965 names.update(tag for (tag, heads, tip, closed)
2965 names.update(tag for (tag, heads, tip, closed)
2966 in repo.branchmap().iterbranches() if not closed)
2966 in repo.branchmap().iterbranches() if not closed)
2967 completions = set()
2967 completions = set()
2968 if not args:
2968 if not args:
2969 args = ['']
2969 args = ['']
2970 for a in args:
2970 for a in args:
2971 completions.update(n for n in names if n.startswith(a))
2971 completions.update(n for n in names if n.startswith(a))
2972 ui.write('\n'.join(sorted(completions)))
2972 ui.write('\n'.join(sorted(completions)))
2973 ui.write('\n')
2973 ui.write('\n')
2974
2974
2975 @command('debuglocks',
2975 @command('debuglocks',
2976 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
2976 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
2977 ('W', 'force-wlock', None,
2977 ('W', 'force-wlock', None,
2978 _('free the working state lock (DANGEROUS)'))],
2978 _('free the working state lock (DANGEROUS)'))],
2979 _('[OPTION]...'))
2979 _('[OPTION]...'))
2980 def debuglocks(ui, repo, **opts):
2980 def debuglocks(ui, repo, **opts):
2981 """show or modify state of locks
2981 """show or modify state of locks
2982
2982
2983 By default, this command will show which locks are held. This
2983 By default, this command will show which locks are held. This
2984 includes the user and process holding the lock, the amount of time
2984 includes the user and process holding the lock, the amount of time
2985 the lock has been held, and the machine name where the process is
2985 the lock has been held, and the machine name where the process is
2986 running if it's not local.
2986 running if it's not local.
2987
2987
2988 Locks protect the integrity of Mercurial's data, so should be
2988 Locks protect the integrity of Mercurial's data, so should be
2989 treated with care. System crashes or other interruptions may cause
2989 treated with care. System crashes or other interruptions may cause
2990 locks to not be properly released, though Mercurial will usually
2990 locks to not be properly released, though Mercurial will usually
2991 detect and remove such stale locks automatically.
2991 detect and remove such stale locks automatically.
2992
2992
2993 However, detecting stale locks may not always be possible (for
2993 However, detecting stale locks may not always be possible (for
2994 instance, on a shared filesystem). Removing locks may also be
2994 instance, on a shared filesystem). Removing locks may also be
2995 blocked by filesystem permissions.
2995 blocked by filesystem permissions.
2996
2996
2997 Returns 0 if no locks are held.
2997 Returns 0 if no locks are held.
2998
2998
2999 """
2999 """
3000
3000
3001 if opts.get('force_lock'):
3001 if opts.get('force_lock'):
3002 repo.svfs.unlink('lock')
3002 repo.svfs.unlink('lock')
3003 if opts.get('force_wlock'):
3003 if opts.get('force_wlock'):
3004 repo.vfs.unlink('wlock')
3004 repo.vfs.unlink('wlock')
3005 if opts.get('force_lock') or opts.get('force_lock'):
3005 if opts.get('force_lock') or opts.get('force_lock'):
3006 return 0
3006 return 0
3007
3007
3008 now = time.time()
3008 now = time.time()
3009 held = 0
3009 held = 0
3010
3010
3011 def report(vfs, name, method):
3011 def report(vfs, name, method):
3012 # this causes stale locks to get reaped for more accurate reporting
3012 # this causes stale locks to get reaped for more accurate reporting
3013 try:
3013 try:
3014 l = method(False)
3014 l = method(False)
3015 except error.LockHeld:
3015 except error.LockHeld:
3016 l = None
3016 l = None
3017
3017
3018 if l:
3018 if l:
3019 l.release()
3019 l.release()
3020 else:
3020 else:
3021 try:
3021 try:
3022 stat = vfs.lstat(name)
3022 stat = vfs.lstat(name)
3023 age = now - stat.st_mtime
3023 age = now - stat.st_mtime
3024 user = util.username(stat.st_uid)
3024 user = util.username(stat.st_uid)
3025 locker = vfs.readlock(name)
3025 locker = vfs.readlock(name)
3026 if ":" in locker:
3026 if ":" in locker:
3027 host, pid = locker.split(':')
3027 host, pid = locker.split(':')
3028 if host == socket.gethostname():
3028 if host == socket.gethostname():
3029 locker = 'user %s, process %s' % (user, pid)
3029 locker = 'user %s, process %s' % (user, pid)
3030 else:
3030 else:
3031 locker = 'user %s, process %s, host %s' \
3031 locker = 'user %s, process %s, host %s' \
3032 % (user, pid, host)
3032 % (user, pid, host)
3033 ui.write("%-6s %s (%ds)\n" % (name + ":", locker, age))
3033 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
3034 return 1
3034 return 1
3035 except OSError as e:
3035 except OSError as e:
3036 if e.errno != errno.ENOENT:
3036 if e.errno != errno.ENOENT:
3037 raise
3037 raise
3038
3038
3039 ui.write("%-6s free\n" % (name + ":"))
3039 ui.write(("%-6s free\n") % (name + ":"))
3040 return 0
3040 return 0
3041
3041
3042 held += report(repo.svfs, "lock", repo.lock)
3042 held += report(repo.svfs, "lock", repo.lock)
3043 held += report(repo.vfs, "wlock", repo.wlock)
3043 held += report(repo.vfs, "wlock", repo.wlock)
3044
3044
3045 return held
3045 return held
3046
3046
3047 @command('debugobsolete',
3047 @command('debugobsolete',
3048 [('', 'flags', 0, _('markers flag')),
3048 [('', 'flags', 0, _('markers flag')),
3049 ('', 'record-parents', False,
3049 ('', 'record-parents', False,
3050 _('record parent information for the precursor')),
3050 _('record parent information for the precursor')),
3051 ('r', 'rev', [], _('display markers relevant to REV')),
3051 ('r', 'rev', [], _('display markers relevant to REV')),
3052 ('', 'index', False, _('display index of the marker')),
3052 ('', 'index', False, _('display index of the marker')),
3053 ('', 'delete', [], _('delete markers specified by indices')),
3053 ('', 'delete', [], _('delete markers specified by indices')),
3054 ] + commitopts2,
3054 ] + commitopts2,
3055 _('[OBSOLETED [REPLACEMENT ...]]'))
3055 _('[OBSOLETED [REPLACEMENT ...]]'))
3056 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
3056 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
3057 """create arbitrary obsolete marker
3057 """create arbitrary obsolete marker
3058
3058
3059 With no arguments, displays the list of obsolescence markers."""
3059 With no arguments, displays the list of obsolescence markers."""
3060
3060
3061 def parsenodeid(s):
3061 def parsenodeid(s):
3062 try:
3062 try:
3063 # We do not use revsingle/revrange functions here to accept
3063 # We do not use revsingle/revrange functions here to accept
3064 # arbitrary node identifiers, possibly not present in the
3064 # arbitrary node identifiers, possibly not present in the
3065 # local repository.
3065 # local repository.
3066 n = bin(s)
3066 n = bin(s)
3067 if len(n) != len(nullid):
3067 if len(n) != len(nullid):
3068 raise TypeError()
3068 raise TypeError()
3069 return n
3069 return n
3070 except TypeError:
3070 except TypeError:
3071 raise error.Abort('changeset references must be full hexadecimal '
3071 raise error.Abort('changeset references must be full hexadecimal '
3072 'node identifiers')
3072 'node identifiers')
3073
3073
3074 if opts.get('delete'):
3074 if opts.get('delete'):
3075 indices = []
3075 indices = []
3076 for v in opts.get('delete'):
3076 for v in opts.get('delete'):
3077 try:
3077 try:
3078 indices.append(int(v))
3078 indices.append(int(v))
3079 except ValueError:
3079 except ValueError:
3080 raise error.Abort(_('invalid index value: %r') % v,
3080 raise error.Abort(_('invalid index value: %r') % v,
3081 hint=_('use integers for indices'))
3081 hint=_('use integers for indices'))
3082
3082
3083 if repo.currenttransaction():
3083 if repo.currenttransaction():
3084 raise error.Abort(_('cannot delete obsmarkers in the middle '
3084 raise error.Abort(_('cannot delete obsmarkers in the middle '
3085 'of transaction.'))
3085 'of transaction.'))
3086
3086
3087 with repo.lock():
3087 with repo.lock():
3088 n = repair.deleteobsmarkers(repo.obsstore, indices)
3088 n = repair.deleteobsmarkers(repo.obsstore, indices)
3089 ui.write(_('deleted %i obsolescense markers\n') % n)
3089 ui.write(_('deleted %i obsolescense markers\n') % n)
3090
3090
3091 return
3091 return
3092
3092
3093 if precursor is not None:
3093 if precursor is not None:
3094 if opts['rev']:
3094 if opts['rev']:
3095 raise error.Abort('cannot select revision when creating marker')
3095 raise error.Abort('cannot select revision when creating marker')
3096 metadata = {}
3096 metadata = {}
3097 metadata['user'] = opts['user'] or ui.username()
3097 metadata['user'] = opts['user'] or ui.username()
3098 succs = tuple(parsenodeid(succ) for succ in successors)
3098 succs = tuple(parsenodeid(succ) for succ in successors)
3099 l = repo.lock()
3099 l = repo.lock()
3100 try:
3100 try:
3101 tr = repo.transaction('debugobsolete')
3101 tr = repo.transaction('debugobsolete')
3102 try:
3102 try:
3103 date = opts.get('date')
3103 date = opts.get('date')
3104 if date:
3104 if date:
3105 date = util.parsedate(date)
3105 date = util.parsedate(date)
3106 else:
3106 else:
3107 date = None
3107 date = None
3108 prec = parsenodeid(precursor)
3108 prec = parsenodeid(precursor)
3109 parents = None
3109 parents = None
3110 if opts['record_parents']:
3110 if opts['record_parents']:
3111 if prec not in repo.unfiltered():
3111 if prec not in repo.unfiltered():
3112 raise error.Abort('cannot used --record-parents on '
3112 raise error.Abort('cannot used --record-parents on '
3113 'unknown changesets')
3113 'unknown changesets')
3114 parents = repo.unfiltered()[prec].parents()
3114 parents = repo.unfiltered()[prec].parents()
3115 parents = tuple(p.node() for p in parents)
3115 parents = tuple(p.node() for p in parents)
3116 repo.obsstore.create(tr, prec, succs, opts['flags'],
3116 repo.obsstore.create(tr, prec, succs, opts['flags'],
3117 parents=parents, date=date,
3117 parents=parents, date=date,
3118 metadata=metadata)
3118 metadata=metadata)
3119 tr.close()
3119 tr.close()
3120 except ValueError as exc:
3120 except ValueError as exc:
3121 raise error.Abort(_('bad obsmarker input: %s') % exc)
3121 raise error.Abort(_('bad obsmarker input: %s') % exc)
3122 finally:
3122 finally:
3123 tr.release()
3123 tr.release()
3124 finally:
3124 finally:
3125 l.release()
3125 l.release()
3126 else:
3126 else:
3127 if opts['rev']:
3127 if opts['rev']:
3128 revs = scmutil.revrange(repo, opts['rev'])
3128 revs = scmutil.revrange(repo, opts['rev'])
3129 nodes = [repo[r].node() for r in revs]
3129 nodes = [repo[r].node() for r in revs]
3130 markers = list(obsolete.getmarkers(repo, nodes=nodes))
3130 markers = list(obsolete.getmarkers(repo, nodes=nodes))
3131 markers.sort(key=lambda x: x._data)
3131 markers.sort(key=lambda x: x._data)
3132 else:
3132 else:
3133 markers = obsolete.getmarkers(repo)
3133 markers = obsolete.getmarkers(repo)
3134
3134
3135 markerstoiter = markers
3135 markerstoiter = markers
3136 isrelevant = lambda m: True
3136 isrelevant = lambda m: True
3137 if opts.get('rev') and opts.get('index'):
3137 if opts.get('rev') and opts.get('index'):
3138 markerstoiter = obsolete.getmarkers(repo)
3138 markerstoiter = obsolete.getmarkers(repo)
3139 markerset = set(markers)
3139 markerset = set(markers)
3140 isrelevant = lambda m: m in markerset
3140 isrelevant = lambda m: m in markerset
3141
3141
3142 for i, m in enumerate(markerstoiter):
3142 for i, m in enumerate(markerstoiter):
3143 if not isrelevant(m):
3143 if not isrelevant(m):
3144 # marker can be irrelevant when we're iterating over a set
3144 # marker can be irrelevant when we're iterating over a set
3145 # of markers (markerstoiter) which is bigger than the set
3145 # of markers (markerstoiter) which is bigger than the set
3146 # of markers we want to display (markers)
3146 # of markers we want to display (markers)
3147 # this can happen if both --index and --rev options are
3147 # this can happen if both --index and --rev options are
3148 # provided and thus we need to iterate over all of the markers
3148 # provided and thus we need to iterate over all of the markers
3149 # to get the correct indices, but only display the ones that
3149 # to get the correct indices, but only display the ones that
3150 # are relevant to --rev value
3150 # are relevant to --rev value
3151 continue
3151 continue
3152 ind = i if opts.get('index') else None
3152 ind = i if opts.get('index') else None
3153 cmdutil.showmarker(ui, m, index=ind)
3153 cmdutil.showmarker(ui, m, index=ind)
3154
3154
3155 @command('debugpathcomplete',
3155 @command('debugpathcomplete',
3156 [('f', 'full', None, _('complete an entire path')),
3156 [('f', 'full', None, _('complete an entire path')),
3157 ('n', 'normal', None, _('show only normal files')),
3157 ('n', 'normal', None, _('show only normal files')),
3158 ('a', 'added', None, _('show only added files')),
3158 ('a', 'added', None, _('show only added files')),
3159 ('r', 'removed', None, _('show only removed files'))],
3159 ('r', 'removed', None, _('show only removed files'))],
3160 _('FILESPEC...'))
3160 _('FILESPEC...'))
3161 def debugpathcomplete(ui, repo, *specs, **opts):
3161 def debugpathcomplete(ui, repo, *specs, **opts):
3162 '''complete part or all of a tracked path
3162 '''complete part or all of a tracked path
3163
3163
3164 This command supports shells that offer path name completion. It
3164 This command supports shells that offer path name completion. It
3165 currently completes only files already known to the dirstate.
3165 currently completes only files already known to the dirstate.
3166
3166
3167 Completion extends only to the next path segment unless
3167 Completion extends only to the next path segment unless
3168 --full is specified, in which case entire paths are used.'''
3168 --full is specified, in which case entire paths are used.'''
3169
3169
3170 def complete(path, acceptable):
3170 def complete(path, acceptable):
3171 dirstate = repo.dirstate
3171 dirstate = repo.dirstate
3172 spec = os.path.normpath(os.path.join(os.getcwd(), path))
3172 spec = os.path.normpath(os.path.join(os.getcwd(), path))
3173 rootdir = repo.root + os.sep
3173 rootdir = repo.root + os.sep
3174 if spec != repo.root and not spec.startswith(rootdir):
3174 if spec != repo.root and not spec.startswith(rootdir):
3175 return [], []
3175 return [], []
3176 if os.path.isdir(spec):
3176 if os.path.isdir(spec):
3177 spec += '/'
3177 spec += '/'
3178 spec = spec[len(rootdir):]
3178 spec = spec[len(rootdir):]
3179 fixpaths = os.sep != '/'
3179 fixpaths = os.sep != '/'
3180 if fixpaths:
3180 if fixpaths:
3181 spec = spec.replace(os.sep, '/')
3181 spec = spec.replace(os.sep, '/')
3182 speclen = len(spec)
3182 speclen = len(spec)
3183 fullpaths = opts['full']
3183 fullpaths = opts['full']
3184 files, dirs = set(), set()
3184 files, dirs = set(), set()
3185 adddir, addfile = dirs.add, files.add
3185 adddir, addfile = dirs.add, files.add
3186 for f, st in dirstate.iteritems():
3186 for f, st in dirstate.iteritems():
3187 if f.startswith(spec) and st[0] in acceptable:
3187 if f.startswith(spec) and st[0] in acceptable:
3188 if fixpaths:
3188 if fixpaths:
3189 f = f.replace('/', os.sep)
3189 f = f.replace('/', os.sep)
3190 if fullpaths:
3190 if fullpaths:
3191 addfile(f)
3191 addfile(f)
3192 continue
3192 continue
3193 s = f.find(os.sep, speclen)
3193 s = f.find(os.sep, speclen)
3194 if s >= 0:
3194 if s >= 0:
3195 adddir(f[:s])
3195 adddir(f[:s])
3196 else:
3196 else:
3197 addfile(f)
3197 addfile(f)
3198 return files, dirs
3198 return files, dirs
3199
3199
3200 acceptable = ''
3200 acceptable = ''
3201 if opts['normal']:
3201 if opts['normal']:
3202 acceptable += 'nm'
3202 acceptable += 'nm'
3203 if opts['added']:
3203 if opts['added']:
3204 acceptable += 'a'
3204 acceptable += 'a'
3205 if opts['removed']:
3205 if opts['removed']:
3206 acceptable += 'r'
3206 acceptable += 'r'
3207 cwd = repo.getcwd()
3207 cwd = repo.getcwd()
3208 if not specs:
3208 if not specs:
3209 specs = ['.']
3209 specs = ['.']
3210
3210
3211 files, dirs = set(), set()
3211 files, dirs = set(), set()
3212 for spec in specs:
3212 for spec in specs:
3213 f, d = complete(spec, acceptable or 'nmar')
3213 f, d = complete(spec, acceptable or 'nmar')
3214 files.update(f)
3214 files.update(f)
3215 dirs.update(d)
3215 dirs.update(d)
3216 files.update(dirs)
3216 files.update(dirs)
3217 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
3217 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
3218 ui.write('\n')
3218 ui.write('\n')
3219
3219
3220 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
3220 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
3221 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
3221 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
3222 '''access the pushkey key/value protocol
3222 '''access the pushkey key/value protocol
3223
3223
3224 With two args, list the keys in the given namespace.
3224 With two args, list the keys in the given namespace.
3225
3225
3226 With five args, set a key to new if it currently is set to old.
3226 With five args, set a key to new if it currently is set to old.
3227 Reports success or failure.
3227 Reports success or failure.
3228 '''
3228 '''
3229
3229
3230 target = hg.peer(ui, {}, repopath)
3230 target = hg.peer(ui, {}, repopath)
3231 if keyinfo:
3231 if keyinfo:
3232 key, old, new = keyinfo
3232 key, old, new = keyinfo
3233 r = target.pushkey(namespace, key, old, new)
3233 r = target.pushkey(namespace, key, old, new)
3234 ui.status(str(r) + '\n')
3234 ui.status(str(r) + '\n')
3235 return not r
3235 return not r
3236 else:
3236 else:
3237 for k, v in sorted(target.listkeys(namespace).iteritems()):
3237 for k, v in sorted(target.listkeys(namespace).iteritems()):
3238 ui.write("%s\t%s\n" % (k.encode('string-escape'),
3238 ui.write("%s\t%s\n" % (k.encode('string-escape'),
3239 v.encode('string-escape')))
3239 v.encode('string-escape')))
3240
3240
3241 @command('debugpvec', [], _('A B'))
3241 @command('debugpvec', [], _('A B'))
3242 def debugpvec(ui, repo, a, b=None):
3242 def debugpvec(ui, repo, a, b=None):
3243 ca = scmutil.revsingle(repo, a)
3243 ca = scmutil.revsingle(repo, a)
3244 cb = scmutil.revsingle(repo, b)
3244 cb = scmutil.revsingle(repo, b)
3245 pa = pvec.ctxpvec(ca)
3245 pa = pvec.ctxpvec(ca)
3246 pb = pvec.ctxpvec(cb)
3246 pb = pvec.ctxpvec(cb)
3247 if pa == pb:
3247 if pa == pb:
3248 rel = "="
3248 rel = "="
3249 elif pa > pb:
3249 elif pa > pb:
3250 rel = ">"
3250 rel = ">"
3251 elif pa < pb:
3251 elif pa < pb:
3252 rel = "<"
3252 rel = "<"
3253 elif pa | pb:
3253 elif pa | pb:
3254 rel = "|"
3254 rel = "|"
3255 ui.write(_("a: %s\n") % pa)
3255 ui.write(_("a: %s\n") % pa)
3256 ui.write(_("b: %s\n") % pb)
3256 ui.write(_("b: %s\n") % pb)
3257 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
3257 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
3258 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
3258 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
3259 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
3259 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
3260 pa.distance(pb), rel))
3260 pa.distance(pb), rel))
3261
3261
3262 @command('debugrebuilddirstate|debugrebuildstate',
3262 @command('debugrebuilddirstate|debugrebuildstate',
3263 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
3263 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
3264 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
3264 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
3265 'the working copy parent')),
3265 'the working copy parent')),
3266 ],
3266 ],
3267 _('[-r REV]'))
3267 _('[-r REV]'))
3268 def debugrebuilddirstate(ui, repo, rev, **opts):
3268 def debugrebuilddirstate(ui, repo, rev, **opts):
3269 """rebuild the dirstate as it would look like for the given revision
3269 """rebuild the dirstate as it would look like for the given revision
3270
3270
3271 If no revision is specified the first current parent will be used.
3271 If no revision is specified the first current parent will be used.
3272
3272
3273 The dirstate will be set to the files of the given revision.
3273 The dirstate will be set to the files of the given revision.
3274 The actual working directory content or existing dirstate
3274 The actual working directory content or existing dirstate
3275 information such as adds or removes is not considered.
3275 information such as adds or removes is not considered.
3276
3276
3277 ``minimal`` will only rebuild the dirstate status for files that claim to be
3277 ``minimal`` will only rebuild the dirstate status for files that claim to be
3278 tracked but are not in the parent manifest, or that exist in the parent
3278 tracked but are not in the parent manifest, or that exist in the parent
3279 manifest but are not in the dirstate. It will not change adds, removes, or
3279 manifest but are not in the dirstate. It will not change adds, removes, or
3280 modified files that are in the working copy parent.
3280 modified files that are in the working copy parent.
3281
3281
3282 One use of this command is to make the next :hg:`status` invocation
3282 One use of this command is to make the next :hg:`status` invocation
3283 check the actual file content.
3283 check the actual file content.
3284 """
3284 """
3285 ctx = scmutil.revsingle(repo, rev)
3285 ctx = scmutil.revsingle(repo, rev)
3286 with repo.wlock():
3286 with repo.wlock():
3287 dirstate = repo.dirstate
3287 dirstate = repo.dirstate
3288 changedfiles = None
3288 changedfiles = None
3289 # See command doc for what minimal does.
3289 # See command doc for what minimal does.
3290 if opts.get('minimal'):
3290 if opts.get('minimal'):
3291 manifestfiles = set(ctx.manifest().keys())
3291 manifestfiles = set(ctx.manifest().keys())
3292 dirstatefiles = set(dirstate)
3292 dirstatefiles = set(dirstate)
3293 manifestonly = manifestfiles - dirstatefiles
3293 manifestonly = manifestfiles - dirstatefiles
3294 dsonly = dirstatefiles - manifestfiles
3294 dsonly = dirstatefiles - manifestfiles
3295 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
3295 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
3296 changedfiles = manifestonly | dsnotadded
3296 changedfiles = manifestonly | dsnotadded
3297
3297
3298 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
3298 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
3299
3299
3300 @command('debugrebuildfncache', [], '')
3300 @command('debugrebuildfncache', [], '')
3301 def debugrebuildfncache(ui, repo):
3301 def debugrebuildfncache(ui, repo):
3302 """rebuild the fncache file"""
3302 """rebuild the fncache file"""
3303 repair.rebuildfncache(ui, repo)
3303 repair.rebuildfncache(ui, repo)
3304
3304
3305 @command('debugrename',
3305 @command('debugrename',
3306 [('r', 'rev', '', _('revision to debug'), _('REV'))],
3306 [('r', 'rev', '', _('revision to debug'), _('REV'))],
3307 _('[-r REV] FILE'))
3307 _('[-r REV] FILE'))
3308 def debugrename(ui, repo, file1, *pats, **opts):
3308 def debugrename(ui, repo, file1, *pats, **opts):
3309 """dump rename information"""
3309 """dump rename information"""
3310
3310
3311 ctx = scmutil.revsingle(repo, opts.get('rev'))
3311 ctx = scmutil.revsingle(repo, opts.get('rev'))
3312 m = scmutil.match(ctx, (file1,) + pats, opts)
3312 m = scmutil.match(ctx, (file1,) + pats, opts)
3313 for abs in ctx.walk(m):
3313 for abs in ctx.walk(m):
3314 fctx = ctx[abs]
3314 fctx = ctx[abs]
3315 o = fctx.filelog().renamed(fctx.filenode())
3315 o = fctx.filelog().renamed(fctx.filenode())
3316 rel = m.rel(abs)
3316 rel = m.rel(abs)
3317 if o:
3317 if o:
3318 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
3318 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
3319 else:
3319 else:
3320 ui.write(_("%s not renamed\n") % rel)
3320 ui.write(_("%s not renamed\n") % rel)
3321
3321
3322 @command('debugrevlog', debugrevlogopts +
3322 @command('debugrevlog', debugrevlogopts +
3323 [('d', 'dump', False, _('dump index data'))],
3323 [('d', 'dump', False, _('dump index data'))],
3324 _('-c|-m|FILE'),
3324 _('-c|-m|FILE'),
3325 optionalrepo=True)
3325 optionalrepo=True)
3326 def debugrevlog(ui, repo, file_=None, **opts):
3326 def debugrevlog(ui, repo, file_=None, **opts):
3327 """show data and statistics about a revlog"""
3327 """show data and statistics about a revlog"""
3328 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
3328 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
3329
3329
3330 if opts.get("dump"):
3330 if opts.get("dump"):
3331 numrevs = len(r)
3331 numrevs = len(r)
3332 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
3332 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
3333 " rawsize totalsize compression heads chainlen\n")
3333 " rawsize totalsize compression heads chainlen\n"))
3334 ts = 0
3334 ts = 0
3335 heads = set()
3335 heads = set()
3336
3336
3337 for rev in xrange(numrevs):
3337 for rev in xrange(numrevs):
3338 dbase = r.deltaparent(rev)
3338 dbase = r.deltaparent(rev)
3339 if dbase == -1:
3339 if dbase == -1:
3340 dbase = rev
3340 dbase = rev
3341 cbase = r.chainbase(rev)
3341 cbase = r.chainbase(rev)
3342 clen = r.chainlen(rev)
3342 clen = r.chainlen(rev)
3343 p1, p2 = r.parentrevs(rev)
3343 p1, p2 = r.parentrevs(rev)
3344 rs = r.rawsize(rev)
3344 rs = r.rawsize(rev)
3345 ts = ts + rs
3345 ts = ts + rs
3346 heads -= set(r.parentrevs(rev))
3346 heads -= set(r.parentrevs(rev))
3347 heads.add(rev)
3347 heads.add(rev)
3348 try:
3348 try:
3349 compression = ts / r.end(rev)
3349 compression = ts / r.end(rev)
3350 except ZeroDivisionError:
3350 except ZeroDivisionError:
3351 compression = 0
3351 compression = 0
3352 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
3352 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
3353 "%11d %5d %8d\n" %
3353 "%11d %5d %8d\n" %
3354 (rev, p1, p2, r.start(rev), r.end(rev),
3354 (rev, p1, p2, r.start(rev), r.end(rev),
3355 r.start(dbase), r.start(cbase),
3355 r.start(dbase), r.start(cbase),
3356 r.start(p1), r.start(p2),
3356 r.start(p1), r.start(p2),
3357 rs, ts, compression, len(heads), clen))
3357 rs, ts, compression, len(heads), clen))
3358 return 0
3358 return 0
3359
3359
3360 v = r.version
3360 v = r.version
3361 format = v & 0xFFFF
3361 format = v & 0xFFFF
3362 flags = []
3362 flags = []
3363 gdelta = False
3363 gdelta = False
3364 if v & revlog.REVLOGNGINLINEDATA:
3364 if v & revlog.REVLOGNGINLINEDATA:
3365 flags.append('inline')
3365 flags.append('inline')
3366 if v & revlog.REVLOGGENERALDELTA:
3366 if v & revlog.REVLOGGENERALDELTA:
3367 gdelta = True
3367 gdelta = True
3368 flags.append('generaldelta')
3368 flags.append('generaldelta')
3369 if not flags:
3369 if not flags:
3370 flags = ['(none)']
3370 flags = ['(none)']
3371
3371
3372 nummerges = 0
3372 nummerges = 0
3373 numfull = 0
3373 numfull = 0
3374 numprev = 0
3374 numprev = 0
3375 nump1 = 0
3375 nump1 = 0
3376 nump2 = 0
3376 nump2 = 0
3377 numother = 0
3377 numother = 0
3378 nump1prev = 0
3378 nump1prev = 0
3379 nump2prev = 0
3379 nump2prev = 0
3380 chainlengths = []
3380 chainlengths = []
3381
3381
3382 datasize = [None, 0, 0L]
3382 datasize = [None, 0, 0L]
3383 fullsize = [None, 0, 0L]
3383 fullsize = [None, 0, 0L]
3384 deltasize = [None, 0, 0L]
3384 deltasize = [None, 0, 0L]
3385
3385
3386 def addsize(size, l):
3386 def addsize(size, l):
3387 if l[0] is None or size < l[0]:
3387 if l[0] is None or size < l[0]:
3388 l[0] = size
3388 l[0] = size
3389 if size > l[1]:
3389 if size > l[1]:
3390 l[1] = size
3390 l[1] = size
3391 l[2] += size
3391 l[2] += size
3392
3392
3393 numrevs = len(r)
3393 numrevs = len(r)
3394 for rev in xrange(numrevs):
3394 for rev in xrange(numrevs):
3395 p1, p2 = r.parentrevs(rev)
3395 p1, p2 = r.parentrevs(rev)
3396 delta = r.deltaparent(rev)
3396 delta = r.deltaparent(rev)
3397 if format > 0:
3397 if format > 0:
3398 addsize(r.rawsize(rev), datasize)
3398 addsize(r.rawsize(rev), datasize)
3399 if p2 != nullrev:
3399 if p2 != nullrev:
3400 nummerges += 1
3400 nummerges += 1
3401 size = r.length(rev)
3401 size = r.length(rev)
3402 if delta == nullrev:
3402 if delta == nullrev:
3403 chainlengths.append(0)
3403 chainlengths.append(0)
3404 numfull += 1
3404 numfull += 1
3405 addsize(size, fullsize)
3405 addsize(size, fullsize)
3406 else:
3406 else:
3407 chainlengths.append(chainlengths[delta] + 1)
3407 chainlengths.append(chainlengths[delta] + 1)
3408 addsize(size, deltasize)
3408 addsize(size, deltasize)
3409 if delta == rev - 1:
3409 if delta == rev - 1:
3410 numprev += 1
3410 numprev += 1
3411 if delta == p1:
3411 if delta == p1:
3412 nump1prev += 1
3412 nump1prev += 1
3413 elif delta == p2:
3413 elif delta == p2:
3414 nump2prev += 1
3414 nump2prev += 1
3415 elif delta == p1:
3415 elif delta == p1:
3416 nump1 += 1
3416 nump1 += 1
3417 elif delta == p2:
3417 elif delta == p2:
3418 nump2 += 1
3418 nump2 += 1
3419 elif delta != nullrev:
3419 elif delta != nullrev:
3420 numother += 1
3420 numother += 1
3421
3421
3422 # Adjust size min value for empty cases
3422 # Adjust size min value for empty cases
3423 for size in (datasize, fullsize, deltasize):
3423 for size in (datasize, fullsize, deltasize):
3424 if size[0] is None:
3424 if size[0] is None:
3425 size[0] = 0
3425 size[0] = 0
3426
3426
3427 numdeltas = numrevs - numfull
3427 numdeltas = numrevs - numfull
3428 numoprev = numprev - nump1prev - nump2prev
3428 numoprev = numprev - nump1prev - nump2prev
3429 totalrawsize = datasize[2]
3429 totalrawsize = datasize[2]
3430 datasize[2] /= numrevs
3430 datasize[2] /= numrevs
3431 fulltotal = fullsize[2]
3431 fulltotal = fullsize[2]
3432 fullsize[2] /= numfull
3432 fullsize[2] /= numfull
3433 deltatotal = deltasize[2]
3433 deltatotal = deltasize[2]
3434 if numrevs - numfull > 0:
3434 if numrevs - numfull > 0:
3435 deltasize[2] /= numrevs - numfull
3435 deltasize[2] /= numrevs - numfull
3436 totalsize = fulltotal + deltatotal
3436 totalsize = fulltotal + deltatotal
3437 avgchainlen = sum(chainlengths) / numrevs
3437 avgchainlen = sum(chainlengths) / numrevs
3438 maxchainlen = max(chainlengths)
3438 maxchainlen = max(chainlengths)
3439 compratio = 1
3439 compratio = 1
3440 if totalsize:
3440 if totalsize:
3441 compratio = totalrawsize / totalsize
3441 compratio = totalrawsize / totalsize
3442
3442
3443 basedfmtstr = '%%%dd\n'
3443 basedfmtstr = '%%%dd\n'
3444 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
3444 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
3445
3445
3446 def dfmtstr(max):
3446 def dfmtstr(max):
3447 return basedfmtstr % len(str(max))
3447 return basedfmtstr % len(str(max))
3448 def pcfmtstr(max, padding=0):
3448 def pcfmtstr(max, padding=0):
3449 return basepcfmtstr % (len(str(max)), ' ' * padding)
3449 return basepcfmtstr % (len(str(max)), ' ' * padding)
3450
3450
3451 def pcfmt(value, total):
3451 def pcfmt(value, total):
3452 if total:
3452 if total:
3453 return (value, 100 * float(value) / total)
3453 return (value, 100 * float(value) / total)
3454 else:
3454 else:
3455 return value, 100.0
3455 return value, 100.0
3456
3456
3457 ui.write(('format : %d\n') % format)
3457 ui.write(('format : %d\n') % format)
3458 ui.write(('flags : %s\n') % ', '.join(flags))
3458 ui.write(('flags : %s\n') % ', '.join(flags))
3459
3459
3460 ui.write('\n')
3460 ui.write('\n')
3461 fmt = pcfmtstr(totalsize)
3461 fmt = pcfmtstr(totalsize)
3462 fmt2 = dfmtstr(totalsize)
3462 fmt2 = dfmtstr(totalsize)
3463 ui.write(('revisions : ') + fmt2 % numrevs)
3463 ui.write(('revisions : ') + fmt2 % numrevs)
3464 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
3464 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
3465 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
3465 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
3466 ui.write(('revisions : ') + fmt2 % numrevs)
3466 ui.write(('revisions : ') + fmt2 % numrevs)
3467 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
3467 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
3468 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
3468 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
3469 ui.write(('revision size : ') + fmt2 % totalsize)
3469 ui.write(('revision size : ') + fmt2 % totalsize)
3470 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
3470 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
3471 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
3471 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
3472
3472
3473 ui.write('\n')
3473 ui.write('\n')
3474 fmt = dfmtstr(max(avgchainlen, compratio))
3474 fmt = dfmtstr(max(avgchainlen, compratio))
3475 ui.write(('avg chain length : ') + fmt % avgchainlen)
3475 ui.write(('avg chain length : ') + fmt % avgchainlen)
3476 ui.write(('max chain length : ') + fmt % maxchainlen)
3476 ui.write(('max chain length : ') + fmt % maxchainlen)
3477 ui.write(('compression ratio : ') + fmt % compratio)
3477 ui.write(('compression ratio : ') + fmt % compratio)
3478
3478
3479 if format > 0:
3479 if format > 0:
3480 ui.write('\n')
3480 ui.write('\n')
3481 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
3481 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
3482 % tuple(datasize))
3482 % tuple(datasize))
3483 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
3483 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
3484 % tuple(fullsize))
3484 % tuple(fullsize))
3485 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
3485 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
3486 % tuple(deltasize))
3486 % tuple(deltasize))
3487
3487
3488 if numdeltas > 0:
3488 if numdeltas > 0:
3489 ui.write('\n')
3489 ui.write('\n')
3490 fmt = pcfmtstr(numdeltas)
3490 fmt = pcfmtstr(numdeltas)
3491 fmt2 = pcfmtstr(numdeltas, 4)
3491 fmt2 = pcfmtstr(numdeltas, 4)
3492 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
3492 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
3493 if numprev > 0:
3493 if numprev > 0:
3494 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
3494 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
3495 numprev))
3495 numprev))
3496 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
3496 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
3497 numprev))
3497 numprev))
3498 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
3498 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
3499 numprev))
3499 numprev))
3500 if gdelta:
3500 if gdelta:
3501 ui.write(('deltas against p1 : ')
3501 ui.write(('deltas against p1 : ')
3502 + fmt % pcfmt(nump1, numdeltas))
3502 + fmt % pcfmt(nump1, numdeltas))
3503 ui.write(('deltas against p2 : ')
3503 ui.write(('deltas against p2 : ')
3504 + fmt % pcfmt(nump2, numdeltas))
3504 + fmt % pcfmt(nump2, numdeltas))
3505 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
3505 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
3506 numdeltas))
3506 numdeltas))
3507
3507
3508 @command('debugrevspec',
3508 @command('debugrevspec',
3509 [('', 'optimize', None, _('print parsed tree after optimizing'))],
3509 [('', 'optimize', None, _('print parsed tree after optimizing'))],
3510 ('REVSPEC'))
3510 ('REVSPEC'))
3511 def debugrevspec(ui, repo, expr, **opts):
3511 def debugrevspec(ui, repo, expr, **opts):
3512 """parse and apply a revision specification
3512 """parse and apply a revision specification
3513
3513
3514 Use --verbose to print the parsed tree before and after aliases
3514 Use --verbose to print the parsed tree before and after aliases
3515 expansion.
3515 expansion.
3516 """
3516 """
3517 if ui.verbose:
3517 if ui.verbose:
3518 tree = revset.parse(expr, lookup=repo.__contains__)
3518 tree = revset.parse(expr, lookup=repo.__contains__)
3519 ui.note(revset.prettyformat(tree), "\n")
3519 ui.note(revset.prettyformat(tree), "\n")
3520 newtree = revset.expandaliases(ui, tree)
3520 newtree = revset.expandaliases(ui, tree)
3521 if newtree != tree:
3521 if newtree != tree:
3522 ui.note("* expanded:\n", revset.prettyformat(newtree), "\n")
3522 ui.note(("* expanded:\n"), revset.prettyformat(newtree), "\n")
3523 tree = newtree
3523 tree = newtree
3524 newtree = revset.foldconcat(tree)
3524 newtree = revset.foldconcat(tree)
3525 if newtree != tree:
3525 if newtree != tree:
3526 ui.note("* concatenated:\n", revset.prettyformat(newtree), "\n")
3526 ui.note(("* concatenated:\n"), revset.prettyformat(newtree), "\n")
3527 if opts["optimize"]:
3527 if opts["optimize"]:
3528 optimizedtree = revset.optimize(newtree)
3528 optimizedtree = revset.optimize(newtree)
3529 ui.note("* optimized:\n", revset.prettyformat(optimizedtree), "\n")
3529 ui.note(("* optimized:\n"),
3530 revset.prettyformat(optimizedtree), "\n")
3530 func = revset.match(ui, expr, repo)
3531 func = revset.match(ui, expr, repo)
3531 revs = func(repo)
3532 revs = func(repo)
3532 if ui.verbose:
3533 if ui.verbose:
3533 ui.note("* set:\n", revset.prettyformatset(revs), "\n")
3534 ui.note(("* set:\n"), revset.prettyformatset(revs), "\n")
3534 for c in revs:
3535 for c in revs:
3535 ui.write("%s\n" % c)
3536 ui.write("%s\n" % c)
3536
3537
3537 @command('debugsetparents', [], _('REV1 [REV2]'))
3538 @command('debugsetparents', [], _('REV1 [REV2]'))
3538 def debugsetparents(ui, repo, rev1, rev2=None):
3539 def debugsetparents(ui, repo, rev1, rev2=None):
3539 """manually set the parents of the current working directory
3540 """manually set the parents of the current working directory
3540
3541
3541 This is useful for writing repository conversion tools, but should
3542 This is useful for writing repository conversion tools, but should
3542 be used with care. For example, neither the working directory nor the
3543 be used with care. For example, neither the working directory nor the
3543 dirstate is updated, so file status may be incorrect after running this
3544 dirstate is updated, so file status may be incorrect after running this
3544 command.
3545 command.
3545
3546
3546 Returns 0 on success.
3547 Returns 0 on success.
3547 """
3548 """
3548
3549
3549 r1 = scmutil.revsingle(repo, rev1).node()
3550 r1 = scmutil.revsingle(repo, rev1).node()
3550 r2 = scmutil.revsingle(repo, rev2, 'null').node()
3551 r2 = scmutil.revsingle(repo, rev2, 'null').node()
3551
3552
3552 with repo.wlock():
3553 with repo.wlock():
3553 repo.setparents(r1, r2)
3554 repo.setparents(r1, r2)
3554
3555
3555 @command('debugdirstate|debugstate',
3556 @command('debugdirstate|debugstate',
3556 [('', 'nodates', None, _('do not display the saved mtime')),
3557 [('', 'nodates', None, _('do not display the saved mtime')),
3557 ('', 'datesort', None, _('sort by saved mtime'))],
3558 ('', 'datesort', None, _('sort by saved mtime'))],
3558 _('[OPTION]...'))
3559 _('[OPTION]...'))
3559 def debugstate(ui, repo, **opts):
3560 def debugstate(ui, repo, **opts):
3560 """show the contents of the current dirstate"""
3561 """show the contents of the current dirstate"""
3561
3562
3562 nodates = opts.get('nodates')
3563 nodates = opts.get('nodates')
3563 datesort = opts.get('datesort')
3564 datesort = opts.get('datesort')
3564
3565
3565 timestr = ""
3566 timestr = ""
3566 if datesort:
3567 if datesort:
3567 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
3568 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
3568 else:
3569 else:
3569 keyfunc = None # sort by filename
3570 keyfunc = None # sort by filename
3570 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
3571 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
3571 if ent[3] == -1:
3572 if ent[3] == -1:
3572 timestr = 'unset '
3573 timestr = 'unset '
3573 elif nodates:
3574 elif nodates:
3574 timestr = 'set '
3575 timestr = 'set '
3575 else:
3576 else:
3576 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
3577 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
3577 time.localtime(ent[3]))
3578 time.localtime(ent[3]))
3578 if ent[1] & 0o20000:
3579 if ent[1] & 0o20000:
3579 mode = 'lnk'
3580 mode = 'lnk'
3580 else:
3581 else:
3581 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
3582 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
3582 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
3583 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
3583 for f in repo.dirstate.copies():
3584 for f in repo.dirstate.copies():
3584 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
3585 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
3585
3586
3586 @command('debugsub',
3587 @command('debugsub',
3587 [('r', 'rev', '',
3588 [('r', 'rev', '',
3588 _('revision to check'), _('REV'))],
3589 _('revision to check'), _('REV'))],
3589 _('[-r REV] [REV]'))
3590 _('[-r REV] [REV]'))
3590 def debugsub(ui, repo, rev=None):
3591 def debugsub(ui, repo, rev=None):
3591 ctx = scmutil.revsingle(repo, rev, None)
3592 ctx = scmutil.revsingle(repo, rev, None)
3592 for k, v in sorted(ctx.substate.items()):
3593 for k, v in sorted(ctx.substate.items()):
3593 ui.write(('path %s\n') % k)
3594 ui.write(('path %s\n') % k)
3594 ui.write((' source %s\n') % v[0])
3595 ui.write((' source %s\n') % v[0])
3595 ui.write((' revision %s\n') % v[1])
3596 ui.write((' revision %s\n') % v[1])
3596
3597
3597 @command('debugsuccessorssets',
3598 @command('debugsuccessorssets',
3598 [],
3599 [],
3599 _('[REV]'))
3600 _('[REV]'))
3600 def debugsuccessorssets(ui, repo, *revs):
3601 def debugsuccessorssets(ui, repo, *revs):
3601 """show set of successors for revision
3602 """show set of successors for revision
3602
3603
3603 A successors set of changeset A is a consistent group of revisions that
3604 A successors set of changeset A is a consistent group of revisions that
3604 succeed A. It contains non-obsolete changesets only.
3605 succeed A. It contains non-obsolete changesets only.
3605
3606
3606 In most cases a changeset A has a single successors set containing a single
3607 In most cases a changeset A has a single successors set containing a single
3607 successor (changeset A replaced by A').
3608 successor (changeset A replaced by A').
3608
3609
3609 A changeset that is made obsolete with no successors are called "pruned".
3610 A changeset that is made obsolete with no successors are called "pruned".
3610 Such changesets have no successors sets at all.
3611 Such changesets have no successors sets at all.
3611
3612
3612 A changeset that has been "split" will have a successors set containing
3613 A changeset that has been "split" will have a successors set containing
3613 more than one successor.
3614 more than one successor.
3614
3615
3615 A changeset that has been rewritten in multiple different ways is called
3616 A changeset that has been rewritten in multiple different ways is called
3616 "divergent". Such changesets have multiple successor sets (each of which
3617 "divergent". Such changesets have multiple successor sets (each of which
3617 may also be split, i.e. have multiple successors).
3618 may also be split, i.e. have multiple successors).
3618
3619
3619 Results are displayed as follows::
3620 Results are displayed as follows::
3620
3621
3621 <rev1>
3622 <rev1>
3622 <successors-1A>
3623 <successors-1A>
3623 <rev2>
3624 <rev2>
3624 <successors-2A>
3625 <successors-2A>
3625 <successors-2B1> <successors-2B2> <successors-2B3>
3626 <successors-2B1> <successors-2B2> <successors-2B3>
3626
3627
3627 Here rev2 has two possible (i.e. divergent) successors sets. The first
3628 Here rev2 has two possible (i.e. divergent) successors sets. The first
3628 holds one element, whereas the second holds three (i.e. the changeset has
3629 holds one element, whereas the second holds three (i.e. the changeset has
3629 been split).
3630 been split).
3630 """
3631 """
3631 # passed to successorssets caching computation from one call to another
3632 # passed to successorssets caching computation from one call to another
3632 cache = {}
3633 cache = {}
3633 ctx2str = str
3634 ctx2str = str
3634 node2str = short
3635 node2str = short
3635 if ui.debug():
3636 if ui.debug():
3636 def ctx2str(ctx):
3637 def ctx2str(ctx):
3637 return ctx.hex()
3638 return ctx.hex()
3638 node2str = hex
3639 node2str = hex
3639 for rev in scmutil.revrange(repo, revs):
3640 for rev in scmutil.revrange(repo, revs):
3640 ctx = repo[rev]
3641 ctx = repo[rev]
3641 ui.write('%s\n'% ctx2str(ctx))
3642 ui.write('%s\n'% ctx2str(ctx))
3642 for succsset in obsolete.successorssets(repo, ctx.node(), cache):
3643 for succsset in obsolete.successorssets(repo, ctx.node(), cache):
3643 if succsset:
3644 if succsset:
3644 ui.write(' ')
3645 ui.write(' ')
3645 ui.write(node2str(succsset[0]))
3646 ui.write(node2str(succsset[0]))
3646 for node in succsset[1:]:
3647 for node in succsset[1:]:
3647 ui.write(' ')
3648 ui.write(' ')
3648 ui.write(node2str(node))
3649 ui.write(node2str(node))
3649 ui.write('\n')
3650 ui.write('\n')
3650
3651
3651 @command('debugtemplate',
3652 @command('debugtemplate',
3652 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
3653 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
3653 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
3654 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
3654 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
3655 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
3655 optionalrepo=True)
3656 optionalrepo=True)
3656 def debugtemplate(ui, repo, tmpl, **opts):
3657 def debugtemplate(ui, repo, tmpl, **opts):
3657 """parse and apply a template
3658 """parse and apply a template
3658
3659
3659 If -r/--rev is given, the template is processed as a log template and
3660 If -r/--rev is given, the template is processed as a log template and
3660 applied to the given changesets. Otherwise, it is processed as a generic
3661 applied to the given changesets. Otherwise, it is processed as a generic
3661 template.
3662 template.
3662
3663
3663 Use --verbose to print the parsed tree.
3664 Use --verbose to print the parsed tree.
3664 """
3665 """
3665 revs = None
3666 revs = None
3666 if opts['rev']:
3667 if opts['rev']:
3667 if repo is None:
3668 if repo is None:
3668 raise error.RepoError(_('there is no Mercurial repository here '
3669 raise error.RepoError(_('there is no Mercurial repository here '
3669 '(.hg not found)'))
3670 '(.hg not found)'))
3670 revs = scmutil.revrange(repo, opts['rev'])
3671 revs = scmutil.revrange(repo, opts['rev'])
3671
3672
3672 props = {}
3673 props = {}
3673 for d in opts['define']:
3674 for d in opts['define']:
3674 try:
3675 try:
3675 k, v = (e.strip() for e in d.split('=', 1))
3676 k, v = (e.strip() for e in d.split('=', 1))
3676 if not k:
3677 if not k:
3677 raise ValueError
3678 raise ValueError
3678 props[k] = v
3679 props[k] = v
3679 except ValueError:
3680 except ValueError:
3680 raise error.Abort(_('malformed keyword definition: %s') % d)
3681 raise error.Abort(_('malformed keyword definition: %s') % d)
3681
3682
3682 if ui.verbose:
3683 if ui.verbose:
3683 aliases = ui.configitems('templatealias')
3684 aliases = ui.configitems('templatealias')
3684 tree = templater.parse(tmpl)
3685 tree = templater.parse(tmpl)
3685 ui.note(templater.prettyformat(tree), '\n')
3686 ui.note(templater.prettyformat(tree), '\n')
3686 newtree = templater.expandaliases(tree, aliases)
3687 newtree = templater.expandaliases(tree, aliases)
3687 if newtree != tree:
3688 if newtree != tree:
3688 ui.note("* expanded:\n", templater.prettyformat(newtree), '\n')
3689 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
3689
3690
3690 mapfile = None
3691 mapfile = None
3691 if revs is None:
3692 if revs is None:
3692 k = 'debugtemplate'
3693 k = 'debugtemplate'
3693 t = formatter.maketemplater(ui, k, tmpl)
3694 t = formatter.maketemplater(ui, k, tmpl)
3694 ui.write(templater.stringify(t(k, **props)))
3695 ui.write(templater.stringify(t(k, **props)))
3695 else:
3696 else:
3696 displayer = cmdutil.changeset_templater(ui, repo, None, opts, tmpl,
3697 displayer = cmdutil.changeset_templater(ui, repo, None, opts, tmpl,
3697 mapfile, buffered=False)
3698 mapfile, buffered=False)
3698 for r in revs:
3699 for r in revs:
3699 displayer.show(repo[r], **props)
3700 displayer.show(repo[r], **props)
3700 displayer.close()
3701 displayer.close()
3701
3702
3702 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'), inferrepo=True)
3703 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'), inferrepo=True)
3703 def debugwalk(ui, repo, *pats, **opts):
3704 def debugwalk(ui, repo, *pats, **opts):
3704 """show how files match on given patterns"""
3705 """show how files match on given patterns"""
3705 m = scmutil.match(repo[None], pats, opts)
3706 m = scmutil.match(repo[None], pats, opts)
3706 items = list(repo.walk(m))
3707 items = list(repo.walk(m))
3707 if not items:
3708 if not items:
3708 return
3709 return
3709 f = lambda fn: fn
3710 f = lambda fn: fn
3710 if ui.configbool('ui', 'slash') and os.sep != '/':
3711 if ui.configbool('ui', 'slash') and os.sep != '/':
3711 f = lambda fn: util.normpath(fn)
3712 f = lambda fn: util.normpath(fn)
3712 fmt = 'f %%-%ds %%-%ds %%s' % (
3713 fmt = 'f %%-%ds %%-%ds %%s' % (
3713 max([len(abs) for abs in items]),
3714 max([len(abs) for abs in items]),
3714 max([len(m.rel(abs)) for abs in items]))
3715 max([len(m.rel(abs)) for abs in items]))
3715 for abs in items:
3716 for abs in items:
3716 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
3717 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
3717 ui.write("%s\n" % line.rstrip())
3718 ui.write("%s\n" % line.rstrip())
3718
3719
3719 @command('debugwireargs',
3720 @command('debugwireargs',
3720 [('', 'three', '', 'three'),
3721 [('', 'three', '', 'three'),
3721 ('', 'four', '', 'four'),
3722 ('', 'four', '', 'four'),
3722 ('', 'five', '', 'five'),
3723 ('', 'five', '', 'five'),
3723 ] + remoteopts,
3724 ] + remoteopts,
3724 _('REPO [OPTIONS]... [ONE [TWO]]'),
3725 _('REPO [OPTIONS]... [ONE [TWO]]'),
3725 norepo=True)
3726 norepo=True)
3726 def debugwireargs(ui, repopath, *vals, **opts):
3727 def debugwireargs(ui, repopath, *vals, **opts):
3727 repo = hg.peer(ui, opts, repopath)
3728 repo = hg.peer(ui, opts, repopath)
3728 for opt in remoteopts:
3729 for opt in remoteopts:
3729 del opts[opt[1]]
3730 del opts[opt[1]]
3730 args = {}
3731 args = {}
3731 for k, v in opts.iteritems():
3732 for k, v in opts.iteritems():
3732 if v:
3733 if v:
3733 args[k] = v
3734 args[k] = v
3734 # run twice to check that we don't mess up the stream for the next command
3735 # run twice to check that we don't mess up the stream for the next command
3735 res1 = repo.debugwireargs(*vals, **args)
3736 res1 = repo.debugwireargs(*vals, **args)
3736 res2 = repo.debugwireargs(*vals, **args)
3737 res2 = repo.debugwireargs(*vals, **args)
3737 ui.write("%s\n" % res1)
3738 ui.write("%s\n" % res1)
3738 if res1 != res2:
3739 if res1 != res2:
3739 ui.warn("%s\n" % res2)
3740 ui.warn("%s\n" % res2)
3740
3741
3741 @command('^diff',
3742 @command('^diff',
3742 [('r', 'rev', [], _('revision'), _('REV')),
3743 [('r', 'rev', [], _('revision'), _('REV')),
3743 ('c', 'change', '', _('change made by revision'), _('REV'))
3744 ('c', 'change', '', _('change made by revision'), _('REV'))
3744 ] + diffopts + diffopts2 + walkopts + subrepoopts,
3745 ] + diffopts + diffopts2 + walkopts + subrepoopts,
3745 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
3746 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
3746 inferrepo=True)
3747 inferrepo=True)
3747 def diff(ui, repo, *pats, **opts):
3748 def diff(ui, repo, *pats, **opts):
3748 """diff repository (or selected files)
3749 """diff repository (or selected files)
3749
3750
3750 Show differences between revisions for the specified files.
3751 Show differences between revisions for the specified files.
3751
3752
3752 Differences between files are shown using the unified diff format.
3753 Differences between files are shown using the unified diff format.
3753
3754
3754 .. note::
3755 .. note::
3755
3756
3756 :hg:`diff` may generate unexpected results for merges, as it will
3757 :hg:`diff` may generate unexpected results for merges, as it will
3757 default to comparing against the working directory's first
3758 default to comparing against the working directory's first
3758 parent changeset if no revisions are specified.
3759 parent changeset if no revisions are specified.
3759
3760
3760 When two revision arguments are given, then changes are shown
3761 When two revision arguments are given, then changes are shown
3761 between those revisions. If only one revision is specified then
3762 between those revisions. If only one revision is specified then
3762 that revision is compared to the working directory, and, when no
3763 that revision is compared to the working directory, and, when no
3763 revisions are specified, the working directory files are compared
3764 revisions are specified, the working directory files are compared
3764 to its first parent.
3765 to its first parent.
3765
3766
3766 Alternatively you can specify -c/--change with a revision to see
3767 Alternatively you can specify -c/--change with a revision to see
3767 the changes in that changeset relative to its first parent.
3768 the changes in that changeset relative to its first parent.
3768
3769
3769 Without the -a/--text option, diff will avoid generating diffs of
3770 Without the -a/--text option, diff will avoid generating diffs of
3770 files it detects as binary. With -a, diff will generate a diff
3771 files it detects as binary. With -a, diff will generate a diff
3771 anyway, probably with undesirable results.
3772 anyway, probably with undesirable results.
3772
3773
3773 Use the -g/--git option to generate diffs in the git extended diff
3774 Use the -g/--git option to generate diffs in the git extended diff
3774 format. For more information, read :hg:`help diffs`.
3775 format. For more information, read :hg:`help diffs`.
3775
3776
3776 .. container:: verbose
3777 .. container:: verbose
3777
3778
3778 Examples:
3779 Examples:
3779
3780
3780 - compare a file in the current working directory to its parent::
3781 - compare a file in the current working directory to its parent::
3781
3782
3782 hg diff foo.c
3783 hg diff foo.c
3783
3784
3784 - compare two historical versions of a directory, with rename info::
3785 - compare two historical versions of a directory, with rename info::
3785
3786
3786 hg diff --git -r 1.0:1.2 lib/
3787 hg diff --git -r 1.0:1.2 lib/
3787
3788
3788 - get change stats relative to the last change on some date::
3789 - get change stats relative to the last change on some date::
3789
3790
3790 hg diff --stat -r "date('may 2')"
3791 hg diff --stat -r "date('may 2')"
3791
3792
3792 - diff all newly-added files that contain a keyword::
3793 - diff all newly-added files that contain a keyword::
3793
3794
3794 hg diff "set:added() and grep(GNU)"
3795 hg diff "set:added() and grep(GNU)"
3795
3796
3796 - compare a revision and its parents::
3797 - compare a revision and its parents::
3797
3798
3798 hg diff -c 9353 # compare against first parent
3799 hg diff -c 9353 # compare against first parent
3799 hg diff -r 9353^:9353 # same using revset syntax
3800 hg diff -r 9353^:9353 # same using revset syntax
3800 hg diff -r 9353^2:9353 # compare against the second parent
3801 hg diff -r 9353^2:9353 # compare against the second parent
3801
3802
3802 Returns 0 on success.
3803 Returns 0 on success.
3803 """
3804 """
3804
3805
3805 revs = opts.get('rev')
3806 revs = opts.get('rev')
3806 change = opts.get('change')
3807 change = opts.get('change')
3807 stat = opts.get('stat')
3808 stat = opts.get('stat')
3808 reverse = opts.get('reverse')
3809 reverse = opts.get('reverse')
3809
3810
3810 if revs and change:
3811 if revs and change:
3811 msg = _('cannot specify --rev and --change at the same time')
3812 msg = _('cannot specify --rev and --change at the same time')
3812 raise error.Abort(msg)
3813 raise error.Abort(msg)
3813 elif change:
3814 elif change:
3814 node2 = scmutil.revsingle(repo, change, None).node()
3815 node2 = scmutil.revsingle(repo, change, None).node()
3815 node1 = repo[node2].p1().node()
3816 node1 = repo[node2].p1().node()
3816 else:
3817 else:
3817 node1, node2 = scmutil.revpair(repo, revs)
3818 node1, node2 = scmutil.revpair(repo, revs)
3818
3819
3819 if reverse:
3820 if reverse:
3820 node1, node2 = node2, node1
3821 node1, node2 = node2, node1
3821
3822
3822 diffopts = patch.diffallopts(ui, opts)
3823 diffopts = patch.diffallopts(ui, opts)
3823 m = scmutil.match(repo[node2], pats, opts)
3824 m = scmutil.match(repo[node2], pats, opts)
3824 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
3825 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
3825 listsubrepos=opts.get('subrepos'),
3826 listsubrepos=opts.get('subrepos'),
3826 root=opts.get('root'))
3827 root=opts.get('root'))
3827
3828
3828 @command('^export',
3829 @command('^export',
3829 [('o', 'output', '',
3830 [('o', 'output', '',
3830 _('print output to file with formatted name'), _('FORMAT')),
3831 _('print output to file with formatted name'), _('FORMAT')),
3831 ('', 'switch-parent', None, _('diff against the second parent')),
3832 ('', 'switch-parent', None, _('diff against the second parent')),
3832 ('r', 'rev', [], _('revisions to export'), _('REV')),
3833 ('r', 'rev', [], _('revisions to export'), _('REV')),
3833 ] + diffopts,
3834 ] + diffopts,
3834 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
3835 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
3835 def export(ui, repo, *changesets, **opts):
3836 def export(ui, repo, *changesets, **opts):
3836 """dump the header and diffs for one or more changesets
3837 """dump the header and diffs for one or more changesets
3837
3838
3838 Print the changeset header and diffs for one or more revisions.
3839 Print the changeset header and diffs for one or more revisions.
3839 If no revision is given, the parent of the working directory is used.
3840 If no revision is given, the parent of the working directory is used.
3840
3841
3841 The information shown in the changeset header is: author, date,
3842 The information shown in the changeset header is: author, date,
3842 branch name (if non-default), changeset hash, parent(s) and commit
3843 branch name (if non-default), changeset hash, parent(s) and commit
3843 comment.
3844 comment.
3844
3845
3845 .. note::
3846 .. note::
3846
3847
3847 :hg:`export` may generate unexpected diff output for merge
3848 :hg:`export` may generate unexpected diff output for merge
3848 changesets, as it will compare the merge changeset against its
3849 changesets, as it will compare the merge changeset against its
3849 first parent only.
3850 first parent only.
3850
3851
3851 Output may be to a file, in which case the name of the file is
3852 Output may be to a file, in which case the name of the file is
3852 given using a format string. The formatting rules are as follows:
3853 given using a format string. The formatting rules are as follows:
3853
3854
3854 :``%%``: literal "%" character
3855 :``%%``: literal "%" character
3855 :``%H``: changeset hash (40 hexadecimal digits)
3856 :``%H``: changeset hash (40 hexadecimal digits)
3856 :``%N``: number of patches being generated
3857 :``%N``: number of patches being generated
3857 :``%R``: changeset revision number
3858 :``%R``: changeset revision number
3858 :``%b``: basename of the exporting repository
3859 :``%b``: basename of the exporting repository
3859 :``%h``: short-form changeset hash (12 hexadecimal digits)
3860 :``%h``: short-form changeset hash (12 hexadecimal digits)
3860 :``%m``: first line of the commit message (only alphanumeric characters)
3861 :``%m``: first line of the commit message (only alphanumeric characters)
3861 :``%n``: zero-padded sequence number, starting at 1
3862 :``%n``: zero-padded sequence number, starting at 1
3862 :``%r``: zero-padded changeset revision number
3863 :``%r``: zero-padded changeset revision number
3863
3864
3864 Without the -a/--text option, export will avoid generating diffs
3865 Without the -a/--text option, export will avoid generating diffs
3865 of files it detects as binary. With -a, export will generate a
3866 of files it detects as binary. With -a, export will generate a
3866 diff anyway, probably with undesirable results.
3867 diff anyway, probably with undesirable results.
3867
3868
3868 Use the -g/--git option to generate diffs in the git extended diff
3869 Use the -g/--git option to generate diffs in the git extended diff
3869 format. See :hg:`help diffs` for more information.
3870 format. See :hg:`help diffs` for more information.
3870
3871
3871 With the --switch-parent option, the diff will be against the
3872 With the --switch-parent option, the diff will be against the
3872 second parent. It can be useful to review a merge.
3873 second parent. It can be useful to review a merge.
3873
3874
3874 .. container:: verbose
3875 .. container:: verbose
3875
3876
3876 Examples:
3877 Examples:
3877
3878
3878 - use export and import to transplant a bugfix to the current
3879 - use export and import to transplant a bugfix to the current
3879 branch::
3880 branch::
3880
3881
3881 hg export -r 9353 | hg import -
3882 hg export -r 9353 | hg import -
3882
3883
3883 - export all the changesets between two revisions to a file with
3884 - export all the changesets between two revisions to a file with
3884 rename information::
3885 rename information::
3885
3886
3886 hg export --git -r 123:150 > changes.txt
3887 hg export --git -r 123:150 > changes.txt
3887
3888
3888 - split outgoing changes into a series of patches with
3889 - split outgoing changes into a series of patches with
3889 descriptive names::
3890 descriptive names::
3890
3891
3891 hg export -r "outgoing()" -o "%n-%m.patch"
3892 hg export -r "outgoing()" -o "%n-%m.patch"
3892
3893
3893 Returns 0 on success.
3894 Returns 0 on success.
3894 """
3895 """
3895 changesets += tuple(opts.get('rev', []))
3896 changesets += tuple(opts.get('rev', []))
3896 if not changesets:
3897 if not changesets:
3897 changesets = ['.']
3898 changesets = ['.']
3898 revs = scmutil.revrange(repo, changesets)
3899 revs = scmutil.revrange(repo, changesets)
3899 if not revs:
3900 if not revs:
3900 raise error.Abort(_("export requires at least one changeset"))
3901 raise error.Abort(_("export requires at least one changeset"))
3901 if len(revs) > 1:
3902 if len(revs) > 1:
3902 ui.note(_('exporting patches:\n'))
3903 ui.note(_('exporting patches:\n'))
3903 else:
3904 else:
3904 ui.note(_('exporting patch:\n'))
3905 ui.note(_('exporting patch:\n'))
3905 cmdutil.export(repo, revs, template=opts.get('output'),
3906 cmdutil.export(repo, revs, template=opts.get('output'),
3906 switch_parent=opts.get('switch_parent'),
3907 switch_parent=opts.get('switch_parent'),
3907 opts=patch.diffallopts(ui, opts))
3908 opts=patch.diffallopts(ui, opts))
3908
3909
3909 @command('files',
3910 @command('files',
3910 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3911 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3911 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3912 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3912 ] + walkopts + formatteropts + subrepoopts,
3913 ] + walkopts + formatteropts + subrepoopts,
3913 _('[OPTION]... [PATTERN]...'))
3914 _('[OPTION]... [PATTERN]...'))
3914 def files(ui, repo, *pats, **opts):
3915 def files(ui, repo, *pats, **opts):
3915 """list tracked files
3916 """list tracked files
3916
3917
3917 Print files under Mercurial control in the working directory or
3918 Print files under Mercurial control in the working directory or
3918 specified revision whose names match the given patterns (excluding
3919 specified revision whose names match the given patterns (excluding
3919 removed files).
3920 removed files).
3920
3921
3921 If no patterns are given to match, this command prints the names
3922 If no patterns are given to match, this command prints the names
3922 of all files under Mercurial control in the working directory.
3923 of all files under Mercurial control in the working directory.
3923
3924
3924 .. container:: verbose
3925 .. container:: verbose
3925
3926
3926 Examples:
3927 Examples:
3927
3928
3928 - list all files under the current directory::
3929 - list all files under the current directory::
3929
3930
3930 hg files .
3931 hg files .
3931
3932
3932 - shows sizes and flags for current revision::
3933 - shows sizes and flags for current revision::
3933
3934
3934 hg files -vr .
3935 hg files -vr .
3935
3936
3936 - list all files named README::
3937 - list all files named README::
3937
3938
3938 hg files -I "**/README"
3939 hg files -I "**/README"
3939
3940
3940 - list all binary files::
3941 - list all binary files::
3941
3942
3942 hg files "set:binary()"
3943 hg files "set:binary()"
3943
3944
3944 - find files containing a regular expression::
3945 - find files containing a regular expression::
3945
3946
3946 hg files "set:grep('bob')"
3947 hg files "set:grep('bob')"
3947
3948
3948 - search tracked file contents with xargs and grep::
3949 - search tracked file contents with xargs and grep::
3949
3950
3950 hg files -0 | xargs -0 grep foo
3951 hg files -0 | xargs -0 grep foo
3951
3952
3952 See :hg:`help patterns` and :hg:`help filesets` for more information
3953 See :hg:`help patterns` and :hg:`help filesets` for more information
3953 on specifying file patterns.
3954 on specifying file patterns.
3954
3955
3955 Returns 0 if a match is found, 1 otherwise.
3956 Returns 0 if a match is found, 1 otherwise.
3956
3957
3957 """
3958 """
3958 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3959 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3959
3960
3960 end = '\n'
3961 end = '\n'
3961 if opts.get('print0'):
3962 if opts.get('print0'):
3962 end = '\0'
3963 end = '\0'
3963 fm = ui.formatter('files', opts)
3964 fm = ui.formatter('files', opts)
3964 fmt = '%s' + end
3965 fmt = '%s' + end
3965
3966
3966 m = scmutil.match(ctx, pats, opts)
3967 m = scmutil.match(ctx, pats, opts)
3967 ret = cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
3968 ret = cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
3968
3969
3969 fm.end()
3970 fm.end()
3970
3971
3971 return ret
3972 return ret
3972
3973
3973 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
3974 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
3974 def forget(ui, repo, *pats, **opts):
3975 def forget(ui, repo, *pats, **opts):
3975 """forget the specified files on the next commit
3976 """forget the specified files on the next commit
3976
3977
3977 Mark the specified files so they will no longer be tracked
3978 Mark the specified files so they will no longer be tracked
3978 after the next commit.
3979 after the next commit.
3979
3980
3980 This only removes files from the current branch, not from the
3981 This only removes files from the current branch, not from the
3981 entire project history, and it does not delete them from the
3982 entire project history, and it does not delete them from the
3982 working directory.
3983 working directory.
3983
3984
3984 To delete the file from the working directory, see :hg:`remove`.
3985 To delete the file from the working directory, see :hg:`remove`.
3985
3986
3986 To undo a forget before the next commit, see :hg:`add`.
3987 To undo a forget before the next commit, see :hg:`add`.
3987
3988
3988 .. container:: verbose
3989 .. container:: verbose
3989
3990
3990 Examples:
3991 Examples:
3991
3992
3992 - forget newly-added binary files::
3993 - forget newly-added binary files::
3993
3994
3994 hg forget "set:added() and binary()"
3995 hg forget "set:added() and binary()"
3995
3996
3996 - forget files that would be excluded by .hgignore::
3997 - forget files that would be excluded by .hgignore::
3997
3998
3998 hg forget "set:hgignore()"
3999 hg forget "set:hgignore()"
3999
4000
4000 Returns 0 on success.
4001 Returns 0 on success.
4001 """
4002 """
4002
4003
4003 if not pats:
4004 if not pats:
4004 raise error.Abort(_('no files specified'))
4005 raise error.Abort(_('no files specified'))
4005
4006
4006 m = scmutil.match(repo[None], pats, opts)
4007 m = scmutil.match(repo[None], pats, opts)
4007 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
4008 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
4008 return rejected and 1 or 0
4009 return rejected and 1 or 0
4009
4010
4010 @command(
4011 @command(
4011 'graft',
4012 'graft',
4012 [('r', 'rev', [], _('revisions to graft'), _('REV')),
4013 [('r', 'rev', [], _('revisions to graft'), _('REV')),
4013 ('c', 'continue', False, _('resume interrupted graft')),
4014 ('c', 'continue', False, _('resume interrupted graft')),
4014 ('e', 'edit', False, _('invoke editor on commit messages')),
4015 ('e', 'edit', False, _('invoke editor on commit messages')),
4015 ('', 'log', None, _('append graft info to log message')),
4016 ('', 'log', None, _('append graft info to log message')),
4016 ('f', 'force', False, _('force graft')),
4017 ('f', 'force', False, _('force graft')),
4017 ('D', 'currentdate', False,
4018 ('D', 'currentdate', False,
4018 _('record the current date as commit date')),
4019 _('record the current date as commit date')),
4019 ('U', 'currentuser', False,
4020 ('U', 'currentuser', False,
4020 _('record the current user as committer'), _('DATE'))]
4021 _('record the current user as committer'), _('DATE'))]
4021 + commitopts2 + mergetoolopts + dryrunopts,
4022 + commitopts2 + mergetoolopts + dryrunopts,
4022 _('[OPTION]... [-r REV]... REV...'))
4023 _('[OPTION]... [-r REV]... REV...'))
4023 def graft(ui, repo, *revs, **opts):
4024 def graft(ui, repo, *revs, **opts):
4024 '''copy changes from other branches onto the current branch
4025 '''copy changes from other branches onto the current branch
4025
4026
4026 This command uses Mercurial's merge logic to copy individual
4027 This command uses Mercurial's merge logic to copy individual
4027 changes from other branches without merging branches in the
4028 changes from other branches without merging branches in the
4028 history graph. This is sometimes known as 'backporting' or
4029 history graph. This is sometimes known as 'backporting' or
4029 'cherry-picking'. By default, graft will copy user, date, and
4030 'cherry-picking'. By default, graft will copy user, date, and
4030 description from the source changesets.
4031 description from the source changesets.
4031
4032
4032 Changesets that are ancestors of the current revision, that have
4033 Changesets that are ancestors of the current revision, that have
4033 already been grafted, or that are merges will be skipped.
4034 already been grafted, or that are merges will be skipped.
4034
4035
4035 If --log is specified, log messages will have a comment appended
4036 If --log is specified, log messages will have a comment appended
4036 of the form::
4037 of the form::
4037
4038
4038 (grafted from CHANGESETHASH)
4039 (grafted from CHANGESETHASH)
4039
4040
4040 If --force is specified, revisions will be grafted even if they
4041 If --force is specified, revisions will be grafted even if they
4041 are already ancestors of or have been grafted to the destination.
4042 are already ancestors of or have been grafted to the destination.
4042 This is useful when the revisions have since been backed out.
4043 This is useful when the revisions have since been backed out.
4043
4044
4044 If a graft merge results in conflicts, the graft process is
4045 If a graft merge results in conflicts, the graft process is
4045 interrupted so that the current merge can be manually resolved.
4046 interrupted so that the current merge can be manually resolved.
4046 Once all conflicts are addressed, the graft process can be
4047 Once all conflicts are addressed, the graft process can be
4047 continued with the -c/--continue option.
4048 continued with the -c/--continue option.
4048
4049
4049 .. note::
4050 .. note::
4050
4051
4051 The -c/--continue option does not reapply earlier options, except
4052 The -c/--continue option does not reapply earlier options, except
4052 for --force.
4053 for --force.
4053
4054
4054 .. container:: verbose
4055 .. container:: verbose
4055
4056
4056 Examples:
4057 Examples:
4057
4058
4058 - copy a single change to the stable branch and edit its description::
4059 - copy a single change to the stable branch and edit its description::
4059
4060
4060 hg update stable
4061 hg update stable
4061 hg graft --edit 9393
4062 hg graft --edit 9393
4062
4063
4063 - graft a range of changesets with one exception, updating dates::
4064 - graft a range of changesets with one exception, updating dates::
4064
4065
4065 hg graft -D "2085::2093 and not 2091"
4066 hg graft -D "2085::2093 and not 2091"
4066
4067
4067 - continue a graft after resolving conflicts::
4068 - continue a graft after resolving conflicts::
4068
4069
4069 hg graft -c
4070 hg graft -c
4070
4071
4071 - show the source of a grafted changeset::
4072 - show the source of a grafted changeset::
4072
4073
4073 hg log --debug -r .
4074 hg log --debug -r .
4074
4075
4075 - show revisions sorted by date::
4076 - show revisions sorted by date::
4076
4077
4077 hg log -r "sort(all(), date)"
4078 hg log -r "sort(all(), date)"
4078
4079
4079 See :hg:`help revisions` and :hg:`help revsets` for more about
4080 See :hg:`help revisions` and :hg:`help revsets` for more about
4080 specifying revisions.
4081 specifying revisions.
4081
4082
4082 Returns 0 on successful completion.
4083 Returns 0 on successful completion.
4083 '''
4084 '''
4084 with repo.wlock():
4085 with repo.wlock():
4085 return _dograft(ui, repo, *revs, **opts)
4086 return _dograft(ui, repo, *revs, **opts)
4086
4087
4087 def _dograft(ui, repo, *revs, **opts):
4088 def _dograft(ui, repo, *revs, **opts):
4088 if revs and opts['rev']:
4089 if revs and opts['rev']:
4089 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
4090 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
4090 'revision ordering!\n'))
4091 'revision ordering!\n'))
4091
4092
4092 revs = list(revs)
4093 revs = list(revs)
4093 revs.extend(opts['rev'])
4094 revs.extend(opts['rev'])
4094
4095
4095 if not opts.get('user') and opts.get('currentuser'):
4096 if not opts.get('user') and opts.get('currentuser'):
4096 opts['user'] = ui.username()
4097 opts['user'] = ui.username()
4097 if not opts.get('date') and opts.get('currentdate'):
4098 if not opts.get('date') and opts.get('currentdate'):
4098 opts['date'] = "%d %d" % util.makedate()
4099 opts['date'] = "%d %d" % util.makedate()
4099
4100
4100 editor = cmdutil.getcommiteditor(editform='graft', **opts)
4101 editor = cmdutil.getcommiteditor(editform='graft', **opts)
4101
4102
4102 cont = False
4103 cont = False
4103 if opts['continue']:
4104 if opts['continue']:
4104 cont = True
4105 cont = True
4105 if revs:
4106 if revs:
4106 raise error.Abort(_("can't specify --continue and revisions"))
4107 raise error.Abort(_("can't specify --continue and revisions"))
4107 # read in unfinished revisions
4108 # read in unfinished revisions
4108 try:
4109 try:
4109 nodes = repo.vfs.read('graftstate').splitlines()
4110 nodes = repo.vfs.read('graftstate').splitlines()
4110 revs = [repo[node].rev() for node in nodes]
4111 revs = [repo[node].rev() for node in nodes]
4111 except IOError as inst:
4112 except IOError as inst:
4112 if inst.errno != errno.ENOENT:
4113 if inst.errno != errno.ENOENT:
4113 raise
4114 raise
4114 cmdutil.wrongtooltocontinue(repo, _('graft'))
4115 cmdutil.wrongtooltocontinue(repo, _('graft'))
4115 else:
4116 else:
4116 cmdutil.checkunfinished(repo)
4117 cmdutil.checkunfinished(repo)
4117 cmdutil.bailifchanged(repo)
4118 cmdutil.bailifchanged(repo)
4118 if not revs:
4119 if not revs:
4119 raise error.Abort(_('no revisions specified'))
4120 raise error.Abort(_('no revisions specified'))
4120 revs = scmutil.revrange(repo, revs)
4121 revs = scmutil.revrange(repo, revs)
4121
4122
4122 skipped = set()
4123 skipped = set()
4123 # check for merges
4124 # check for merges
4124 for rev in repo.revs('%ld and merge()', revs):
4125 for rev in repo.revs('%ld and merge()', revs):
4125 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
4126 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
4126 skipped.add(rev)
4127 skipped.add(rev)
4127 revs = [r for r in revs if r not in skipped]
4128 revs = [r for r in revs if r not in skipped]
4128 if not revs:
4129 if not revs:
4129 return -1
4130 return -1
4130
4131
4131 # Don't check in the --continue case, in effect retaining --force across
4132 # Don't check in the --continue case, in effect retaining --force across
4132 # --continues. That's because without --force, any revisions we decided to
4133 # --continues. That's because without --force, any revisions we decided to
4133 # skip would have been filtered out here, so they wouldn't have made their
4134 # skip would have been filtered out here, so they wouldn't have made their
4134 # way to the graftstate. With --force, any revisions we would have otherwise
4135 # way to the graftstate. With --force, any revisions we would have otherwise
4135 # skipped would not have been filtered out, and if they hadn't been applied
4136 # skipped would not have been filtered out, and if they hadn't been applied
4136 # already, they'd have been in the graftstate.
4137 # already, they'd have been in the graftstate.
4137 if not (cont or opts.get('force')):
4138 if not (cont or opts.get('force')):
4138 # check for ancestors of dest branch
4139 # check for ancestors of dest branch
4139 crev = repo['.'].rev()
4140 crev = repo['.'].rev()
4140 ancestors = repo.changelog.ancestors([crev], inclusive=True)
4141 ancestors = repo.changelog.ancestors([crev], inclusive=True)
4141 # Cannot use x.remove(y) on smart set, this has to be a list.
4142 # Cannot use x.remove(y) on smart set, this has to be a list.
4142 # XXX make this lazy in the future
4143 # XXX make this lazy in the future
4143 revs = list(revs)
4144 revs = list(revs)
4144 # don't mutate while iterating, create a copy
4145 # don't mutate while iterating, create a copy
4145 for rev in list(revs):
4146 for rev in list(revs):
4146 if rev in ancestors:
4147 if rev in ancestors:
4147 ui.warn(_('skipping ancestor revision %d:%s\n') %
4148 ui.warn(_('skipping ancestor revision %d:%s\n') %
4148 (rev, repo[rev]))
4149 (rev, repo[rev]))
4149 # XXX remove on list is slow
4150 # XXX remove on list is slow
4150 revs.remove(rev)
4151 revs.remove(rev)
4151 if not revs:
4152 if not revs:
4152 return -1
4153 return -1
4153
4154
4154 # analyze revs for earlier grafts
4155 # analyze revs for earlier grafts
4155 ids = {}
4156 ids = {}
4156 for ctx in repo.set("%ld", revs):
4157 for ctx in repo.set("%ld", revs):
4157 ids[ctx.hex()] = ctx.rev()
4158 ids[ctx.hex()] = ctx.rev()
4158 n = ctx.extra().get('source')
4159 n = ctx.extra().get('source')
4159 if n:
4160 if n:
4160 ids[n] = ctx.rev()
4161 ids[n] = ctx.rev()
4161
4162
4162 # check ancestors for earlier grafts
4163 # check ancestors for earlier grafts
4163 ui.debug('scanning for duplicate grafts\n')
4164 ui.debug('scanning for duplicate grafts\n')
4164
4165
4165 for rev in repo.changelog.findmissingrevs(revs, [crev]):
4166 for rev in repo.changelog.findmissingrevs(revs, [crev]):
4166 ctx = repo[rev]
4167 ctx = repo[rev]
4167 n = ctx.extra().get('source')
4168 n = ctx.extra().get('source')
4168 if n in ids:
4169 if n in ids:
4169 try:
4170 try:
4170 r = repo[n].rev()
4171 r = repo[n].rev()
4171 except error.RepoLookupError:
4172 except error.RepoLookupError:
4172 r = None
4173 r = None
4173 if r in revs:
4174 if r in revs:
4174 ui.warn(_('skipping revision %d:%s '
4175 ui.warn(_('skipping revision %d:%s '
4175 '(already grafted to %d:%s)\n')
4176 '(already grafted to %d:%s)\n')
4176 % (r, repo[r], rev, ctx))
4177 % (r, repo[r], rev, ctx))
4177 revs.remove(r)
4178 revs.remove(r)
4178 elif ids[n] in revs:
4179 elif ids[n] in revs:
4179 if r is None:
4180 if r is None:
4180 ui.warn(_('skipping already grafted revision %d:%s '
4181 ui.warn(_('skipping already grafted revision %d:%s '
4181 '(%d:%s also has unknown origin %s)\n')
4182 '(%d:%s also has unknown origin %s)\n')
4182 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
4183 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
4183 else:
4184 else:
4184 ui.warn(_('skipping already grafted revision %d:%s '
4185 ui.warn(_('skipping already grafted revision %d:%s '
4185 '(%d:%s also has origin %d:%s)\n')
4186 '(%d:%s also has origin %d:%s)\n')
4186 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
4187 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
4187 revs.remove(ids[n])
4188 revs.remove(ids[n])
4188 elif ctx.hex() in ids:
4189 elif ctx.hex() in ids:
4189 r = ids[ctx.hex()]
4190 r = ids[ctx.hex()]
4190 ui.warn(_('skipping already grafted revision %d:%s '
4191 ui.warn(_('skipping already grafted revision %d:%s '
4191 '(was grafted from %d:%s)\n') %
4192 '(was grafted from %d:%s)\n') %
4192 (r, repo[r], rev, ctx))
4193 (r, repo[r], rev, ctx))
4193 revs.remove(r)
4194 revs.remove(r)
4194 if not revs:
4195 if not revs:
4195 return -1
4196 return -1
4196
4197
4197 for pos, ctx in enumerate(repo.set("%ld", revs)):
4198 for pos, ctx in enumerate(repo.set("%ld", revs)):
4198 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
4199 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
4199 ctx.description().split('\n', 1)[0])
4200 ctx.description().split('\n', 1)[0])
4200 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
4201 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
4201 if names:
4202 if names:
4202 desc += ' (%s)' % ' '.join(names)
4203 desc += ' (%s)' % ' '.join(names)
4203 ui.status(_('grafting %s\n') % desc)
4204 ui.status(_('grafting %s\n') % desc)
4204 if opts.get('dry_run'):
4205 if opts.get('dry_run'):
4205 continue
4206 continue
4206
4207
4207 source = ctx.extra().get('source')
4208 source = ctx.extra().get('source')
4208 extra = {}
4209 extra = {}
4209 if source:
4210 if source:
4210 extra['source'] = source
4211 extra['source'] = source
4211 extra['intermediate-source'] = ctx.hex()
4212 extra['intermediate-source'] = ctx.hex()
4212 else:
4213 else:
4213 extra['source'] = ctx.hex()
4214 extra['source'] = ctx.hex()
4214 user = ctx.user()
4215 user = ctx.user()
4215 if opts.get('user'):
4216 if opts.get('user'):
4216 user = opts['user']
4217 user = opts['user']
4217 date = ctx.date()
4218 date = ctx.date()
4218 if opts.get('date'):
4219 if opts.get('date'):
4219 date = opts['date']
4220 date = opts['date']
4220 message = ctx.description()
4221 message = ctx.description()
4221 if opts.get('log'):
4222 if opts.get('log'):
4222 message += '\n(grafted from %s)' % ctx.hex()
4223 message += '\n(grafted from %s)' % ctx.hex()
4223
4224
4224 # we don't merge the first commit when continuing
4225 # we don't merge the first commit when continuing
4225 if not cont:
4226 if not cont:
4226 # perform the graft merge with p1(rev) as 'ancestor'
4227 # perform the graft merge with p1(rev) as 'ancestor'
4227 try:
4228 try:
4228 # ui.forcemerge is an internal variable, do not document
4229 # ui.forcemerge is an internal variable, do not document
4229 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4230 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4230 'graft')
4231 'graft')
4231 stats = mergemod.graft(repo, ctx, ctx.p1(),
4232 stats = mergemod.graft(repo, ctx, ctx.p1(),
4232 ['local', 'graft'])
4233 ['local', 'graft'])
4233 finally:
4234 finally:
4234 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
4235 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
4235 # report any conflicts
4236 # report any conflicts
4236 if stats and stats[3] > 0:
4237 if stats and stats[3] > 0:
4237 # write out state for --continue
4238 # write out state for --continue
4238 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
4239 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
4239 repo.vfs.write('graftstate', ''.join(nodelines))
4240 repo.vfs.write('graftstate', ''.join(nodelines))
4240 extra = ''
4241 extra = ''
4241 if opts.get('user'):
4242 if opts.get('user'):
4242 extra += ' --user %s' % util.shellquote(opts['user'])
4243 extra += ' --user %s' % util.shellquote(opts['user'])
4243 if opts.get('date'):
4244 if opts.get('date'):
4244 extra += ' --date %s' % util.shellquote(opts['date'])
4245 extra += ' --date %s' % util.shellquote(opts['date'])
4245 if opts.get('log'):
4246 if opts.get('log'):
4246 extra += ' --log'
4247 extra += ' --log'
4247 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
4248 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
4248 raise error.Abort(
4249 raise error.Abort(
4249 _("unresolved conflicts, can't continue"),
4250 _("unresolved conflicts, can't continue"),
4250 hint=hint)
4251 hint=hint)
4251 else:
4252 else:
4252 cont = False
4253 cont = False
4253
4254
4254 # commit
4255 # commit
4255 node = repo.commit(text=message, user=user,
4256 node = repo.commit(text=message, user=user,
4256 date=date, extra=extra, editor=editor)
4257 date=date, extra=extra, editor=editor)
4257 if node is None:
4258 if node is None:
4258 ui.warn(
4259 ui.warn(
4259 _('note: graft of %d:%s created no changes to commit\n') %
4260 _('note: graft of %d:%s created no changes to commit\n') %
4260 (ctx.rev(), ctx))
4261 (ctx.rev(), ctx))
4261
4262
4262 # remove state when we complete successfully
4263 # remove state when we complete successfully
4263 if not opts.get('dry_run'):
4264 if not opts.get('dry_run'):
4264 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
4265 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
4265
4266
4266 return 0
4267 return 0
4267
4268
4268 @command('grep',
4269 @command('grep',
4269 [('0', 'print0', None, _('end fields with NUL')),
4270 [('0', 'print0', None, _('end fields with NUL')),
4270 ('', 'all', None, _('print all revisions that match')),
4271 ('', 'all', None, _('print all revisions that match')),
4271 ('a', 'text', None, _('treat all files as text')),
4272 ('a', 'text', None, _('treat all files as text')),
4272 ('f', 'follow', None,
4273 ('f', 'follow', None,
4273 _('follow changeset history,'
4274 _('follow changeset history,'
4274 ' or file history across copies and renames')),
4275 ' or file history across copies and renames')),
4275 ('i', 'ignore-case', None, _('ignore case when matching')),
4276 ('i', 'ignore-case', None, _('ignore case when matching')),
4276 ('l', 'files-with-matches', None,
4277 ('l', 'files-with-matches', None,
4277 _('print only filenames and revisions that match')),
4278 _('print only filenames and revisions that match')),
4278 ('n', 'line-number', None, _('print matching line numbers')),
4279 ('n', 'line-number', None, _('print matching line numbers')),
4279 ('r', 'rev', [],
4280 ('r', 'rev', [],
4280 _('only search files changed within revision range'), _('REV')),
4281 _('only search files changed within revision range'), _('REV')),
4281 ('u', 'user', None, _('list the author (long with -v)')),
4282 ('u', 'user', None, _('list the author (long with -v)')),
4282 ('d', 'date', None, _('list the date (short with -q)')),
4283 ('d', 'date', None, _('list the date (short with -q)')),
4283 ] + walkopts,
4284 ] + walkopts,
4284 _('[OPTION]... PATTERN [FILE]...'),
4285 _('[OPTION]... PATTERN [FILE]...'),
4285 inferrepo=True)
4286 inferrepo=True)
4286 def grep(ui, repo, pattern, *pats, **opts):
4287 def grep(ui, repo, pattern, *pats, **opts):
4287 """search for a pattern in specified files and revisions
4288 """search for a pattern in specified files and revisions
4288
4289
4289 Search revisions of files for a regular expression.
4290 Search revisions of files for a regular expression.
4290
4291
4291 This command behaves differently than Unix grep. It only accepts
4292 This command behaves differently than Unix grep. It only accepts
4292 Python/Perl regexps. It searches repository history, not the
4293 Python/Perl regexps. It searches repository history, not the
4293 working directory. It always prints the revision number in which a
4294 working directory. It always prints the revision number in which a
4294 match appears.
4295 match appears.
4295
4296
4296 By default, grep only prints output for the first revision of a
4297 By default, grep only prints output for the first revision of a
4297 file in which it finds a match. To get it to print every revision
4298 file in which it finds a match. To get it to print every revision
4298 that contains a change in match status ("-" for a match that
4299 that contains a change in match status ("-" for a match that
4299 becomes a non-match, or "+" for a non-match that becomes a match),
4300 becomes a non-match, or "+" for a non-match that becomes a match),
4300 use the --all flag.
4301 use the --all flag.
4301
4302
4302 Returns 0 if a match is found, 1 otherwise.
4303 Returns 0 if a match is found, 1 otherwise.
4303 """
4304 """
4304 reflags = re.M
4305 reflags = re.M
4305 if opts.get('ignore_case'):
4306 if opts.get('ignore_case'):
4306 reflags |= re.I
4307 reflags |= re.I
4307 try:
4308 try:
4308 regexp = util.re.compile(pattern, reflags)
4309 regexp = util.re.compile(pattern, reflags)
4309 except re.error as inst:
4310 except re.error as inst:
4310 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
4311 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
4311 return 1
4312 return 1
4312 sep, eol = ':', '\n'
4313 sep, eol = ':', '\n'
4313 if opts.get('print0'):
4314 if opts.get('print0'):
4314 sep = eol = '\0'
4315 sep = eol = '\0'
4315
4316
4316 getfile = util.lrucachefunc(repo.file)
4317 getfile = util.lrucachefunc(repo.file)
4317
4318
4318 def matchlines(body):
4319 def matchlines(body):
4319 begin = 0
4320 begin = 0
4320 linenum = 0
4321 linenum = 0
4321 while begin < len(body):
4322 while begin < len(body):
4322 match = regexp.search(body, begin)
4323 match = regexp.search(body, begin)
4323 if not match:
4324 if not match:
4324 break
4325 break
4325 mstart, mend = match.span()
4326 mstart, mend = match.span()
4326 linenum += body.count('\n', begin, mstart) + 1
4327 linenum += body.count('\n', begin, mstart) + 1
4327 lstart = body.rfind('\n', begin, mstart) + 1 or begin
4328 lstart = body.rfind('\n', begin, mstart) + 1 or begin
4328 begin = body.find('\n', mend) + 1 or len(body) + 1
4329 begin = body.find('\n', mend) + 1 or len(body) + 1
4329 lend = begin - 1
4330 lend = begin - 1
4330 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
4331 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
4331
4332
4332 class linestate(object):
4333 class linestate(object):
4333 def __init__(self, line, linenum, colstart, colend):
4334 def __init__(self, line, linenum, colstart, colend):
4334 self.line = line
4335 self.line = line
4335 self.linenum = linenum
4336 self.linenum = linenum
4336 self.colstart = colstart
4337 self.colstart = colstart
4337 self.colend = colend
4338 self.colend = colend
4338
4339
4339 def __hash__(self):
4340 def __hash__(self):
4340 return hash((self.linenum, self.line))
4341 return hash((self.linenum, self.line))
4341
4342
4342 def __eq__(self, other):
4343 def __eq__(self, other):
4343 return self.line == other.line
4344 return self.line == other.line
4344
4345
4345 def __iter__(self):
4346 def __iter__(self):
4346 yield (self.line[:self.colstart], '')
4347 yield (self.line[:self.colstart], '')
4347 yield (self.line[self.colstart:self.colend], 'grep.match')
4348 yield (self.line[self.colstart:self.colend], 'grep.match')
4348 rest = self.line[self.colend:]
4349 rest = self.line[self.colend:]
4349 while rest != '':
4350 while rest != '':
4350 match = regexp.search(rest)
4351 match = regexp.search(rest)
4351 if not match:
4352 if not match:
4352 yield (rest, '')
4353 yield (rest, '')
4353 break
4354 break
4354 mstart, mend = match.span()
4355 mstart, mend = match.span()
4355 yield (rest[:mstart], '')
4356 yield (rest[:mstart], '')
4356 yield (rest[mstart:mend], 'grep.match')
4357 yield (rest[mstart:mend], 'grep.match')
4357 rest = rest[mend:]
4358 rest = rest[mend:]
4358
4359
4359 matches = {}
4360 matches = {}
4360 copies = {}
4361 copies = {}
4361 def grepbody(fn, rev, body):
4362 def grepbody(fn, rev, body):
4362 matches[rev].setdefault(fn, [])
4363 matches[rev].setdefault(fn, [])
4363 m = matches[rev][fn]
4364 m = matches[rev][fn]
4364 for lnum, cstart, cend, line in matchlines(body):
4365 for lnum, cstart, cend, line in matchlines(body):
4365 s = linestate(line, lnum, cstart, cend)
4366 s = linestate(line, lnum, cstart, cend)
4366 m.append(s)
4367 m.append(s)
4367
4368
4368 def difflinestates(a, b):
4369 def difflinestates(a, b):
4369 sm = difflib.SequenceMatcher(None, a, b)
4370 sm = difflib.SequenceMatcher(None, a, b)
4370 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
4371 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
4371 if tag == 'insert':
4372 if tag == 'insert':
4372 for i in xrange(blo, bhi):
4373 for i in xrange(blo, bhi):
4373 yield ('+', b[i])
4374 yield ('+', b[i])
4374 elif tag == 'delete':
4375 elif tag == 'delete':
4375 for i in xrange(alo, ahi):
4376 for i in xrange(alo, ahi):
4376 yield ('-', a[i])
4377 yield ('-', a[i])
4377 elif tag == 'replace':
4378 elif tag == 'replace':
4378 for i in xrange(alo, ahi):
4379 for i in xrange(alo, ahi):
4379 yield ('-', a[i])
4380 yield ('-', a[i])
4380 for i in xrange(blo, bhi):
4381 for i in xrange(blo, bhi):
4381 yield ('+', b[i])
4382 yield ('+', b[i])
4382
4383
4383 def display(fn, ctx, pstates, states):
4384 def display(fn, ctx, pstates, states):
4384 rev = ctx.rev()
4385 rev = ctx.rev()
4385 if ui.quiet:
4386 if ui.quiet:
4386 datefunc = util.shortdate
4387 datefunc = util.shortdate
4387 else:
4388 else:
4388 datefunc = util.datestr
4389 datefunc = util.datestr
4389 found = False
4390 found = False
4390 @util.cachefunc
4391 @util.cachefunc
4391 def binary():
4392 def binary():
4392 flog = getfile(fn)
4393 flog = getfile(fn)
4393 return util.binary(flog.read(ctx.filenode(fn)))
4394 return util.binary(flog.read(ctx.filenode(fn)))
4394
4395
4395 if opts.get('all'):
4396 if opts.get('all'):
4396 iter = difflinestates(pstates, states)
4397 iter = difflinestates(pstates, states)
4397 else:
4398 else:
4398 iter = [('', l) for l in states]
4399 iter = [('', l) for l in states]
4399 for change, l in iter:
4400 for change, l in iter:
4400 cols = [(fn, 'grep.filename'), (str(rev), 'grep.rev')]
4401 cols = [(fn, 'grep.filename'), (str(rev), 'grep.rev')]
4401
4402
4402 if opts.get('line_number'):
4403 if opts.get('line_number'):
4403 cols.append((str(l.linenum), 'grep.linenumber'))
4404 cols.append((str(l.linenum), 'grep.linenumber'))
4404 if opts.get('all'):
4405 if opts.get('all'):
4405 cols.append((change, 'grep.change'))
4406 cols.append((change, 'grep.change'))
4406 if opts.get('user'):
4407 if opts.get('user'):
4407 cols.append((ui.shortuser(ctx.user()), 'grep.user'))
4408 cols.append((ui.shortuser(ctx.user()), 'grep.user'))
4408 if opts.get('date'):
4409 if opts.get('date'):
4409 cols.append((datefunc(ctx.date()), 'grep.date'))
4410 cols.append((datefunc(ctx.date()), 'grep.date'))
4410 for col, label in cols[:-1]:
4411 for col, label in cols[:-1]:
4411 ui.write(col, label=label)
4412 ui.write(col, label=label)
4412 ui.write(sep, label='grep.sep')
4413 ui.write(sep, label='grep.sep')
4413 ui.write(cols[-1][0], label=cols[-1][1])
4414 ui.write(cols[-1][0], label=cols[-1][1])
4414 if not opts.get('files_with_matches'):
4415 if not opts.get('files_with_matches'):
4415 ui.write(sep, label='grep.sep')
4416 ui.write(sep, label='grep.sep')
4416 if not opts.get('text') and binary():
4417 if not opts.get('text') and binary():
4417 ui.write(_(" Binary file matches"))
4418 ui.write(_(" Binary file matches"))
4418 else:
4419 else:
4419 for s, label in l:
4420 for s, label in l:
4420 ui.write(s, label=label)
4421 ui.write(s, label=label)
4421 ui.write(eol)
4422 ui.write(eol)
4422 found = True
4423 found = True
4423 if opts.get('files_with_matches'):
4424 if opts.get('files_with_matches'):
4424 break
4425 break
4425 return found
4426 return found
4426
4427
4427 skip = {}
4428 skip = {}
4428 revfiles = {}
4429 revfiles = {}
4429 matchfn = scmutil.match(repo[None], pats, opts)
4430 matchfn = scmutil.match(repo[None], pats, opts)
4430 found = False
4431 found = False
4431 follow = opts.get('follow')
4432 follow = opts.get('follow')
4432
4433
4433 def prep(ctx, fns):
4434 def prep(ctx, fns):
4434 rev = ctx.rev()
4435 rev = ctx.rev()
4435 pctx = ctx.p1()
4436 pctx = ctx.p1()
4436 parent = pctx.rev()
4437 parent = pctx.rev()
4437 matches.setdefault(rev, {})
4438 matches.setdefault(rev, {})
4438 matches.setdefault(parent, {})
4439 matches.setdefault(parent, {})
4439 files = revfiles.setdefault(rev, [])
4440 files = revfiles.setdefault(rev, [])
4440 for fn in fns:
4441 for fn in fns:
4441 flog = getfile(fn)
4442 flog = getfile(fn)
4442 try:
4443 try:
4443 fnode = ctx.filenode(fn)
4444 fnode = ctx.filenode(fn)
4444 except error.LookupError:
4445 except error.LookupError:
4445 continue
4446 continue
4446
4447
4447 copied = flog.renamed(fnode)
4448 copied = flog.renamed(fnode)
4448 copy = follow and copied and copied[0]
4449 copy = follow and copied and copied[0]
4449 if copy:
4450 if copy:
4450 copies.setdefault(rev, {})[fn] = copy
4451 copies.setdefault(rev, {})[fn] = copy
4451 if fn in skip:
4452 if fn in skip:
4452 if copy:
4453 if copy:
4453 skip[copy] = True
4454 skip[copy] = True
4454 continue
4455 continue
4455 files.append(fn)
4456 files.append(fn)
4456
4457
4457 if fn not in matches[rev]:
4458 if fn not in matches[rev]:
4458 grepbody(fn, rev, flog.read(fnode))
4459 grepbody(fn, rev, flog.read(fnode))
4459
4460
4460 pfn = copy or fn
4461 pfn = copy or fn
4461 if pfn not in matches[parent]:
4462 if pfn not in matches[parent]:
4462 try:
4463 try:
4463 fnode = pctx.filenode(pfn)
4464 fnode = pctx.filenode(pfn)
4464 grepbody(pfn, parent, flog.read(fnode))
4465 grepbody(pfn, parent, flog.read(fnode))
4465 except error.LookupError:
4466 except error.LookupError:
4466 pass
4467 pass
4467
4468
4468 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4469 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4469 rev = ctx.rev()
4470 rev = ctx.rev()
4470 parent = ctx.p1().rev()
4471 parent = ctx.p1().rev()
4471 for fn in sorted(revfiles.get(rev, [])):
4472 for fn in sorted(revfiles.get(rev, [])):
4472 states = matches[rev][fn]
4473 states = matches[rev][fn]
4473 copy = copies.get(rev, {}).get(fn)
4474 copy = copies.get(rev, {}).get(fn)
4474 if fn in skip:
4475 if fn in skip:
4475 if copy:
4476 if copy:
4476 skip[copy] = True
4477 skip[copy] = True
4477 continue
4478 continue
4478 pstates = matches.get(parent, {}).get(copy or fn, [])
4479 pstates = matches.get(parent, {}).get(copy or fn, [])
4479 if pstates or states:
4480 if pstates or states:
4480 r = display(fn, ctx, pstates, states)
4481 r = display(fn, ctx, pstates, states)
4481 found = found or r
4482 found = found or r
4482 if r and not opts.get('all'):
4483 if r and not opts.get('all'):
4483 skip[fn] = True
4484 skip[fn] = True
4484 if copy:
4485 if copy:
4485 skip[copy] = True
4486 skip[copy] = True
4486 del matches[rev]
4487 del matches[rev]
4487 del revfiles[rev]
4488 del revfiles[rev]
4488
4489
4489 return not found
4490 return not found
4490
4491
4491 @command('heads',
4492 @command('heads',
4492 [('r', 'rev', '',
4493 [('r', 'rev', '',
4493 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
4494 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
4494 ('t', 'topo', False, _('show topological heads only')),
4495 ('t', 'topo', False, _('show topological heads only')),
4495 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
4496 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
4496 ('c', 'closed', False, _('show normal and closed branch heads')),
4497 ('c', 'closed', False, _('show normal and closed branch heads')),
4497 ] + templateopts,
4498 ] + templateopts,
4498 _('[-ct] [-r STARTREV] [REV]...'))
4499 _('[-ct] [-r STARTREV] [REV]...'))
4499 def heads(ui, repo, *branchrevs, **opts):
4500 def heads(ui, repo, *branchrevs, **opts):
4500 """show branch heads
4501 """show branch heads
4501
4502
4502 With no arguments, show all open branch heads in the repository.
4503 With no arguments, show all open branch heads in the repository.
4503 Branch heads are changesets that have no descendants on the
4504 Branch heads are changesets that have no descendants on the
4504 same branch. They are where development generally takes place and
4505 same branch. They are where development generally takes place and
4505 are the usual targets for update and merge operations.
4506 are the usual targets for update and merge operations.
4506
4507
4507 If one or more REVs are given, only open branch heads on the
4508 If one or more REVs are given, only open branch heads on the
4508 branches associated with the specified changesets are shown. This
4509 branches associated with the specified changesets are shown. This
4509 means that you can use :hg:`heads .` to see the heads on the
4510 means that you can use :hg:`heads .` to see the heads on the
4510 currently checked-out branch.
4511 currently checked-out branch.
4511
4512
4512 If -c/--closed is specified, also show branch heads marked closed
4513 If -c/--closed is specified, also show branch heads marked closed
4513 (see :hg:`commit --close-branch`).
4514 (see :hg:`commit --close-branch`).
4514
4515
4515 If STARTREV is specified, only those heads that are descendants of
4516 If STARTREV is specified, only those heads that are descendants of
4516 STARTREV will be displayed.
4517 STARTREV will be displayed.
4517
4518
4518 If -t/--topo is specified, named branch mechanics will be ignored and only
4519 If -t/--topo is specified, named branch mechanics will be ignored and only
4519 topological heads (changesets with no children) will be shown.
4520 topological heads (changesets with no children) will be shown.
4520
4521
4521 Returns 0 if matching heads are found, 1 if not.
4522 Returns 0 if matching heads are found, 1 if not.
4522 """
4523 """
4523
4524
4524 start = None
4525 start = None
4525 if 'rev' in opts:
4526 if 'rev' in opts:
4526 start = scmutil.revsingle(repo, opts['rev'], None).node()
4527 start = scmutil.revsingle(repo, opts['rev'], None).node()
4527
4528
4528 if opts.get('topo'):
4529 if opts.get('topo'):
4529 heads = [repo[h] for h in repo.heads(start)]
4530 heads = [repo[h] for h in repo.heads(start)]
4530 else:
4531 else:
4531 heads = []
4532 heads = []
4532 for branch in repo.branchmap():
4533 for branch in repo.branchmap():
4533 heads += repo.branchheads(branch, start, opts.get('closed'))
4534 heads += repo.branchheads(branch, start, opts.get('closed'))
4534 heads = [repo[h] for h in heads]
4535 heads = [repo[h] for h in heads]
4535
4536
4536 if branchrevs:
4537 if branchrevs:
4537 branches = set(repo[br].branch() for br in branchrevs)
4538 branches = set(repo[br].branch() for br in branchrevs)
4538 heads = [h for h in heads if h.branch() in branches]
4539 heads = [h for h in heads if h.branch() in branches]
4539
4540
4540 if opts.get('active') and branchrevs:
4541 if opts.get('active') and branchrevs:
4541 dagheads = repo.heads(start)
4542 dagheads = repo.heads(start)
4542 heads = [h for h in heads if h.node() in dagheads]
4543 heads = [h for h in heads if h.node() in dagheads]
4543
4544
4544 if branchrevs:
4545 if branchrevs:
4545 haveheads = set(h.branch() for h in heads)
4546 haveheads = set(h.branch() for h in heads)
4546 if branches - haveheads:
4547 if branches - haveheads:
4547 headless = ', '.join(b for b in branches - haveheads)
4548 headless = ', '.join(b for b in branches - haveheads)
4548 msg = _('no open branch heads found on branches %s')
4549 msg = _('no open branch heads found on branches %s')
4549 if opts.get('rev'):
4550 if opts.get('rev'):
4550 msg += _(' (started at %s)') % opts['rev']
4551 msg += _(' (started at %s)') % opts['rev']
4551 ui.warn((msg + '\n') % headless)
4552 ui.warn((msg + '\n') % headless)
4552
4553
4553 if not heads:
4554 if not heads:
4554 return 1
4555 return 1
4555
4556
4556 heads = sorted(heads, key=lambda x: -x.rev())
4557 heads = sorted(heads, key=lambda x: -x.rev())
4557 displayer = cmdutil.show_changeset(ui, repo, opts)
4558 displayer = cmdutil.show_changeset(ui, repo, opts)
4558 for ctx in heads:
4559 for ctx in heads:
4559 displayer.show(ctx)
4560 displayer.show(ctx)
4560 displayer.close()
4561 displayer.close()
4561
4562
4562 @command('help',
4563 @command('help',
4563 [('e', 'extension', None, _('show only help for extensions')),
4564 [('e', 'extension', None, _('show only help for extensions')),
4564 ('c', 'command', None, _('show only help for commands')),
4565 ('c', 'command', None, _('show only help for commands')),
4565 ('k', 'keyword', None, _('show topics matching keyword')),
4566 ('k', 'keyword', None, _('show topics matching keyword')),
4566 ('s', 'system', [], _('show help for specific platform(s)')),
4567 ('s', 'system', [], _('show help for specific platform(s)')),
4567 ],
4568 ],
4568 _('[-ecks] [TOPIC]'),
4569 _('[-ecks] [TOPIC]'),
4569 norepo=True)
4570 norepo=True)
4570 def help_(ui, name=None, **opts):
4571 def help_(ui, name=None, **opts):
4571 """show help for a given topic or a help overview
4572 """show help for a given topic or a help overview
4572
4573
4573 With no arguments, print a list of commands with short help messages.
4574 With no arguments, print a list of commands with short help messages.
4574
4575
4575 Given a topic, extension, or command name, print help for that
4576 Given a topic, extension, or command name, print help for that
4576 topic.
4577 topic.
4577
4578
4578 Returns 0 if successful.
4579 Returns 0 if successful.
4579 """
4580 """
4580
4581
4581 textwidth = ui.configint('ui', 'textwidth', 78)
4582 textwidth = ui.configint('ui', 'textwidth', 78)
4582 termwidth = ui.termwidth() - 2
4583 termwidth = ui.termwidth() - 2
4583 if textwidth <= 0 or termwidth < textwidth:
4584 if textwidth <= 0 or termwidth < textwidth:
4584 textwidth = termwidth
4585 textwidth = termwidth
4585
4586
4586 keep = opts.get('system') or []
4587 keep = opts.get('system') or []
4587 if len(keep) == 0:
4588 if len(keep) == 0:
4588 if sys.platform.startswith('win'):
4589 if sys.platform.startswith('win'):
4589 keep.append('windows')
4590 keep.append('windows')
4590 elif sys.platform == 'OpenVMS':
4591 elif sys.platform == 'OpenVMS':
4591 keep.append('vms')
4592 keep.append('vms')
4592 elif sys.platform == 'plan9':
4593 elif sys.platform == 'plan9':
4593 keep.append('plan9')
4594 keep.append('plan9')
4594 else:
4595 else:
4595 keep.append('unix')
4596 keep.append('unix')
4596 keep.append(sys.platform.lower())
4597 keep.append(sys.platform.lower())
4597 if ui.verbose:
4598 if ui.verbose:
4598 keep.append('verbose')
4599 keep.append('verbose')
4599
4600
4600 section = None
4601 section = None
4601 subtopic = None
4602 subtopic = None
4602 if name and '.' in name:
4603 if name and '.' in name:
4603 name, section = name.split('.', 1)
4604 name, section = name.split('.', 1)
4604 section = encoding.lower(section)
4605 section = encoding.lower(section)
4605 if '.' in section:
4606 if '.' in section:
4606 subtopic, section = section.split('.', 1)
4607 subtopic, section = section.split('.', 1)
4607 else:
4608 else:
4608 subtopic = section
4609 subtopic = section
4609
4610
4610 text = help.help_(ui, name, subtopic=subtopic, **opts)
4611 text = help.help_(ui, name, subtopic=subtopic, **opts)
4611
4612
4612 formatted, pruned = minirst.format(text, textwidth, keep=keep,
4613 formatted, pruned = minirst.format(text, textwidth, keep=keep,
4613 section=section)
4614 section=section)
4614
4615
4615 # We could have been given a weird ".foo" section without a name
4616 # We could have been given a weird ".foo" section without a name
4616 # to look for, or we could have simply failed to found "foo.bar"
4617 # to look for, or we could have simply failed to found "foo.bar"
4617 # because bar isn't a section of foo
4618 # because bar isn't a section of foo
4618 if section and not (formatted and name):
4619 if section and not (formatted and name):
4619 raise error.Abort(_("help section not found"))
4620 raise error.Abort(_("help section not found"))
4620
4621
4621 if 'verbose' in pruned:
4622 if 'verbose' in pruned:
4622 keep.append('omitted')
4623 keep.append('omitted')
4623 else:
4624 else:
4624 keep.append('notomitted')
4625 keep.append('notomitted')
4625 formatted, pruned = minirst.format(text, textwidth, keep=keep,
4626 formatted, pruned = minirst.format(text, textwidth, keep=keep,
4626 section=section)
4627 section=section)
4627 ui.write(formatted)
4628 ui.write(formatted)
4628
4629
4629
4630
4630 @command('identify|id',
4631 @command('identify|id',
4631 [('r', 'rev', '',
4632 [('r', 'rev', '',
4632 _('identify the specified revision'), _('REV')),
4633 _('identify the specified revision'), _('REV')),
4633 ('n', 'num', None, _('show local revision number')),
4634 ('n', 'num', None, _('show local revision number')),
4634 ('i', 'id', None, _('show global revision id')),
4635 ('i', 'id', None, _('show global revision id')),
4635 ('b', 'branch', None, _('show branch')),
4636 ('b', 'branch', None, _('show branch')),
4636 ('t', 'tags', None, _('show tags')),
4637 ('t', 'tags', None, _('show tags')),
4637 ('B', 'bookmarks', None, _('show bookmarks')),
4638 ('B', 'bookmarks', None, _('show bookmarks')),
4638 ] + remoteopts,
4639 ] + remoteopts,
4639 _('[-nibtB] [-r REV] [SOURCE]'),
4640 _('[-nibtB] [-r REV] [SOURCE]'),
4640 optionalrepo=True)
4641 optionalrepo=True)
4641 def identify(ui, repo, source=None, rev=None,
4642 def identify(ui, repo, source=None, rev=None,
4642 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
4643 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
4643 """identify the working directory or specified revision
4644 """identify the working directory or specified revision
4644
4645
4645 Print a summary identifying the repository state at REV using one or
4646 Print a summary identifying the repository state at REV using one or
4646 two parent hash identifiers, followed by a "+" if the working
4647 two parent hash identifiers, followed by a "+" if the working
4647 directory has uncommitted changes, the branch name (if not default),
4648 directory has uncommitted changes, the branch name (if not default),
4648 a list of tags, and a list of bookmarks.
4649 a list of tags, and a list of bookmarks.
4649
4650
4650 When REV is not given, print a summary of the current state of the
4651 When REV is not given, print a summary of the current state of the
4651 repository.
4652 repository.
4652
4653
4653 Specifying a path to a repository root or Mercurial bundle will
4654 Specifying a path to a repository root or Mercurial bundle will
4654 cause lookup to operate on that repository/bundle.
4655 cause lookup to operate on that repository/bundle.
4655
4656
4656 .. container:: verbose
4657 .. container:: verbose
4657
4658
4658 Examples:
4659 Examples:
4659
4660
4660 - generate a build identifier for the working directory::
4661 - generate a build identifier for the working directory::
4661
4662
4662 hg id --id > build-id.dat
4663 hg id --id > build-id.dat
4663
4664
4664 - find the revision corresponding to a tag::
4665 - find the revision corresponding to a tag::
4665
4666
4666 hg id -n -r 1.3
4667 hg id -n -r 1.3
4667
4668
4668 - check the most recent revision of a remote repository::
4669 - check the most recent revision of a remote repository::
4669
4670
4670 hg id -r tip http://selenic.com/hg/
4671 hg id -r tip http://selenic.com/hg/
4671
4672
4672 See :hg:`log` for generating more information about specific revisions,
4673 See :hg:`log` for generating more information about specific revisions,
4673 including full hash identifiers.
4674 including full hash identifiers.
4674
4675
4675 Returns 0 if successful.
4676 Returns 0 if successful.
4676 """
4677 """
4677
4678
4678 if not repo and not source:
4679 if not repo and not source:
4679 raise error.Abort(_("there is no Mercurial repository here "
4680 raise error.Abort(_("there is no Mercurial repository here "
4680 "(.hg not found)"))
4681 "(.hg not found)"))
4681
4682
4682 if ui.debugflag:
4683 if ui.debugflag:
4683 hexfunc = hex
4684 hexfunc = hex
4684 else:
4685 else:
4685 hexfunc = short
4686 hexfunc = short
4686 default = not (num or id or branch or tags or bookmarks)
4687 default = not (num or id or branch or tags or bookmarks)
4687 output = []
4688 output = []
4688 revs = []
4689 revs = []
4689
4690
4690 if source:
4691 if source:
4691 source, branches = hg.parseurl(ui.expandpath(source))
4692 source, branches = hg.parseurl(ui.expandpath(source))
4692 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
4693 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
4693 repo = peer.local()
4694 repo = peer.local()
4694 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
4695 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
4695
4696
4696 if not repo:
4697 if not repo:
4697 if num or branch or tags:
4698 if num or branch or tags:
4698 raise error.Abort(
4699 raise error.Abort(
4699 _("can't query remote revision number, branch, or tags"))
4700 _("can't query remote revision number, branch, or tags"))
4700 if not rev and revs:
4701 if not rev and revs:
4701 rev = revs[0]
4702 rev = revs[0]
4702 if not rev:
4703 if not rev:
4703 rev = "tip"
4704 rev = "tip"
4704
4705
4705 remoterev = peer.lookup(rev)
4706 remoterev = peer.lookup(rev)
4706 if default or id:
4707 if default or id:
4707 output = [hexfunc(remoterev)]
4708 output = [hexfunc(remoterev)]
4708
4709
4709 def getbms():
4710 def getbms():
4710 bms = []
4711 bms = []
4711
4712
4712 if 'bookmarks' in peer.listkeys('namespaces'):
4713 if 'bookmarks' in peer.listkeys('namespaces'):
4713 hexremoterev = hex(remoterev)
4714 hexremoterev = hex(remoterev)
4714 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
4715 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
4715 if bmr == hexremoterev]
4716 if bmr == hexremoterev]
4716
4717
4717 return sorted(bms)
4718 return sorted(bms)
4718
4719
4719 if bookmarks:
4720 if bookmarks:
4720 output.extend(getbms())
4721 output.extend(getbms())
4721 elif default and not ui.quiet:
4722 elif default and not ui.quiet:
4722 # multiple bookmarks for a single parent separated by '/'
4723 # multiple bookmarks for a single parent separated by '/'
4723 bm = '/'.join(getbms())
4724 bm = '/'.join(getbms())
4724 if bm:
4725 if bm:
4725 output.append(bm)
4726 output.append(bm)
4726 else:
4727 else:
4727 ctx = scmutil.revsingle(repo, rev, None)
4728 ctx = scmutil.revsingle(repo, rev, None)
4728
4729
4729 if ctx.rev() is None:
4730 if ctx.rev() is None:
4730 ctx = repo[None]
4731 ctx = repo[None]
4731 parents = ctx.parents()
4732 parents = ctx.parents()
4732 taglist = []
4733 taglist = []
4733 for p in parents:
4734 for p in parents:
4734 taglist.extend(p.tags())
4735 taglist.extend(p.tags())
4735
4736
4736 changed = ""
4737 changed = ""
4737 if default or id or num:
4738 if default or id or num:
4738 if (any(repo.status())
4739 if (any(repo.status())
4739 or any(ctx.sub(s).dirty() for s in ctx.substate)):
4740 or any(ctx.sub(s).dirty() for s in ctx.substate)):
4740 changed = '+'
4741 changed = '+'
4741 if default or id:
4742 if default or id:
4742 output = ["%s%s" %
4743 output = ["%s%s" %
4743 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
4744 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
4744 if num:
4745 if num:
4745 output.append("%s%s" %
4746 output.append("%s%s" %
4746 ('+'.join([str(p.rev()) for p in parents]), changed))
4747 ('+'.join([str(p.rev()) for p in parents]), changed))
4747 else:
4748 else:
4748 if default or id:
4749 if default or id:
4749 output = [hexfunc(ctx.node())]
4750 output = [hexfunc(ctx.node())]
4750 if num:
4751 if num:
4751 output.append(str(ctx.rev()))
4752 output.append(str(ctx.rev()))
4752 taglist = ctx.tags()
4753 taglist = ctx.tags()
4753
4754
4754 if default and not ui.quiet:
4755 if default and not ui.quiet:
4755 b = ctx.branch()
4756 b = ctx.branch()
4756 if b != 'default':
4757 if b != 'default':
4757 output.append("(%s)" % b)
4758 output.append("(%s)" % b)
4758
4759
4759 # multiple tags for a single parent separated by '/'
4760 # multiple tags for a single parent separated by '/'
4760 t = '/'.join(taglist)
4761 t = '/'.join(taglist)
4761 if t:
4762 if t:
4762 output.append(t)
4763 output.append(t)
4763
4764
4764 # multiple bookmarks for a single parent separated by '/'
4765 # multiple bookmarks for a single parent separated by '/'
4765 bm = '/'.join(ctx.bookmarks())
4766 bm = '/'.join(ctx.bookmarks())
4766 if bm:
4767 if bm:
4767 output.append(bm)
4768 output.append(bm)
4768 else:
4769 else:
4769 if branch:
4770 if branch:
4770 output.append(ctx.branch())
4771 output.append(ctx.branch())
4771
4772
4772 if tags:
4773 if tags:
4773 output.extend(taglist)
4774 output.extend(taglist)
4774
4775
4775 if bookmarks:
4776 if bookmarks:
4776 output.extend(ctx.bookmarks())
4777 output.extend(ctx.bookmarks())
4777
4778
4778 ui.write("%s\n" % ' '.join(output))
4779 ui.write("%s\n" % ' '.join(output))
4779
4780
4780 @command('import|patch',
4781 @command('import|patch',
4781 [('p', 'strip', 1,
4782 [('p', 'strip', 1,
4782 _('directory strip option for patch. This has the same '
4783 _('directory strip option for patch. This has the same '
4783 'meaning as the corresponding patch option'), _('NUM')),
4784 'meaning as the corresponding patch option'), _('NUM')),
4784 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
4785 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
4785 ('e', 'edit', False, _('invoke editor on commit messages')),
4786 ('e', 'edit', False, _('invoke editor on commit messages')),
4786 ('f', 'force', None,
4787 ('f', 'force', None,
4787 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
4788 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
4788 ('', 'no-commit', None,
4789 ('', 'no-commit', None,
4789 _("don't commit, just update the working directory")),
4790 _("don't commit, just update the working directory")),
4790 ('', 'bypass', None,
4791 ('', 'bypass', None,
4791 _("apply patch without touching the working directory")),
4792 _("apply patch without touching the working directory")),
4792 ('', 'partial', None,
4793 ('', 'partial', None,
4793 _('commit even if some hunks fail')),
4794 _('commit even if some hunks fail')),
4794 ('', 'exact', None,
4795 ('', 'exact', None,
4795 _('abort if patch would apply lossily')),
4796 _('abort if patch would apply lossily')),
4796 ('', 'prefix', '',
4797 ('', 'prefix', '',
4797 _('apply patch to subdirectory'), _('DIR')),
4798 _('apply patch to subdirectory'), _('DIR')),
4798 ('', 'import-branch', None,
4799 ('', 'import-branch', None,
4799 _('use any branch information in patch (implied by --exact)'))] +
4800 _('use any branch information in patch (implied by --exact)'))] +
4800 commitopts + commitopts2 + similarityopts,
4801 commitopts + commitopts2 + similarityopts,
4801 _('[OPTION]... PATCH...'))
4802 _('[OPTION]... PATCH...'))
4802 def import_(ui, repo, patch1=None, *patches, **opts):
4803 def import_(ui, repo, patch1=None, *patches, **opts):
4803 """import an ordered set of patches
4804 """import an ordered set of patches
4804
4805
4805 Import a list of patches and commit them individually (unless
4806 Import a list of patches and commit them individually (unless
4806 --no-commit is specified).
4807 --no-commit is specified).
4807
4808
4808 To read a patch from standard input, use "-" as the patch name. If
4809 To read a patch from standard input, use "-" as the patch name. If
4809 a URL is specified, the patch will be downloaded from there.
4810 a URL is specified, the patch will be downloaded from there.
4810
4811
4811 Import first applies changes to the working directory (unless
4812 Import first applies changes to the working directory (unless
4812 --bypass is specified), import will abort if there are outstanding
4813 --bypass is specified), import will abort if there are outstanding
4813 changes.
4814 changes.
4814
4815
4815 Use --bypass to apply and commit patches directly to the
4816 Use --bypass to apply and commit patches directly to the
4816 repository, without affecting the working directory. Without
4817 repository, without affecting the working directory. Without
4817 --exact, patches will be applied on top of the working directory
4818 --exact, patches will be applied on top of the working directory
4818 parent revision.
4819 parent revision.
4819
4820
4820 You can import a patch straight from a mail message. Even patches
4821 You can import a patch straight from a mail message. Even patches
4821 as attachments work (to use the body part, it must have type
4822 as attachments work (to use the body part, it must have type
4822 text/plain or text/x-patch). From and Subject headers of email
4823 text/plain or text/x-patch). From and Subject headers of email
4823 message are used as default committer and commit message. All
4824 message are used as default committer and commit message. All
4824 text/plain body parts before first diff are added to the commit
4825 text/plain body parts before first diff are added to the commit
4825 message.
4826 message.
4826
4827
4827 If the imported patch was generated by :hg:`export`, user and
4828 If the imported patch was generated by :hg:`export`, user and
4828 description from patch override values from message headers and
4829 description from patch override values from message headers and
4829 body. Values given on command line with -m/--message and -u/--user
4830 body. Values given on command line with -m/--message and -u/--user
4830 override these.
4831 override these.
4831
4832
4832 If --exact is specified, import will set the working directory to
4833 If --exact is specified, import will set the working directory to
4833 the parent of each patch before applying it, and will abort if the
4834 the parent of each patch before applying it, and will abort if the
4834 resulting changeset has a different ID than the one recorded in
4835 resulting changeset has a different ID than the one recorded in
4835 the patch. This will guard against various ways that portable
4836 the patch. This will guard against various ways that portable
4836 patch formats and mail systems might fail to transfer Mercurial
4837 patch formats and mail systems might fail to transfer Mercurial
4837 data or metadata. See ':hg: bundle' for lossless transmission.
4838 data or metadata. See ':hg: bundle' for lossless transmission.
4838
4839
4839 Use --partial to ensure a changeset will be created from the patch
4840 Use --partial to ensure a changeset will be created from the patch
4840 even if some hunks fail to apply. Hunks that fail to apply will be
4841 even if some hunks fail to apply. Hunks that fail to apply will be
4841 written to a <target-file>.rej file. Conflicts can then be resolved
4842 written to a <target-file>.rej file. Conflicts can then be resolved
4842 by hand before :hg:`commit --amend` is run to update the created
4843 by hand before :hg:`commit --amend` is run to update the created
4843 changeset. This flag exists to let people import patches that
4844 changeset. This flag exists to let people import patches that
4844 partially apply without losing the associated metadata (author,
4845 partially apply without losing the associated metadata (author,
4845 date, description, ...).
4846 date, description, ...).
4846
4847
4847 .. note::
4848 .. note::
4848
4849
4849 When no hunks apply cleanly, :hg:`import --partial` will create
4850 When no hunks apply cleanly, :hg:`import --partial` will create
4850 an empty changeset, importing only the patch metadata.
4851 an empty changeset, importing only the patch metadata.
4851
4852
4852 With -s/--similarity, hg will attempt to discover renames and
4853 With -s/--similarity, hg will attempt to discover renames and
4853 copies in the patch in the same way as :hg:`addremove`.
4854 copies in the patch in the same way as :hg:`addremove`.
4854
4855
4855 It is possible to use external patch programs to perform the patch
4856 It is possible to use external patch programs to perform the patch
4856 by setting the ``ui.patch`` configuration option. For the default
4857 by setting the ``ui.patch`` configuration option. For the default
4857 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4858 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4858 See :hg:`help config` for more information about configuration
4859 See :hg:`help config` for more information about configuration
4859 files and how to use these options.
4860 files and how to use these options.
4860
4861
4861 See :hg:`help dates` for a list of formats valid for -d/--date.
4862 See :hg:`help dates` for a list of formats valid for -d/--date.
4862
4863
4863 .. container:: verbose
4864 .. container:: verbose
4864
4865
4865 Examples:
4866 Examples:
4866
4867
4867 - import a traditional patch from a website and detect renames::
4868 - import a traditional patch from a website and detect renames::
4868
4869
4869 hg import -s 80 http://example.com/bugfix.patch
4870 hg import -s 80 http://example.com/bugfix.patch
4870
4871
4871 - import a changeset from an hgweb server::
4872 - import a changeset from an hgweb server::
4872
4873
4873 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
4874 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
4874
4875
4875 - import all the patches in an Unix-style mbox::
4876 - import all the patches in an Unix-style mbox::
4876
4877
4877 hg import incoming-patches.mbox
4878 hg import incoming-patches.mbox
4878
4879
4879 - attempt to exactly restore an exported changeset (not always
4880 - attempt to exactly restore an exported changeset (not always
4880 possible)::
4881 possible)::
4881
4882
4882 hg import --exact proposed-fix.patch
4883 hg import --exact proposed-fix.patch
4883
4884
4884 - use an external tool to apply a patch which is too fuzzy for
4885 - use an external tool to apply a patch which is too fuzzy for
4885 the default internal tool.
4886 the default internal tool.
4886
4887
4887 hg import --config ui.patch="patch --merge" fuzzy.patch
4888 hg import --config ui.patch="patch --merge" fuzzy.patch
4888
4889
4889 - change the default fuzzing from 2 to a less strict 7
4890 - change the default fuzzing from 2 to a less strict 7
4890
4891
4891 hg import --config ui.fuzz=7 fuzz.patch
4892 hg import --config ui.fuzz=7 fuzz.patch
4892
4893
4893 Returns 0 on success, 1 on partial success (see --partial).
4894 Returns 0 on success, 1 on partial success (see --partial).
4894 """
4895 """
4895
4896
4896 if not patch1:
4897 if not patch1:
4897 raise error.Abort(_('need at least one patch to import'))
4898 raise error.Abort(_('need at least one patch to import'))
4898
4899
4899 patches = (patch1,) + patches
4900 patches = (patch1,) + patches
4900
4901
4901 date = opts.get('date')
4902 date = opts.get('date')
4902 if date:
4903 if date:
4903 opts['date'] = util.parsedate(date)
4904 opts['date'] = util.parsedate(date)
4904
4905
4905 exact = opts.get('exact')
4906 exact = opts.get('exact')
4906 update = not opts.get('bypass')
4907 update = not opts.get('bypass')
4907 if not update and opts.get('no_commit'):
4908 if not update and opts.get('no_commit'):
4908 raise error.Abort(_('cannot use --no-commit with --bypass'))
4909 raise error.Abort(_('cannot use --no-commit with --bypass'))
4909 try:
4910 try:
4910 sim = float(opts.get('similarity') or 0)
4911 sim = float(opts.get('similarity') or 0)
4911 except ValueError:
4912 except ValueError:
4912 raise error.Abort(_('similarity must be a number'))
4913 raise error.Abort(_('similarity must be a number'))
4913 if sim < 0 or sim > 100:
4914 if sim < 0 or sim > 100:
4914 raise error.Abort(_('similarity must be between 0 and 100'))
4915 raise error.Abort(_('similarity must be between 0 and 100'))
4915 if sim and not update:
4916 if sim and not update:
4916 raise error.Abort(_('cannot use --similarity with --bypass'))
4917 raise error.Abort(_('cannot use --similarity with --bypass'))
4917 if exact:
4918 if exact:
4918 if opts.get('edit'):
4919 if opts.get('edit'):
4919 raise error.Abort(_('cannot use --exact with --edit'))
4920 raise error.Abort(_('cannot use --exact with --edit'))
4920 if opts.get('prefix'):
4921 if opts.get('prefix'):
4921 raise error.Abort(_('cannot use --exact with --prefix'))
4922 raise error.Abort(_('cannot use --exact with --prefix'))
4922
4923
4923 base = opts["base"]
4924 base = opts["base"]
4924 wlock = dsguard = lock = tr = None
4925 wlock = dsguard = lock = tr = None
4925 msgs = []
4926 msgs = []
4926 ret = 0
4927 ret = 0
4927
4928
4928
4929
4929 try:
4930 try:
4930 wlock = repo.wlock()
4931 wlock = repo.wlock()
4931
4932
4932 if update:
4933 if update:
4933 cmdutil.checkunfinished(repo)
4934 cmdutil.checkunfinished(repo)
4934 if (exact or not opts.get('force')):
4935 if (exact or not opts.get('force')):
4935 cmdutil.bailifchanged(repo)
4936 cmdutil.bailifchanged(repo)
4936
4937
4937 if not opts.get('no_commit'):
4938 if not opts.get('no_commit'):
4938 lock = repo.lock()
4939 lock = repo.lock()
4939 tr = repo.transaction('import')
4940 tr = repo.transaction('import')
4940 else:
4941 else:
4941 dsguard = cmdutil.dirstateguard(repo, 'import')
4942 dsguard = cmdutil.dirstateguard(repo, 'import')
4942 parents = repo[None].parents()
4943 parents = repo[None].parents()
4943 for patchurl in patches:
4944 for patchurl in patches:
4944 if patchurl == '-':
4945 if patchurl == '-':
4945 ui.status(_('applying patch from stdin\n'))
4946 ui.status(_('applying patch from stdin\n'))
4946 patchfile = ui.fin
4947 patchfile = ui.fin
4947 patchurl = 'stdin' # for error message
4948 patchurl = 'stdin' # for error message
4948 else:
4949 else:
4949 patchurl = os.path.join(base, patchurl)
4950 patchurl = os.path.join(base, patchurl)
4950 ui.status(_('applying %s\n') % patchurl)
4951 ui.status(_('applying %s\n') % patchurl)
4951 patchfile = hg.openpath(ui, patchurl)
4952 patchfile = hg.openpath(ui, patchurl)
4952
4953
4953 haspatch = False
4954 haspatch = False
4954 for hunk in patch.split(patchfile):
4955 for hunk in patch.split(patchfile):
4955 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
4956 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
4956 parents, opts,
4957 parents, opts,
4957 msgs, hg.clean)
4958 msgs, hg.clean)
4958 if msg:
4959 if msg:
4959 haspatch = True
4960 haspatch = True
4960 ui.note(msg + '\n')
4961 ui.note(msg + '\n')
4961 if update or exact:
4962 if update or exact:
4962 parents = repo[None].parents()
4963 parents = repo[None].parents()
4963 else:
4964 else:
4964 parents = [repo[node]]
4965 parents = [repo[node]]
4965 if rej:
4966 if rej:
4966 ui.write_err(_("patch applied partially\n"))
4967 ui.write_err(_("patch applied partially\n"))
4967 ui.write_err(_("(fix the .rej files and run "
4968 ui.write_err(_("(fix the .rej files and run "
4968 "`hg commit --amend`)\n"))
4969 "`hg commit --amend`)\n"))
4969 ret = 1
4970 ret = 1
4970 break
4971 break
4971
4972
4972 if not haspatch:
4973 if not haspatch:
4973 raise error.Abort(_('%s: no diffs found') % patchurl)
4974 raise error.Abort(_('%s: no diffs found') % patchurl)
4974
4975
4975 if tr:
4976 if tr:
4976 tr.close()
4977 tr.close()
4977 if msgs:
4978 if msgs:
4978 repo.savecommitmessage('\n* * *\n'.join(msgs))
4979 repo.savecommitmessage('\n* * *\n'.join(msgs))
4979 if dsguard:
4980 if dsguard:
4980 dsguard.close()
4981 dsguard.close()
4981 return ret
4982 return ret
4982 finally:
4983 finally:
4983 if tr:
4984 if tr:
4984 tr.release()
4985 tr.release()
4985 release(lock, dsguard, wlock)
4986 release(lock, dsguard, wlock)
4986
4987
4987 @command('incoming|in',
4988 @command('incoming|in',
4988 [('f', 'force', None,
4989 [('f', 'force', None,
4989 _('run even if remote repository is unrelated')),
4990 _('run even if remote repository is unrelated')),
4990 ('n', 'newest-first', None, _('show newest record first')),
4991 ('n', 'newest-first', None, _('show newest record first')),
4991 ('', 'bundle', '',
4992 ('', 'bundle', '',
4992 _('file to store the bundles into'), _('FILE')),
4993 _('file to store the bundles into'), _('FILE')),
4993 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4994 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4994 ('B', 'bookmarks', False, _("compare bookmarks")),
4995 ('B', 'bookmarks', False, _("compare bookmarks")),
4995 ('b', 'branch', [],
4996 ('b', 'branch', [],
4996 _('a specific branch you would like to pull'), _('BRANCH')),
4997 _('a specific branch you would like to pull'), _('BRANCH')),
4997 ] + logopts + remoteopts + subrepoopts,
4998 ] + logopts + remoteopts + subrepoopts,
4998 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
4999 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
4999 def incoming(ui, repo, source="default", **opts):
5000 def incoming(ui, repo, source="default", **opts):
5000 """show new changesets found in source
5001 """show new changesets found in source
5001
5002
5002 Show new changesets found in the specified path/URL or the default
5003 Show new changesets found in the specified path/URL or the default
5003 pull location. These are the changesets that would have been pulled
5004 pull location. These are the changesets that would have been pulled
5004 if a pull at the time you issued this command.
5005 if a pull at the time you issued this command.
5005
5006
5006 See pull for valid source format details.
5007 See pull for valid source format details.
5007
5008
5008 .. container:: verbose
5009 .. container:: verbose
5009
5010
5010 With -B/--bookmarks, the result of bookmark comparison between
5011 With -B/--bookmarks, the result of bookmark comparison between
5011 local and remote repositories is displayed. With -v/--verbose,
5012 local and remote repositories is displayed. With -v/--verbose,
5012 status is also displayed for each bookmark like below::
5013 status is also displayed for each bookmark like below::
5013
5014
5014 BM1 01234567890a added
5015 BM1 01234567890a added
5015 BM2 1234567890ab advanced
5016 BM2 1234567890ab advanced
5016 BM3 234567890abc diverged
5017 BM3 234567890abc diverged
5017 BM4 34567890abcd changed
5018 BM4 34567890abcd changed
5018
5019
5019 The action taken locally when pulling depends on the
5020 The action taken locally when pulling depends on the
5020 status of each bookmark:
5021 status of each bookmark:
5021
5022
5022 :``added``: pull will create it
5023 :``added``: pull will create it
5023 :``advanced``: pull will update it
5024 :``advanced``: pull will update it
5024 :``diverged``: pull will create a divergent bookmark
5025 :``diverged``: pull will create a divergent bookmark
5025 :``changed``: result depends on remote changesets
5026 :``changed``: result depends on remote changesets
5026
5027
5027 From the point of view of pulling behavior, bookmark
5028 From the point of view of pulling behavior, bookmark
5028 existing only in the remote repository are treated as ``added``,
5029 existing only in the remote repository are treated as ``added``,
5029 even if it is in fact locally deleted.
5030 even if it is in fact locally deleted.
5030
5031
5031 .. container:: verbose
5032 .. container:: verbose
5032
5033
5033 For remote repository, using --bundle avoids downloading the
5034 For remote repository, using --bundle avoids downloading the
5034 changesets twice if the incoming is followed by a pull.
5035 changesets twice if the incoming is followed by a pull.
5035
5036
5036 Examples:
5037 Examples:
5037
5038
5038 - show incoming changes with patches and full description::
5039 - show incoming changes with patches and full description::
5039
5040
5040 hg incoming -vp
5041 hg incoming -vp
5041
5042
5042 - show incoming changes excluding merges, store a bundle::
5043 - show incoming changes excluding merges, store a bundle::
5043
5044
5044 hg in -vpM --bundle incoming.hg
5045 hg in -vpM --bundle incoming.hg
5045 hg pull incoming.hg
5046 hg pull incoming.hg
5046
5047
5047 - briefly list changes inside a bundle::
5048 - briefly list changes inside a bundle::
5048
5049
5049 hg in changes.hg -T "{desc|firstline}\\n"
5050 hg in changes.hg -T "{desc|firstline}\\n"
5050
5051
5051 Returns 0 if there are incoming changes, 1 otherwise.
5052 Returns 0 if there are incoming changes, 1 otherwise.
5052 """
5053 """
5053 if opts.get('graph'):
5054 if opts.get('graph'):
5054 cmdutil.checkunsupportedgraphflags([], opts)
5055 cmdutil.checkunsupportedgraphflags([], opts)
5055 def display(other, chlist, displayer):
5056 def display(other, chlist, displayer):
5056 revdag = cmdutil.graphrevs(other, chlist, opts)
5057 revdag = cmdutil.graphrevs(other, chlist, opts)
5057 cmdutil.displaygraph(ui, repo, revdag, displayer,
5058 cmdutil.displaygraph(ui, repo, revdag, displayer,
5058 graphmod.asciiedges)
5059 graphmod.asciiedges)
5059
5060
5060 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
5061 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
5061 return 0
5062 return 0
5062
5063
5063 if opts.get('bundle') and opts.get('subrepos'):
5064 if opts.get('bundle') and opts.get('subrepos'):
5064 raise error.Abort(_('cannot combine --bundle and --subrepos'))
5065 raise error.Abort(_('cannot combine --bundle and --subrepos'))
5065
5066
5066 if opts.get('bookmarks'):
5067 if opts.get('bookmarks'):
5067 source, branches = hg.parseurl(ui.expandpath(source),
5068 source, branches = hg.parseurl(ui.expandpath(source),
5068 opts.get('branch'))
5069 opts.get('branch'))
5069 other = hg.peer(repo, opts, source)
5070 other = hg.peer(repo, opts, source)
5070 if 'bookmarks' not in other.listkeys('namespaces'):
5071 if 'bookmarks' not in other.listkeys('namespaces'):
5071 ui.warn(_("remote doesn't support bookmarks\n"))
5072 ui.warn(_("remote doesn't support bookmarks\n"))
5072 return 0
5073 return 0
5073 ui.status(_('comparing with %s\n') % util.hidepassword(source))
5074 ui.status(_('comparing with %s\n') % util.hidepassword(source))
5074 return bookmarks.incoming(ui, repo, other)
5075 return bookmarks.incoming(ui, repo, other)
5075
5076
5076 repo._subtoppath = ui.expandpath(source)
5077 repo._subtoppath = ui.expandpath(source)
5077 try:
5078 try:
5078 return hg.incoming(ui, repo, source, opts)
5079 return hg.incoming(ui, repo, source, opts)
5079 finally:
5080 finally:
5080 del repo._subtoppath
5081 del repo._subtoppath
5081
5082
5082
5083
5083 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
5084 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
5084 norepo=True)
5085 norepo=True)
5085 def init(ui, dest=".", **opts):
5086 def init(ui, dest=".", **opts):
5086 """create a new repository in the given directory
5087 """create a new repository in the given directory
5087
5088
5088 Initialize a new repository in the given directory. If the given
5089 Initialize a new repository in the given directory. If the given
5089 directory does not exist, it will be created.
5090 directory does not exist, it will be created.
5090
5091
5091 If no directory is given, the current directory is used.
5092 If no directory is given, the current directory is used.
5092
5093
5093 It is possible to specify an ``ssh://`` URL as the destination.
5094 It is possible to specify an ``ssh://`` URL as the destination.
5094 See :hg:`help urls` for more information.
5095 See :hg:`help urls` for more information.
5095
5096
5096 Returns 0 on success.
5097 Returns 0 on success.
5097 """
5098 """
5098 hg.peer(ui, opts, ui.expandpath(dest), create=True)
5099 hg.peer(ui, opts, ui.expandpath(dest), create=True)
5099
5100
5100 @command('locate',
5101 @command('locate',
5101 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
5102 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
5102 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5103 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5103 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
5104 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
5104 ] + walkopts,
5105 ] + walkopts,
5105 _('[OPTION]... [PATTERN]...'))
5106 _('[OPTION]... [PATTERN]...'))
5106 def locate(ui, repo, *pats, **opts):
5107 def locate(ui, repo, *pats, **opts):
5107 """locate files matching specific patterns (DEPRECATED)
5108 """locate files matching specific patterns (DEPRECATED)
5108
5109
5109 Print files under Mercurial control in the working directory whose
5110 Print files under Mercurial control in the working directory whose
5110 names match the given patterns.
5111 names match the given patterns.
5111
5112
5112 By default, this command searches all directories in the working
5113 By default, this command searches all directories in the working
5113 directory. To search just the current directory and its
5114 directory. To search just the current directory and its
5114 subdirectories, use "--include .".
5115 subdirectories, use "--include .".
5115
5116
5116 If no patterns are given to match, this command prints the names
5117 If no patterns are given to match, this command prints the names
5117 of all files under Mercurial control in the working directory.
5118 of all files under Mercurial control in the working directory.
5118
5119
5119 If you want to feed the output of this command into the "xargs"
5120 If you want to feed the output of this command into the "xargs"
5120 command, use the -0 option to both this command and "xargs". This
5121 command, use the -0 option to both this command and "xargs". This
5121 will avoid the problem of "xargs" treating single filenames that
5122 will avoid the problem of "xargs" treating single filenames that
5122 contain whitespace as multiple filenames.
5123 contain whitespace as multiple filenames.
5123
5124
5124 See :hg:`help files` for a more versatile command.
5125 See :hg:`help files` for a more versatile command.
5125
5126
5126 Returns 0 if a match is found, 1 otherwise.
5127 Returns 0 if a match is found, 1 otherwise.
5127 """
5128 """
5128 if opts.get('print0'):
5129 if opts.get('print0'):
5129 end = '\0'
5130 end = '\0'
5130 else:
5131 else:
5131 end = '\n'
5132 end = '\n'
5132 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
5133 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
5133
5134
5134 ret = 1
5135 ret = 1
5135 ctx = repo[rev]
5136 ctx = repo[rev]
5136 m = scmutil.match(ctx, pats, opts, default='relglob',
5137 m = scmutil.match(ctx, pats, opts, default='relglob',
5137 badfn=lambda x, y: False)
5138 badfn=lambda x, y: False)
5138
5139
5139 for abs in ctx.matches(m):
5140 for abs in ctx.matches(m):
5140 if opts.get('fullpath'):
5141 if opts.get('fullpath'):
5141 ui.write(repo.wjoin(abs), end)
5142 ui.write(repo.wjoin(abs), end)
5142 else:
5143 else:
5143 ui.write(((pats and m.rel(abs)) or abs), end)
5144 ui.write(((pats and m.rel(abs)) or abs), end)
5144 ret = 0
5145 ret = 0
5145
5146
5146 return ret
5147 return ret
5147
5148
5148 @command('^log|history',
5149 @command('^log|history',
5149 [('f', 'follow', None,
5150 [('f', 'follow', None,
5150 _('follow changeset history, or file history across copies and renames')),
5151 _('follow changeset history, or file history across copies and renames')),
5151 ('', 'follow-first', None,
5152 ('', 'follow-first', None,
5152 _('only follow the first parent of merge changesets (DEPRECATED)')),
5153 _('only follow the first parent of merge changesets (DEPRECATED)')),
5153 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
5154 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
5154 ('C', 'copies', None, _('show copied files')),
5155 ('C', 'copies', None, _('show copied files')),
5155 ('k', 'keyword', [],
5156 ('k', 'keyword', [],
5156 _('do case-insensitive search for a given text'), _('TEXT')),
5157 _('do case-insensitive search for a given text'), _('TEXT')),
5157 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
5158 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
5158 ('', 'removed', None, _('include revisions where files were removed')),
5159 ('', 'removed', None, _('include revisions where files were removed')),
5159 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
5160 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
5160 ('u', 'user', [], _('revisions committed by user'), _('USER')),
5161 ('u', 'user', [], _('revisions committed by user'), _('USER')),
5161 ('', 'only-branch', [],
5162 ('', 'only-branch', [],
5162 _('show only changesets within the given named branch (DEPRECATED)'),
5163 _('show only changesets within the given named branch (DEPRECATED)'),
5163 _('BRANCH')),
5164 _('BRANCH')),
5164 ('b', 'branch', [],
5165 ('b', 'branch', [],
5165 _('show changesets within the given named branch'), _('BRANCH')),
5166 _('show changesets within the given named branch'), _('BRANCH')),
5166 ('P', 'prune', [],
5167 ('P', 'prune', [],
5167 _('do not display revision or any of its ancestors'), _('REV')),
5168 _('do not display revision or any of its ancestors'), _('REV')),
5168 ] + logopts + walkopts,
5169 ] + logopts + walkopts,
5169 _('[OPTION]... [FILE]'),
5170 _('[OPTION]... [FILE]'),
5170 inferrepo=True)
5171 inferrepo=True)
5171 def log(ui, repo, *pats, **opts):
5172 def log(ui, repo, *pats, **opts):
5172 """show revision history of entire repository or files
5173 """show revision history of entire repository or files
5173
5174
5174 Print the revision history of the specified files or the entire
5175 Print the revision history of the specified files or the entire
5175 project.
5176 project.
5176
5177
5177 If no revision range is specified, the default is ``tip:0`` unless
5178 If no revision range is specified, the default is ``tip:0`` unless
5178 --follow is set, in which case the working directory parent is
5179 --follow is set, in which case the working directory parent is
5179 used as the starting revision.
5180 used as the starting revision.
5180
5181
5181 File history is shown without following rename or copy history of
5182 File history is shown without following rename or copy history of
5182 files. Use -f/--follow with a filename to follow history across
5183 files. Use -f/--follow with a filename to follow history across
5183 renames and copies. --follow without a filename will only show
5184 renames and copies. --follow without a filename will only show
5184 ancestors or descendants of the starting revision.
5185 ancestors or descendants of the starting revision.
5185
5186
5186 By default this command prints revision number and changeset id,
5187 By default this command prints revision number and changeset id,
5187 tags, non-trivial parents, user, date and time, and a summary for
5188 tags, non-trivial parents, user, date and time, and a summary for
5188 each commit. When the -v/--verbose switch is used, the list of
5189 each commit. When the -v/--verbose switch is used, the list of
5189 changed files and full commit message are shown.
5190 changed files and full commit message are shown.
5190
5191
5191 With --graph the revisions are shown as an ASCII art DAG with the most
5192 With --graph the revisions are shown as an ASCII art DAG with the most
5192 recent changeset at the top.
5193 recent changeset at the top.
5193 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
5194 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
5194 and '+' represents a fork where the changeset from the lines below is a
5195 and '+' represents a fork where the changeset from the lines below is a
5195 parent of the 'o' merge on the same line.
5196 parent of the 'o' merge on the same line.
5196
5197
5197 .. note::
5198 .. note::
5198
5199
5199 :hg:`log --patch` may generate unexpected diff output for merge
5200 :hg:`log --patch` may generate unexpected diff output for merge
5200 changesets, as it will only compare the merge changeset against
5201 changesets, as it will only compare the merge changeset against
5201 its first parent. Also, only files different from BOTH parents
5202 its first parent. Also, only files different from BOTH parents
5202 will appear in files:.
5203 will appear in files:.
5203
5204
5204 .. note::
5205 .. note::
5205
5206
5206 For performance reasons, :hg:`log FILE` may omit duplicate changes
5207 For performance reasons, :hg:`log FILE` may omit duplicate changes
5207 made on branches and will not show removals or mode changes. To
5208 made on branches and will not show removals or mode changes. To
5208 see all such changes, use the --removed switch.
5209 see all such changes, use the --removed switch.
5209
5210
5210 .. container:: verbose
5211 .. container:: verbose
5211
5212
5212 Some examples:
5213 Some examples:
5213
5214
5214 - changesets with full descriptions and file lists::
5215 - changesets with full descriptions and file lists::
5215
5216
5216 hg log -v
5217 hg log -v
5217
5218
5218 - changesets ancestral to the working directory::
5219 - changesets ancestral to the working directory::
5219
5220
5220 hg log -f
5221 hg log -f
5221
5222
5222 - last 10 commits on the current branch::
5223 - last 10 commits on the current branch::
5223
5224
5224 hg log -l 10 -b .
5225 hg log -l 10 -b .
5225
5226
5226 - changesets showing all modifications of a file, including removals::
5227 - changesets showing all modifications of a file, including removals::
5227
5228
5228 hg log --removed file.c
5229 hg log --removed file.c
5229
5230
5230 - all changesets that touch a directory, with diffs, excluding merges::
5231 - all changesets that touch a directory, with diffs, excluding merges::
5231
5232
5232 hg log -Mp lib/
5233 hg log -Mp lib/
5233
5234
5234 - all revision numbers that match a keyword::
5235 - all revision numbers that match a keyword::
5235
5236
5236 hg log -k bug --template "{rev}\\n"
5237 hg log -k bug --template "{rev}\\n"
5237
5238
5238 - the full hash identifier of the working directory parent::
5239 - the full hash identifier of the working directory parent::
5239
5240
5240 hg log -r . --template "{node}\\n"
5241 hg log -r . --template "{node}\\n"
5241
5242
5242 - list available log templates::
5243 - list available log templates::
5243
5244
5244 hg log -T list
5245 hg log -T list
5245
5246
5246 - check if a given changeset is included in a tagged release::
5247 - check if a given changeset is included in a tagged release::
5247
5248
5248 hg log -r "a21ccf and ancestor(1.9)"
5249 hg log -r "a21ccf and ancestor(1.9)"
5249
5250
5250 - find all changesets by some user in a date range::
5251 - find all changesets by some user in a date range::
5251
5252
5252 hg log -k alice -d "may 2008 to jul 2008"
5253 hg log -k alice -d "may 2008 to jul 2008"
5253
5254
5254 - summary of all changesets after the last tag::
5255 - summary of all changesets after the last tag::
5255
5256
5256 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
5257 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
5257
5258
5258 See :hg:`help dates` for a list of formats valid for -d/--date.
5259 See :hg:`help dates` for a list of formats valid for -d/--date.
5259
5260
5260 See :hg:`help revisions` and :hg:`help revsets` for more about
5261 See :hg:`help revisions` and :hg:`help revsets` for more about
5261 specifying and ordering revisions.
5262 specifying and ordering revisions.
5262
5263
5263 See :hg:`help templates` for more about pre-packaged styles and
5264 See :hg:`help templates` for more about pre-packaged styles and
5264 specifying custom templates.
5265 specifying custom templates.
5265
5266
5266 Returns 0 on success.
5267 Returns 0 on success.
5267
5268
5268 """
5269 """
5269 if opts.get('follow') and opts.get('rev'):
5270 if opts.get('follow') and opts.get('rev'):
5270 opts['rev'] = [revset.formatspec('reverse(::%lr)', opts.get('rev'))]
5271 opts['rev'] = [revset.formatspec('reverse(::%lr)', opts.get('rev'))]
5271 del opts['follow']
5272 del opts['follow']
5272
5273
5273 if opts.get('graph'):
5274 if opts.get('graph'):
5274 return cmdutil.graphlog(ui, repo, *pats, **opts)
5275 return cmdutil.graphlog(ui, repo, *pats, **opts)
5275
5276
5276 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
5277 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
5277 limit = cmdutil.loglimit(opts)
5278 limit = cmdutil.loglimit(opts)
5278 count = 0
5279 count = 0
5279
5280
5280 getrenamed = None
5281 getrenamed = None
5281 if opts.get('copies'):
5282 if opts.get('copies'):
5282 endrev = None
5283 endrev = None
5283 if opts.get('rev'):
5284 if opts.get('rev'):
5284 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
5285 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
5285 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
5286 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
5286
5287
5287 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
5288 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
5288 for rev in revs:
5289 for rev in revs:
5289 if count == limit:
5290 if count == limit:
5290 break
5291 break
5291 ctx = repo[rev]
5292 ctx = repo[rev]
5292 copies = None
5293 copies = None
5293 if getrenamed is not None and rev:
5294 if getrenamed is not None and rev:
5294 copies = []
5295 copies = []
5295 for fn in ctx.files():
5296 for fn in ctx.files():
5296 rename = getrenamed(fn, rev)
5297 rename = getrenamed(fn, rev)
5297 if rename:
5298 if rename:
5298 copies.append((fn, rename[0]))
5299 copies.append((fn, rename[0]))
5299 if filematcher:
5300 if filematcher:
5300 revmatchfn = filematcher(ctx.rev())
5301 revmatchfn = filematcher(ctx.rev())
5301 else:
5302 else:
5302 revmatchfn = None
5303 revmatchfn = None
5303 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
5304 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
5304 if displayer.flush(ctx):
5305 if displayer.flush(ctx):
5305 count += 1
5306 count += 1
5306
5307
5307 displayer.close()
5308 displayer.close()
5308
5309
5309 @command('manifest',
5310 @command('manifest',
5310 [('r', 'rev', '', _('revision to display'), _('REV')),
5311 [('r', 'rev', '', _('revision to display'), _('REV')),
5311 ('', 'all', False, _("list files from all revisions"))]
5312 ('', 'all', False, _("list files from all revisions"))]
5312 + formatteropts,
5313 + formatteropts,
5313 _('[-r REV]'))
5314 _('[-r REV]'))
5314 def manifest(ui, repo, node=None, rev=None, **opts):
5315 def manifest(ui, repo, node=None, rev=None, **opts):
5315 """output the current or given revision of the project manifest
5316 """output the current or given revision of the project manifest
5316
5317
5317 Print a list of version controlled files for the given revision.
5318 Print a list of version controlled files for the given revision.
5318 If no revision is given, the first parent of the working directory
5319 If no revision is given, the first parent of the working directory
5319 is used, or the null revision if no revision is checked out.
5320 is used, or the null revision if no revision is checked out.
5320
5321
5321 With -v, print file permissions, symlink and executable bits.
5322 With -v, print file permissions, symlink and executable bits.
5322 With --debug, print file revision hashes.
5323 With --debug, print file revision hashes.
5323
5324
5324 If option --all is specified, the list of all files from all revisions
5325 If option --all is specified, the list of all files from all revisions
5325 is printed. This includes deleted and renamed files.
5326 is printed. This includes deleted and renamed files.
5326
5327
5327 Returns 0 on success.
5328 Returns 0 on success.
5328 """
5329 """
5329
5330
5330 fm = ui.formatter('manifest', opts)
5331 fm = ui.formatter('manifest', opts)
5331
5332
5332 if opts.get('all'):
5333 if opts.get('all'):
5333 if rev or node:
5334 if rev or node:
5334 raise error.Abort(_("can't specify a revision with --all"))
5335 raise error.Abort(_("can't specify a revision with --all"))
5335
5336
5336 res = []
5337 res = []
5337 prefix = "data/"
5338 prefix = "data/"
5338 suffix = ".i"
5339 suffix = ".i"
5339 plen = len(prefix)
5340 plen = len(prefix)
5340 slen = len(suffix)
5341 slen = len(suffix)
5341 with repo.lock():
5342 with repo.lock():
5342 for fn, b, size in repo.store.datafiles():
5343 for fn, b, size in repo.store.datafiles():
5343 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
5344 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
5344 res.append(fn[plen:-slen])
5345 res.append(fn[plen:-slen])
5345 for f in res:
5346 for f in res:
5346 fm.startitem()
5347 fm.startitem()
5347 fm.write("path", '%s\n', f)
5348 fm.write("path", '%s\n', f)
5348 fm.end()
5349 fm.end()
5349 return
5350 return
5350
5351
5351 if rev and node:
5352 if rev and node:
5352 raise error.Abort(_("please specify just one revision"))
5353 raise error.Abort(_("please specify just one revision"))
5353
5354
5354 if not node:
5355 if not node:
5355 node = rev
5356 node = rev
5356
5357
5357 char = {'l': '@', 'x': '*', '': ''}
5358 char = {'l': '@', 'x': '*', '': ''}
5358 mode = {'l': '644', 'x': '755', '': '644'}
5359 mode = {'l': '644', 'x': '755', '': '644'}
5359 ctx = scmutil.revsingle(repo, node)
5360 ctx = scmutil.revsingle(repo, node)
5360 mf = ctx.manifest()
5361 mf = ctx.manifest()
5361 for f in ctx:
5362 for f in ctx:
5362 fm.startitem()
5363 fm.startitem()
5363 fl = ctx[f].flags()
5364 fl = ctx[f].flags()
5364 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
5365 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
5365 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
5366 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
5366 fm.write('path', '%s\n', f)
5367 fm.write('path', '%s\n', f)
5367 fm.end()
5368 fm.end()
5368
5369
5369 @command('^merge',
5370 @command('^merge',
5370 [('f', 'force', None,
5371 [('f', 'force', None,
5371 _('force a merge including outstanding changes (DEPRECATED)')),
5372 _('force a merge including outstanding changes (DEPRECATED)')),
5372 ('r', 'rev', '', _('revision to merge'), _('REV')),
5373 ('r', 'rev', '', _('revision to merge'), _('REV')),
5373 ('P', 'preview', None,
5374 ('P', 'preview', None,
5374 _('review revisions to merge (no merge is performed)'))
5375 _('review revisions to merge (no merge is performed)'))
5375 ] + mergetoolopts,
5376 ] + mergetoolopts,
5376 _('[-P] [[-r] REV]'))
5377 _('[-P] [[-r] REV]'))
5377 def merge(ui, repo, node=None, **opts):
5378 def merge(ui, repo, node=None, **opts):
5378 """merge another revision into working directory
5379 """merge another revision into working directory
5379
5380
5380 The current working directory is updated with all changes made in
5381 The current working directory is updated with all changes made in
5381 the requested revision since the last common predecessor revision.
5382 the requested revision since the last common predecessor revision.
5382
5383
5383 Files that changed between either parent are marked as changed for
5384 Files that changed between either parent are marked as changed for
5384 the next commit and a commit must be performed before any further
5385 the next commit and a commit must be performed before any further
5385 updates to the repository are allowed. The next commit will have
5386 updates to the repository are allowed. The next commit will have
5386 two parents.
5387 two parents.
5387
5388
5388 ``--tool`` can be used to specify the merge tool used for file
5389 ``--tool`` can be used to specify the merge tool used for file
5389 merges. It overrides the HGMERGE environment variable and your
5390 merges. It overrides the HGMERGE environment variable and your
5390 configuration files. See :hg:`help merge-tools` for options.
5391 configuration files. See :hg:`help merge-tools` for options.
5391
5392
5392 If no revision is specified, the working directory's parent is a
5393 If no revision is specified, the working directory's parent is a
5393 head revision, and the current branch contains exactly one other
5394 head revision, and the current branch contains exactly one other
5394 head, the other head is merged with by default. Otherwise, an
5395 head, the other head is merged with by default. Otherwise, an
5395 explicit revision with which to merge with must be provided.
5396 explicit revision with which to merge with must be provided.
5396
5397
5397 See :hg:`help resolve` for information on handling file conflicts.
5398 See :hg:`help resolve` for information on handling file conflicts.
5398
5399
5399 To undo an uncommitted merge, use :hg:`update --clean .` which
5400 To undo an uncommitted merge, use :hg:`update --clean .` which
5400 will check out a clean copy of the original merge parent, losing
5401 will check out a clean copy of the original merge parent, losing
5401 all changes.
5402 all changes.
5402
5403
5403 Returns 0 on success, 1 if there are unresolved files.
5404 Returns 0 on success, 1 if there are unresolved files.
5404 """
5405 """
5405
5406
5406 if opts.get('rev') and node:
5407 if opts.get('rev') and node:
5407 raise error.Abort(_("please specify just one revision"))
5408 raise error.Abort(_("please specify just one revision"))
5408 if not node:
5409 if not node:
5409 node = opts.get('rev')
5410 node = opts.get('rev')
5410
5411
5411 if node:
5412 if node:
5412 node = scmutil.revsingle(repo, node).node()
5413 node = scmutil.revsingle(repo, node).node()
5413
5414
5414 if not node:
5415 if not node:
5415 node = repo[destutil.destmerge(repo)].node()
5416 node = repo[destutil.destmerge(repo)].node()
5416
5417
5417 if opts.get('preview'):
5418 if opts.get('preview'):
5418 # find nodes that are ancestors of p2 but not of p1
5419 # find nodes that are ancestors of p2 but not of p1
5419 p1 = repo.lookup('.')
5420 p1 = repo.lookup('.')
5420 p2 = repo.lookup(node)
5421 p2 = repo.lookup(node)
5421 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
5422 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
5422
5423
5423 displayer = cmdutil.show_changeset(ui, repo, opts)
5424 displayer = cmdutil.show_changeset(ui, repo, opts)
5424 for node in nodes:
5425 for node in nodes:
5425 displayer.show(repo[node])
5426 displayer.show(repo[node])
5426 displayer.close()
5427 displayer.close()
5427 return 0
5428 return 0
5428
5429
5429 try:
5430 try:
5430 # ui.forcemerge is an internal variable, do not document
5431 # ui.forcemerge is an internal variable, do not document
5431 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
5432 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
5432 force = opts.get('force')
5433 force = opts.get('force')
5433 return hg.merge(repo, node, force=force, mergeforce=force)
5434 return hg.merge(repo, node, force=force, mergeforce=force)
5434 finally:
5435 finally:
5435 ui.setconfig('ui', 'forcemerge', '', 'merge')
5436 ui.setconfig('ui', 'forcemerge', '', 'merge')
5436
5437
5437 @command('outgoing|out',
5438 @command('outgoing|out',
5438 [('f', 'force', None, _('run even when the destination is unrelated')),
5439 [('f', 'force', None, _('run even when the destination is unrelated')),
5439 ('r', 'rev', [],
5440 ('r', 'rev', [],
5440 _('a changeset intended to be included in the destination'), _('REV')),
5441 _('a changeset intended to be included in the destination'), _('REV')),
5441 ('n', 'newest-first', None, _('show newest record first')),
5442 ('n', 'newest-first', None, _('show newest record first')),
5442 ('B', 'bookmarks', False, _('compare bookmarks')),
5443 ('B', 'bookmarks', False, _('compare bookmarks')),
5443 ('b', 'branch', [], _('a specific branch you would like to push'),
5444 ('b', 'branch', [], _('a specific branch you would like to push'),
5444 _('BRANCH')),
5445 _('BRANCH')),
5445 ] + logopts + remoteopts + subrepoopts,
5446 ] + logopts + remoteopts + subrepoopts,
5446 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
5447 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
5447 def outgoing(ui, repo, dest=None, **opts):
5448 def outgoing(ui, repo, dest=None, **opts):
5448 """show changesets not found in the destination
5449 """show changesets not found in the destination
5449
5450
5450 Show changesets not found in the specified destination repository
5451 Show changesets not found in the specified destination repository
5451 or the default push location. These are the changesets that would
5452 or the default push location. These are the changesets that would
5452 be pushed if a push was requested.
5453 be pushed if a push was requested.
5453
5454
5454 See pull for details of valid destination formats.
5455 See pull for details of valid destination formats.
5455
5456
5456 .. container:: verbose
5457 .. container:: verbose
5457
5458
5458 With -B/--bookmarks, the result of bookmark comparison between
5459 With -B/--bookmarks, the result of bookmark comparison between
5459 local and remote repositories is displayed. With -v/--verbose,
5460 local and remote repositories is displayed. With -v/--verbose,
5460 status is also displayed for each bookmark like below::
5461 status is also displayed for each bookmark like below::
5461
5462
5462 BM1 01234567890a added
5463 BM1 01234567890a added
5463 BM2 deleted
5464 BM2 deleted
5464 BM3 234567890abc advanced
5465 BM3 234567890abc advanced
5465 BM4 34567890abcd diverged
5466 BM4 34567890abcd diverged
5466 BM5 4567890abcde changed
5467 BM5 4567890abcde changed
5467
5468
5468 The action taken when pushing depends on the
5469 The action taken when pushing depends on the
5469 status of each bookmark:
5470 status of each bookmark:
5470
5471
5471 :``added``: push with ``-B`` will create it
5472 :``added``: push with ``-B`` will create it
5472 :``deleted``: push with ``-B`` will delete it
5473 :``deleted``: push with ``-B`` will delete it
5473 :``advanced``: push will update it
5474 :``advanced``: push will update it
5474 :``diverged``: push with ``-B`` will update it
5475 :``diverged``: push with ``-B`` will update it
5475 :``changed``: push with ``-B`` will update it
5476 :``changed``: push with ``-B`` will update it
5476
5477
5477 From the point of view of pushing behavior, bookmarks
5478 From the point of view of pushing behavior, bookmarks
5478 existing only in the remote repository are treated as
5479 existing only in the remote repository are treated as
5479 ``deleted``, even if it is in fact added remotely.
5480 ``deleted``, even if it is in fact added remotely.
5480
5481
5481 Returns 0 if there are outgoing changes, 1 otherwise.
5482 Returns 0 if there are outgoing changes, 1 otherwise.
5482 """
5483 """
5483 if opts.get('graph'):
5484 if opts.get('graph'):
5484 cmdutil.checkunsupportedgraphflags([], opts)
5485 cmdutil.checkunsupportedgraphflags([], opts)
5485 o, other = hg._outgoing(ui, repo, dest, opts)
5486 o, other = hg._outgoing(ui, repo, dest, opts)
5486 if not o:
5487 if not o:
5487 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5488 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5488 return
5489 return
5489
5490
5490 revdag = cmdutil.graphrevs(repo, o, opts)
5491 revdag = cmdutil.graphrevs(repo, o, opts)
5491 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
5492 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
5492 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
5493 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
5493 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5494 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5494 return 0
5495 return 0
5495
5496
5496 if opts.get('bookmarks'):
5497 if opts.get('bookmarks'):
5497 dest = ui.expandpath(dest or 'default-push', dest or 'default')
5498 dest = ui.expandpath(dest or 'default-push', dest or 'default')
5498 dest, branches = hg.parseurl(dest, opts.get('branch'))
5499 dest, branches = hg.parseurl(dest, opts.get('branch'))
5499 other = hg.peer(repo, opts, dest)
5500 other = hg.peer(repo, opts, dest)
5500 if 'bookmarks' not in other.listkeys('namespaces'):
5501 if 'bookmarks' not in other.listkeys('namespaces'):
5501 ui.warn(_("remote doesn't support bookmarks\n"))
5502 ui.warn(_("remote doesn't support bookmarks\n"))
5502 return 0
5503 return 0
5503 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
5504 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
5504 return bookmarks.outgoing(ui, repo, other)
5505 return bookmarks.outgoing(ui, repo, other)
5505
5506
5506 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
5507 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
5507 try:
5508 try:
5508 return hg.outgoing(ui, repo, dest, opts)
5509 return hg.outgoing(ui, repo, dest, opts)
5509 finally:
5510 finally:
5510 del repo._subtoppath
5511 del repo._subtoppath
5511
5512
5512 @command('parents',
5513 @command('parents',
5513 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
5514 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
5514 ] + templateopts,
5515 ] + templateopts,
5515 _('[-r REV] [FILE]'),
5516 _('[-r REV] [FILE]'),
5516 inferrepo=True)
5517 inferrepo=True)
5517 def parents(ui, repo, file_=None, **opts):
5518 def parents(ui, repo, file_=None, **opts):
5518 """show the parents of the working directory or revision (DEPRECATED)
5519 """show the parents of the working directory or revision (DEPRECATED)
5519
5520
5520 Print the working directory's parent revisions. If a revision is
5521 Print the working directory's parent revisions. If a revision is
5521 given via -r/--rev, the parent of that revision will be printed.
5522 given via -r/--rev, the parent of that revision will be printed.
5522 If a file argument is given, the revision in which the file was
5523 If a file argument is given, the revision in which the file was
5523 last changed (before the working directory revision or the
5524 last changed (before the working directory revision or the
5524 argument to --rev if given) is printed.
5525 argument to --rev if given) is printed.
5525
5526
5526 This command is equivalent to::
5527 This command is equivalent to::
5527
5528
5528 hg log -r "p1()+p2()" or
5529 hg log -r "p1()+p2()" or
5529 hg log -r "p1(REV)+p2(REV)" or
5530 hg log -r "p1(REV)+p2(REV)" or
5530 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5531 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5531 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5532 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5532
5533
5533 See :hg:`summary` and :hg:`help revsets` for related information.
5534 See :hg:`summary` and :hg:`help revsets` for related information.
5534
5535
5535 Returns 0 on success.
5536 Returns 0 on success.
5536 """
5537 """
5537
5538
5538 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
5539 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
5539
5540
5540 if file_:
5541 if file_:
5541 m = scmutil.match(ctx, (file_,), opts)
5542 m = scmutil.match(ctx, (file_,), opts)
5542 if m.anypats() or len(m.files()) != 1:
5543 if m.anypats() or len(m.files()) != 1:
5543 raise error.Abort(_('can only specify an explicit filename'))
5544 raise error.Abort(_('can only specify an explicit filename'))
5544 file_ = m.files()[0]
5545 file_ = m.files()[0]
5545 filenodes = []
5546 filenodes = []
5546 for cp in ctx.parents():
5547 for cp in ctx.parents():
5547 if not cp:
5548 if not cp:
5548 continue
5549 continue
5549 try:
5550 try:
5550 filenodes.append(cp.filenode(file_))
5551 filenodes.append(cp.filenode(file_))
5551 except error.LookupError:
5552 except error.LookupError:
5552 pass
5553 pass
5553 if not filenodes:
5554 if not filenodes:
5554 raise error.Abort(_("'%s' not found in manifest!") % file_)
5555 raise error.Abort(_("'%s' not found in manifest!") % file_)
5555 p = []
5556 p = []
5556 for fn in filenodes:
5557 for fn in filenodes:
5557 fctx = repo.filectx(file_, fileid=fn)
5558 fctx = repo.filectx(file_, fileid=fn)
5558 p.append(fctx.node())
5559 p.append(fctx.node())
5559 else:
5560 else:
5560 p = [cp.node() for cp in ctx.parents()]
5561 p = [cp.node() for cp in ctx.parents()]
5561
5562
5562 displayer = cmdutil.show_changeset(ui, repo, opts)
5563 displayer = cmdutil.show_changeset(ui, repo, opts)
5563 for n in p:
5564 for n in p:
5564 if n != nullid:
5565 if n != nullid:
5565 displayer.show(repo[n])
5566 displayer.show(repo[n])
5566 displayer.close()
5567 displayer.close()
5567
5568
5568 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True)
5569 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True)
5569 def paths(ui, repo, search=None, **opts):
5570 def paths(ui, repo, search=None, **opts):
5570 """show aliases for remote repositories
5571 """show aliases for remote repositories
5571
5572
5572 Show definition of symbolic path name NAME. If no name is given,
5573 Show definition of symbolic path name NAME. If no name is given,
5573 show definition of all available names.
5574 show definition of all available names.
5574
5575
5575 Option -q/--quiet suppresses all output when searching for NAME
5576 Option -q/--quiet suppresses all output when searching for NAME
5576 and shows only the path names when listing all definitions.
5577 and shows only the path names when listing all definitions.
5577
5578
5578 Path names are defined in the [paths] section of your
5579 Path names are defined in the [paths] section of your
5579 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5580 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5580 repository, ``.hg/hgrc`` is used, too.
5581 repository, ``.hg/hgrc`` is used, too.
5581
5582
5582 The path names ``default`` and ``default-push`` have a special
5583 The path names ``default`` and ``default-push`` have a special
5583 meaning. When performing a push or pull operation, they are used
5584 meaning. When performing a push or pull operation, they are used
5584 as fallbacks if no location is specified on the command-line.
5585 as fallbacks if no location is specified on the command-line.
5585 When ``default-push`` is set, it will be used for push and
5586 When ``default-push`` is set, it will be used for push and
5586 ``default`` will be used for pull; otherwise ``default`` is used
5587 ``default`` will be used for pull; otherwise ``default`` is used
5587 as the fallback for both. When cloning a repository, the clone
5588 as the fallback for both. When cloning a repository, the clone
5588 source is written as ``default`` in ``.hg/hgrc``.
5589 source is written as ``default`` in ``.hg/hgrc``.
5589
5590
5590 .. note::
5591 .. note::
5591
5592
5592 ``default`` and ``default-push`` apply to all inbound (e.g.
5593 ``default`` and ``default-push`` apply to all inbound (e.g.
5593 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5594 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5594 and :hg:`bundle`) operations.
5595 and :hg:`bundle`) operations.
5595
5596
5596 See :hg:`help urls` for more information.
5597 See :hg:`help urls` for more information.
5597
5598
5598 Returns 0 on success.
5599 Returns 0 on success.
5599 """
5600 """
5600 if search:
5601 if search:
5601 pathitems = [(name, path) for name, path in ui.paths.iteritems()
5602 pathitems = [(name, path) for name, path in ui.paths.iteritems()
5602 if name == search]
5603 if name == search]
5603 else:
5604 else:
5604 pathitems = sorted(ui.paths.iteritems())
5605 pathitems = sorted(ui.paths.iteritems())
5605
5606
5606 fm = ui.formatter('paths', opts)
5607 fm = ui.formatter('paths', opts)
5607 if fm:
5608 if fm:
5608 hidepassword = str
5609 hidepassword = str
5609 else:
5610 else:
5610 hidepassword = util.hidepassword
5611 hidepassword = util.hidepassword
5611 if ui.quiet:
5612 if ui.quiet:
5612 namefmt = '%s\n'
5613 namefmt = '%s\n'
5613 else:
5614 else:
5614 namefmt = '%s = '
5615 namefmt = '%s = '
5615 showsubopts = not search and not ui.quiet
5616 showsubopts = not search and not ui.quiet
5616
5617
5617 for name, path in pathitems:
5618 for name, path in pathitems:
5618 fm.startitem()
5619 fm.startitem()
5619 fm.condwrite(not search, 'name', namefmt, name)
5620 fm.condwrite(not search, 'name', namefmt, name)
5620 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
5621 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
5621 for subopt, value in sorted(path.suboptions.items()):
5622 for subopt, value in sorted(path.suboptions.items()):
5622 assert subopt not in ('name', 'url')
5623 assert subopt not in ('name', 'url')
5623 if showsubopts:
5624 if showsubopts:
5624 fm.plain('%s:%s = ' % (name, subopt))
5625 fm.plain('%s:%s = ' % (name, subopt))
5625 fm.condwrite(showsubopts, subopt, '%s\n', value)
5626 fm.condwrite(showsubopts, subopt, '%s\n', value)
5626
5627
5627 fm.end()
5628 fm.end()
5628
5629
5629 if search and not pathitems:
5630 if search and not pathitems:
5630 if not ui.quiet:
5631 if not ui.quiet:
5631 ui.warn(_("not found!\n"))
5632 ui.warn(_("not found!\n"))
5632 return 1
5633 return 1
5633 else:
5634 else:
5634 return 0
5635 return 0
5635
5636
5636 @command('phase',
5637 @command('phase',
5637 [('p', 'public', False, _('set changeset phase to public')),
5638 [('p', 'public', False, _('set changeset phase to public')),
5638 ('d', 'draft', False, _('set changeset phase to draft')),
5639 ('d', 'draft', False, _('set changeset phase to draft')),
5639 ('s', 'secret', False, _('set changeset phase to secret')),
5640 ('s', 'secret', False, _('set changeset phase to secret')),
5640 ('f', 'force', False, _('allow to move boundary backward')),
5641 ('f', 'force', False, _('allow to move boundary backward')),
5641 ('r', 'rev', [], _('target revision'), _('REV')),
5642 ('r', 'rev', [], _('target revision'), _('REV')),
5642 ],
5643 ],
5643 _('[-p|-d|-s] [-f] [-r] [REV...]'))
5644 _('[-p|-d|-s] [-f] [-r] [REV...]'))
5644 def phase(ui, repo, *revs, **opts):
5645 def phase(ui, repo, *revs, **opts):
5645 """set or show the current phase name
5646 """set or show the current phase name
5646
5647
5647 With no argument, show the phase name of the current revision(s).
5648 With no argument, show the phase name of the current revision(s).
5648
5649
5649 With one of -p/--public, -d/--draft or -s/--secret, change the
5650 With one of -p/--public, -d/--draft or -s/--secret, change the
5650 phase value of the specified revisions.
5651 phase value of the specified revisions.
5651
5652
5652 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
5653 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
5653 lower phase to an higher phase. Phases are ordered as follows::
5654 lower phase to an higher phase. Phases are ordered as follows::
5654
5655
5655 public < draft < secret
5656 public < draft < secret
5656
5657
5657 Returns 0 on success, 1 if some phases could not be changed.
5658 Returns 0 on success, 1 if some phases could not be changed.
5658
5659
5659 (For more information about the phases concept, see :hg:`help phases`.)
5660 (For more information about the phases concept, see :hg:`help phases`.)
5660 """
5661 """
5661 # search for a unique phase argument
5662 # search for a unique phase argument
5662 targetphase = None
5663 targetphase = None
5663 for idx, name in enumerate(phases.phasenames):
5664 for idx, name in enumerate(phases.phasenames):
5664 if opts[name]:
5665 if opts[name]:
5665 if targetphase is not None:
5666 if targetphase is not None:
5666 raise error.Abort(_('only one phase can be specified'))
5667 raise error.Abort(_('only one phase can be specified'))
5667 targetphase = idx
5668 targetphase = idx
5668
5669
5669 # look for specified revision
5670 # look for specified revision
5670 revs = list(revs)
5671 revs = list(revs)
5671 revs.extend(opts['rev'])
5672 revs.extend(opts['rev'])
5672 if not revs:
5673 if not revs:
5673 # display both parents as the second parent phase can influence
5674 # display both parents as the second parent phase can influence
5674 # the phase of a merge commit
5675 # the phase of a merge commit
5675 revs = [c.rev() for c in repo[None].parents()]
5676 revs = [c.rev() for c in repo[None].parents()]
5676
5677
5677 revs = scmutil.revrange(repo, revs)
5678 revs = scmutil.revrange(repo, revs)
5678
5679
5679 lock = None
5680 lock = None
5680 ret = 0
5681 ret = 0
5681 if targetphase is None:
5682 if targetphase is None:
5682 # display
5683 # display
5683 for r in revs:
5684 for r in revs:
5684 ctx = repo[r]
5685 ctx = repo[r]
5685 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5686 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5686 else:
5687 else:
5687 tr = None
5688 tr = None
5688 lock = repo.lock()
5689 lock = repo.lock()
5689 try:
5690 try:
5690 tr = repo.transaction("phase")
5691 tr = repo.transaction("phase")
5691 # set phase
5692 # set phase
5692 if not revs:
5693 if not revs:
5693 raise error.Abort(_('empty revision set'))
5694 raise error.Abort(_('empty revision set'))
5694 nodes = [repo[r].node() for r in revs]
5695 nodes = [repo[r].node() for r in revs]
5695 # moving revision from public to draft may hide them
5696 # moving revision from public to draft may hide them
5696 # We have to check result on an unfiltered repository
5697 # We have to check result on an unfiltered repository
5697 unfi = repo.unfiltered()
5698 unfi = repo.unfiltered()
5698 getphase = unfi._phasecache.phase
5699 getphase = unfi._phasecache.phase
5699 olddata = [getphase(unfi, r) for r in unfi]
5700 olddata = [getphase(unfi, r) for r in unfi]
5700 phases.advanceboundary(repo, tr, targetphase, nodes)
5701 phases.advanceboundary(repo, tr, targetphase, nodes)
5701 if opts['force']:
5702 if opts['force']:
5702 phases.retractboundary(repo, tr, targetphase, nodes)
5703 phases.retractboundary(repo, tr, targetphase, nodes)
5703 tr.close()
5704 tr.close()
5704 finally:
5705 finally:
5705 if tr is not None:
5706 if tr is not None:
5706 tr.release()
5707 tr.release()
5707 lock.release()
5708 lock.release()
5708 getphase = unfi._phasecache.phase
5709 getphase = unfi._phasecache.phase
5709 newdata = [getphase(unfi, r) for r in unfi]
5710 newdata = [getphase(unfi, r) for r in unfi]
5710 changes = sum(newdata[r] != olddata[r] for r in unfi)
5711 changes = sum(newdata[r] != olddata[r] for r in unfi)
5711 cl = unfi.changelog
5712 cl = unfi.changelog
5712 rejected = [n for n in nodes
5713 rejected = [n for n in nodes
5713 if newdata[cl.rev(n)] < targetphase]
5714 if newdata[cl.rev(n)] < targetphase]
5714 if rejected:
5715 if rejected:
5715 ui.warn(_('cannot move %i changesets to a higher '
5716 ui.warn(_('cannot move %i changesets to a higher '
5716 'phase, use --force\n') % len(rejected))
5717 'phase, use --force\n') % len(rejected))
5717 ret = 1
5718 ret = 1
5718 if changes:
5719 if changes:
5719 msg = _('phase changed for %i changesets\n') % changes
5720 msg = _('phase changed for %i changesets\n') % changes
5720 if ret:
5721 if ret:
5721 ui.status(msg)
5722 ui.status(msg)
5722 else:
5723 else:
5723 ui.note(msg)
5724 ui.note(msg)
5724 else:
5725 else:
5725 ui.warn(_('no phases changed\n'))
5726 ui.warn(_('no phases changed\n'))
5726 return ret
5727 return ret
5727
5728
5728 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5729 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5729 """Run after a changegroup has been added via pull/unbundle
5730 """Run after a changegroup has been added via pull/unbundle
5730
5731
5731 This takes arguments below:
5732 This takes arguments below:
5732
5733
5733 :modheads: change of heads by pull/unbundle
5734 :modheads: change of heads by pull/unbundle
5734 :optupdate: updating working directory is needed or not
5735 :optupdate: updating working directory is needed or not
5735 :checkout: update destination revision (or None to default destination)
5736 :checkout: update destination revision (or None to default destination)
5736 :brev: a name, which might be a bookmark to be activated after updating
5737 :brev: a name, which might be a bookmark to be activated after updating
5737 """
5738 """
5738 if modheads == 0:
5739 if modheads == 0:
5739 return
5740 return
5740 if optupdate:
5741 if optupdate:
5741 try:
5742 try:
5742 return hg.updatetotally(ui, repo, checkout, brev)
5743 return hg.updatetotally(ui, repo, checkout, brev)
5743 except error.UpdateAbort as inst:
5744 except error.UpdateAbort as inst:
5744 msg = _("not updating: %s") % str(inst)
5745 msg = _("not updating: %s") % str(inst)
5745 hint = inst.hint
5746 hint = inst.hint
5746 raise error.UpdateAbort(msg, hint=hint)
5747 raise error.UpdateAbort(msg, hint=hint)
5747 if modheads > 1:
5748 if modheads > 1:
5748 currentbranchheads = len(repo.branchheads())
5749 currentbranchheads = len(repo.branchheads())
5749 if currentbranchheads == modheads:
5750 if currentbranchheads == modheads:
5750 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
5751 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
5751 elif currentbranchheads > 1:
5752 elif currentbranchheads > 1:
5752 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
5753 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
5753 "merge)\n"))
5754 "merge)\n"))
5754 else:
5755 else:
5755 ui.status(_("(run 'hg heads' to see heads)\n"))
5756 ui.status(_("(run 'hg heads' to see heads)\n"))
5756 else:
5757 else:
5757 ui.status(_("(run 'hg update' to get a working copy)\n"))
5758 ui.status(_("(run 'hg update' to get a working copy)\n"))
5758
5759
5759 @command('^pull',
5760 @command('^pull',
5760 [('u', 'update', None,
5761 [('u', 'update', None,
5761 _('update to new branch head if changesets were pulled')),
5762 _('update to new branch head if changesets were pulled')),
5762 ('f', 'force', None, _('run even when remote repository is unrelated')),
5763 ('f', 'force', None, _('run even when remote repository is unrelated')),
5763 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
5764 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
5764 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
5765 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
5765 ('b', 'branch', [], _('a specific branch you would like to pull'),
5766 ('b', 'branch', [], _('a specific branch you would like to pull'),
5766 _('BRANCH')),
5767 _('BRANCH')),
5767 ] + remoteopts,
5768 ] + remoteopts,
5768 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
5769 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
5769 def pull(ui, repo, source="default", **opts):
5770 def pull(ui, repo, source="default", **opts):
5770 """pull changes from the specified source
5771 """pull changes from the specified source
5771
5772
5772 Pull changes from a remote repository to a local one.
5773 Pull changes from a remote repository to a local one.
5773
5774
5774 This finds all changes from the repository at the specified path
5775 This finds all changes from the repository at the specified path
5775 or URL and adds them to a local repository (the current one unless
5776 or URL and adds them to a local repository (the current one unless
5776 -R is specified). By default, this does not update the copy of the
5777 -R is specified). By default, this does not update the copy of the
5777 project in the working directory.
5778 project in the working directory.
5778
5779
5779 Use :hg:`incoming` if you want to see what would have been added
5780 Use :hg:`incoming` if you want to see what would have been added
5780 by a pull at the time you issued this command. If you then decide
5781 by a pull at the time you issued this command. If you then decide
5781 to add those changes to the repository, you should use :hg:`pull
5782 to add those changes to the repository, you should use :hg:`pull
5782 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5783 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5783
5784
5784 If SOURCE is omitted, the 'default' path will be used.
5785 If SOURCE is omitted, the 'default' path will be used.
5785 See :hg:`help urls` for more information.
5786 See :hg:`help urls` for more information.
5786
5787
5787 Specifying bookmark as ``.`` is equivalent to specifying the active
5788 Specifying bookmark as ``.`` is equivalent to specifying the active
5788 bookmark's name.
5789 bookmark's name.
5789
5790
5790 Returns 0 on success, 1 if an update had unresolved files.
5791 Returns 0 on success, 1 if an update had unresolved files.
5791 """
5792 """
5792 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
5793 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
5793 ui.status(_('pulling from %s\n') % util.hidepassword(source))
5794 ui.status(_('pulling from %s\n') % util.hidepassword(source))
5794 other = hg.peer(repo, opts, source)
5795 other = hg.peer(repo, opts, source)
5795 try:
5796 try:
5796 revs, checkout = hg.addbranchrevs(repo, other, branches,
5797 revs, checkout = hg.addbranchrevs(repo, other, branches,
5797 opts.get('rev'))
5798 opts.get('rev'))
5798
5799
5799
5800
5800 pullopargs = {}
5801 pullopargs = {}
5801 if opts.get('bookmark'):
5802 if opts.get('bookmark'):
5802 if not revs:
5803 if not revs:
5803 revs = []
5804 revs = []
5804 # The list of bookmark used here is not the one used to actually
5805 # The list of bookmark used here is not the one used to actually
5805 # update the bookmark name. This can result in the revision pulled
5806 # update the bookmark name. This can result in the revision pulled
5806 # not ending up with the name of the bookmark because of a race
5807 # not ending up with the name of the bookmark because of a race
5807 # condition on the server. (See issue 4689 for details)
5808 # condition on the server. (See issue 4689 for details)
5808 remotebookmarks = other.listkeys('bookmarks')
5809 remotebookmarks = other.listkeys('bookmarks')
5809 pullopargs['remotebookmarks'] = remotebookmarks
5810 pullopargs['remotebookmarks'] = remotebookmarks
5810 for b in opts['bookmark']:
5811 for b in opts['bookmark']:
5811 b = repo._bookmarks.expandname(b)
5812 b = repo._bookmarks.expandname(b)
5812 if b not in remotebookmarks:
5813 if b not in remotebookmarks:
5813 raise error.Abort(_('remote bookmark %s not found!') % b)
5814 raise error.Abort(_('remote bookmark %s not found!') % b)
5814 revs.append(remotebookmarks[b])
5815 revs.append(remotebookmarks[b])
5815
5816
5816 if revs:
5817 if revs:
5817 try:
5818 try:
5818 # When 'rev' is a bookmark name, we cannot guarantee that it
5819 # When 'rev' is a bookmark name, we cannot guarantee that it
5819 # will be updated with that name because of a race condition
5820 # will be updated with that name because of a race condition
5820 # server side. (See issue 4689 for details)
5821 # server side. (See issue 4689 for details)
5821 oldrevs = revs
5822 oldrevs = revs
5822 revs = [] # actually, nodes
5823 revs = [] # actually, nodes
5823 for r in oldrevs:
5824 for r in oldrevs:
5824 node = other.lookup(r)
5825 node = other.lookup(r)
5825 revs.append(node)
5826 revs.append(node)
5826 if r == checkout:
5827 if r == checkout:
5827 checkout = node
5828 checkout = node
5828 except error.CapabilityError:
5829 except error.CapabilityError:
5829 err = _("other repository doesn't support revision lookup, "
5830 err = _("other repository doesn't support revision lookup, "
5830 "so a rev cannot be specified.")
5831 "so a rev cannot be specified.")
5831 raise error.Abort(err)
5832 raise error.Abort(err)
5832
5833
5833 pullopargs.update(opts.get('opargs', {}))
5834 pullopargs.update(opts.get('opargs', {}))
5834 modheads = exchange.pull(repo, other, heads=revs,
5835 modheads = exchange.pull(repo, other, heads=revs,
5835 force=opts.get('force'),
5836 force=opts.get('force'),
5836 bookmarks=opts.get('bookmark', ()),
5837 bookmarks=opts.get('bookmark', ()),
5837 opargs=pullopargs).cgresult
5838 opargs=pullopargs).cgresult
5838
5839
5839 # brev is a name, which might be a bookmark to be activated at
5840 # brev is a name, which might be a bookmark to be activated at
5840 # the end of the update. In other words, it is an explicit
5841 # the end of the update. In other words, it is an explicit
5841 # destination of the update
5842 # destination of the update
5842 brev = None
5843 brev = None
5843
5844
5844 if checkout:
5845 if checkout:
5845 checkout = str(repo.changelog.rev(checkout))
5846 checkout = str(repo.changelog.rev(checkout))
5846
5847
5847 # order below depends on implementation of
5848 # order below depends on implementation of
5848 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5849 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5849 # because 'checkout' is determined without it.
5850 # because 'checkout' is determined without it.
5850 if opts.get('rev'):
5851 if opts.get('rev'):
5851 brev = opts['rev'][0]
5852 brev = opts['rev'][0]
5852 elif opts.get('branch'):
5853 elif opts.get('branch'):
5853 brev = opts['branch'][0]
5854 brev = opts['branch'][0]
5854 else:
5855 else:
5855 brev = branches[0]
5856 brev = branches[0]
5856 repo._subtoppath = source
5857 repo._subtoppath = source
5857 try:
5858 try:
5858 ret = postincoming(ui, repo, modheads, opts.get('update'),
5859 ret = postincoming(ui, repo, modheads, opts.get('update'),
5859 checkout, brev)
5860 checkout, brev)
5860
5861
5861 finally:
5862 finally:
5862 del repo._subtoppath
5863 del repo._subtoppath
5863
5864
5864 finally:
5865 finally:
5865 other.close()
5866 other.close()
5866 return ret
5867 return ret
5867
5868
5868 @command('^push',
5869 @command('^push',
5869 [('f', 'force', None, _('force push')),
5870 [('f', 'force', None, _('force push')),
5870 ('r', 'rev', [],
5871 ('r', 'rev', [],
5871 _('a changeset intended to be included in the destination'),
5872 _('a changeset intended to be included in the destination'),
5872 _('REV')),
5873 _('REV')),
5873 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
5874 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
5874 ('b', 'branch', [],
5875 ('b', 'branch', [],
5875 _('a specific branch you would like to push'), _('BRANCH')),
5876 _('a specific branch you would like to push'), _('BRANCH')),
5876 ('', 'new-branch', False, _('allow pushing a new branch')),
5877 ('', 'new-branch', False, _('allow pushing a new branch')),
5877 ] + remoteopts,
5878 ] + remoteopts,
5878 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
5879 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
5879 def push(ui, repo, dest=None, **opts):
5880 def push(ui, repo, dest=None, **opts):
5880 """push changes to the specified destination
5881 """push changes to the specified destination
5881
5882
5882 Push changesets from the local repository to the specified
5883 Push changesets from the local repository to the specified
5883 destination.
5884 destination.
5884
5885
5885 This operation is symmetrical to pull: it is identical to a pull
5886 This operation is symmetrical to pull: it is identical to a pull
5886 in the destination repository from the current one.
5887 in the destination repository from the current one.
5887
5888
5888 By default, push will not allow creation of new heads at the
5889 By default, push will not allow creation of new heads at the
5889 destination, since multiple heads would make it unclear which head
5890 destination, since multiple heads would make it unclear which head
5890 to use. In this situation, it is recommended to pull and merge
5891 to use. In this situation, it is recommended to pull and merge
5891 before pushing.
5892 before pushing.
5892
5893
5893 Use --new-branch if you want to allow push to create a new named
5894 Use --new-branch if you want to allow push to create a new named
5894 branch that is not present at the destination. This allows you to
5895 branch that is not present at the destination. This allows you to
5895 only create a new branch without forcing other changes.
5896 only create a new branch without forcing other changes.
5896
5897
5897 .. note::
5898 .. note::
5898
5899
5899 Extra care should be taken with the -f/--force option,
5900 Extra care should be taken with the -f/--force option,
5900 which will push all new heads on all branches, an action which will
5901 which will push all new heads on all branches, an action which will
5901 almost always cause confusion for collaborators.
5902 almost always cause confusion for collaborators.
5902
5903
5903 If -r/--rev is used, the specified revision and all its ancestors
5904 If -r/--rev is used, the specified revision and all its ancestors
5904 will be pushed to the remote repository.
5905 will be pushed to the remote repository.
5905
5906
5906 If -B/--bookmark is used, the specified bookmarked revision, its
5907 If -B/--bookmark is used, the specified bookmarked revision, its
5907 ancestors, and the bookmark will be pushed to the remote
5908 ancestors, and the bookmark will be pushed to the remote
5908 repository. Specifying ``.`` is equivalent to specifying the active
5909 repository. Specifying ``.`` is equivalent to specifying the active
5909 bookmark's name.
5910 bookmark's name.
5910
5911
5911 Please see :hg:`help urls` for important details about ``ssh://``
5912 Please see :hg:`help urls` for important details about ``ssh://``
5912 URLs. If DESTINATION is omitted, a default path will be used.
5913 URLs. If DESTINATION is omitted, a default path will be used.
5913
5914
5914 Returns 0 if push was successful, 1 if nothing to push.
5915 Returns 0 if push was successful, 1 if nothing to push.
5915 """
5916 """
5916
5917
5917 if opts.get('bookmark'):
5918 if opts.get('bookmark'):
5918 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
5919 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
5919 for b in opts['bookmark']:
5920 for b in opts['bookmark']:
5920 # translate -B options to -r so changesets get pushed
5921 # translate -B options to -r so changesets get pushed
5921 b = repo._bookmarks.expandname(b)
5922 b = repo._bookmarks.expandname(b)
5922 if b in repo._bookmarks:
5923 if b in repo._bookmarks:
5923 opts.setdefault('rev', []).append(b)
5924 opts.setdefault('rev', []).append(b)
5924 else:
5925 else:
5925 # if we try to push a deleted bookmark, translate it to null
5926 # if we try to push a deleted bookmark, translate it to null
5926 # this lets simultaneous -r, -b options continue working
5927 # this lets simultaneous -r, -b options continue working
5927 opts.setdefault('rev', []).append("null")
5928 opts.setdefault('rev', []).append("null")
5928
5929
5929 path = ui.paths.getpath(dest, default=('default-push', 'default'))
5930 path = ui.paths.getpath(dest, default=('default-push', 'default'))
5930 if not path:
5931 if not path:
5931 raise error.Abort(_('default repository not configured!'),
5932 raise error.Abort(_('default repository not configured!'),
5932 hint=_('see the "path" section in "hg help config"'))
5933 hint=_('see the "path" section in "hg help config"'))
5933 dest = path.pushloc or path.loc
5934 dest = path.pushloc or path.loc
5934 branches = (path.branch, opts.get('branch') or [])
5935 branches = (path.branch, opts.get('branch') or [])
5935 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
5936 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
5936 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
5937 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
5937 other = hg.peer(repo, opts, dest)
5938 other = hg.peer(repo, opts, dest)
5938
5939
5939 if revs:
5940 if revs:
5940 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
5941 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
5941 if not revs:
5942 if not revs:
5942 raise error.Abort(_("specified revisions evaluate to an empty set"),
5943 raise error.Abort(_("specified revisions evaluate to an empty set"),
5943 hint=_("use different revision arguments"))
5944 hint=_("use different revision arguments"))
5944
5945
5945 repo._subtoppath = dest
5946 repo._subtoppath = dest
5946 try:
5947 try:
5947 # push subrepos depth-first for coherent ordering
5948 # push subrepos depth-first for coherent ordering
5948 c = repo['']
5949 c = repo['']
5949 subs = c.substate # only repos that are committed
5950 subs = c.substate # only repos that are committed
5950 for s in sorted(subs):
5951 for s in sorted(subs):
5951 result = c.sub(s).push(opts)
5952 result = c.sub(s).push(opts)
5952 if result == 0:
5953 if result == 0:
5953 return not result
5954 return not result
5954 finally:
5955 finally:
5955 del repo._subtoppath
5956 del repo._subtoppath
5956 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
5957 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
5957 newbranch=opts.get('new_branch'),
5958 newbranch=opts.get('new_branch'),
5958 bookmarks=opts.get('bookmark', ()),
5959 bookmarks=opts.get('bookmark', ()),
5959 opargs=opts.get('opargs'))
5960 opargs=opts.get('opargs'))
5960
5961
5961 result = not pushop.cgresult
5962 result = not pushop.cgresult
5962
5963
5963 if pushop.bkresult is not None:
5964 if pushop.bkresult is not None:
5964 if pushop.bkresult == 2:
5965 if pushop.bkresult == 2:
5965 result = 2
5966 result = 2
5966 elif not result and pushop.bkresult:
5967 elif not result and pushop.bkresult:
5967 result = 2
5968 result = 2
5968
5969
5969 return result
5970 return result
5970
5971
5971 @command('recover', [])
5972 @command('recover', [])
5972 def recover(ui, repo):
5973 def recover(ui, repo):
5973 """roll back an interrupted transaction
5974 """roll back an interrupted transaction
5974
5975
5975 Recover from an interrupted commit or pull.
5976 Recover from an interrupted commit or pull.
5976
5977
5977 This command tries to fix the repository status after an
5978 This command tries to fix the repository status after an
5978 interrupted operation. It should only be necessary when Mercurial
5979 interrupted operation. It should only be necessary when Mercurial
5979 suggests it.
5980 suggests it.
5980
5981
5981 Returns 0 if successful, 1 if nothing to recover or verify fails.
5982 Returns 0 if successful, 1 if nothing to recover or verify fails.
5982 """
5983 """
5983 if repo.recover():
5984 if repo.recover():
5984 return hg.verify(repo)
5985 return hg.verify(repo)
5985 return 1
5986 return 1
5986
5987
5987 @command('^remove|rm',
5988 @command('^remove|rm',
5988 [('A', 'after', None, _('record delete for missing files')),
5989 [('A', 'after', None, _('record delete for missing files')),
5989 ('f', 'force', None,
5990 ('f', 'force', None,
5990 _('forget added files, delete modified files')),
5991 _('forget added files, delete modified files')),
5991 ] + subrepoopts + walkopts,
5992 ] + subrepoopts + walkopts,
5992 _('[OPTION]... FILE...'),
5993 _('[OPTION]... FILE...'),
5993 inferrepo=True)
5994 inferrepo=True)
5994 def remove(ui, repo, *pats, **opts):
5995 def remove(ui, repo, *pats, **opts):
5995 """remove the specified files on the next commit
5996 """remove the specified files on the next commit
5996
5997
5997 Schedule the indicated files for removal from the current branch.
5998 Schedule the indicated files for removal from the current branch.
5998
5999
5999 This command schedules the files to be removed at the next commit.
6000 This command schedules the files to be removed at the next commit.
6000 To undo a remove before that, see :hg:`revert`. To undo added
6001 To undo a remove before that, see :hg:`revert`. To undo added
6001 files, see :hg:`forget`.
6002 files, see :hg:`forget`.
6002
6003
6003 .. container:: verbose
6004 .. container:: verbose
6004
6005
6005 -A/--after can be used to remove only files that have already
6006 -A/--after can be used to remove only files that have already
6006 been deleted, -f/--force can be used to force deletion, and -Af
6007 been deleted, -f/--force can be used to force deletion, and -Af
6007 can be used to remove files from the next revision without
6008 can be used to remove files from the next revision without
6008 deleting them from the working directory.
6009 deleting them from the working directory.
6009
6010
6010 The following table details the behavior of remove for different
6011 The following table details the behavior of remove for different
6011 file states (columns) and option combinations (rows). The file
6012 file states (columns) and option combinations (rows). The file
6012 states are Added [A], Clean [C], Modified [M] and Missing [!]
6013 states are Added [A], Clean [C], Modified [M] and Missing [!]
6013 (as reported by :hg:`status`). The actions are Warn, Remove
6014 (as reported by :hg:`status`). The actions are Warn, Remove
6014 (from branch) and Delete (from disk):
6015 (from branch) and Delete (from disk):
6015
6016
6016 ========= == == == ==
6017 ========= == == == ==
6017 opt/state A C M !
6018 opt/state A C M !
6018 ========= == == == ==
6019 ========= == == == ==
6019 none W RD W R
6020 none W RD W R
6020 -f R RD RD R
6021 -f R RD RD R
6021 -A W W W R
6022 -A W W W R
6022 -Af R R R R
6023 -Af R R R R
6023 ========= == == == ==
6024 ========= == == == ==
6024
6025
6025 .. note::
6026 .. note::
6026
6027
6027 :hg:`remove` never deletes files in Added [A] state from the
6028 :hg:`remove` never deletes files in Added [A] state from the
6028 working directory, not even if ``--force`` is specified.
6029 working directory, not even if ``--force`` is specified.
6029
6030
6030 Returns 0 on success, 1 if any warnings encountered.
6031 Returns 0 on success, 1 if any warnings encountered.
6031 """
6032 """
6032
6033
6033 after, force = opts.get('after'), opts.get('force')
6034 after, force = opts.get('after'), opts.get('force')
6034 if not pats and not after:
6035 if not pats and not after:
6035 raise error.Abort(_('no files specified'))
6036 raise error.Abort(_('no files specified'))
6036
6037
6037 m = scmutil.match(repo[None], pats, opts)
6038 m = scmutil.match(repo[None], pats, opts)
6038 subrepos = opts.get('subrepos')
6039 subrepos = opts.get('subrepos')
6039 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
6040 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
6040
6041
6041 @command('rename|move|mv',
6042 @command('rename|move|mv',
6042 [('A', 'after', None, _('record a rename that has already occurred')),
6043 [('A', 'after', None, _('record a rename that has already occurred')),
6043 ('f', 'force', None, _('forcibly copy over an existing managed file')),
6044 ('f', 'force', None, _('forcibly copy over an existing managed file')),
6044 ] + walkopts + dryrunopts,
6045 ] + walkopts + dryrunopts,
6045 _('[OPTION]... SOURCE... DEST'))
6046 _('[OPTION]... SOURCE... DEST'))
6046 def rename(ui, repo, *pats, **opts):
6047 def rename(ui, repo, *pats, **opts):
6047 """rename files; equivalent of copy + remove
6048 """rename files; equivalent of copy + remove
6048
6049
6049 Mark dest as copies of sources; mark sources for deletion. If dest
6050 Mark dest as copies of sources; mark sources for deletion. If dest
6050 is a directory, copies are put in that directory. If dest is a
6051 is a directory, copies are put in that directory. If dest is a
6051 file, there can only be one source.
6052 file, there can only be one source.
6052
6053
6053 By default, this command copies the contents of files as they
6054 By default, this command copies the contents of files as they
6054 exist in the working directory. If invoked with -A/--after, the
6055 exist in the working directory. If invoked with -A/--after, the
6055 operation is recorded, but no copying is performed.
6056 operation is recorded, but no copying is performed.
6056
6057
6057 This command takes effect at the next commit. To undo a rename
6058 This command takes effect at the next commit. To undo a rename
6058 before that, see :hg:`revert`.
6059 before that, see :hg:`revert`.
6059
6060
6060 Returns 0 on success, 1 if errors are encountered.
6061 Returns 0 on success, 1 if errors are encountered.
6061 """
6062 """
6062 with repo.wlock(False):
6063 with repo.wlock(False):
6063 return cmdutil.copy(ui, repo, pats, opts, rename=True)
6064 return cmdutil.copy(ui, repo, pats, opts, rename=True)
6064
6065
6065 @command('resolve',
6066 @command('resolve',
6066 [('a', 'all', None, _('select all unresolved files')),
6067 [('a', 'all', None, _('select all unresolved files')),
6067 ('l', 'list', None, _('list state of files needing merge')),
6068 ('l', 'list', None, _('list state of files needing merge')),
6068 ('m', 'mark', None, _('mark files as resolved')),
6069 ('m', 'mark', None, _('mark files as resolved')),
6069 ('u', 'unmark', None, _('mark files as unresolved')),
6070 ('u', 'unmark', None, _('mark files as unresolved')),
6070 ('n', 'no-status', None, _('hide status prefix'))]
6071 ('n', 'no-status', None, _('hide status prefix'))]
6071 + mergetoolopts + walkopts + formatteropts,
6072 + mergetoolopts + walkopts + formatteropts,
6072 _('[OPTION]... [FILE]...'),
6073 _('[OPTION]... [FILE]...'),
6073 inferrepo=True)
6074 inferrepo=True)
6074 def resolve(ui, repo, *pats, **opts):
6075 def resolve(ui, repo, *pats, **opts):
6075 """redo merges or set/view the merge status of files
6076 """redo merges or set/view the merge status of files
6076
6077
6077 Merges with unresolved conflicts are often the result of
6078 Merges with unresolved conflicts are often the result of
6078 non-interactive merging using the ``internal:merge`` configuration
6079 non-interactive merging using the ``internal:merge`` configuration
6079 setting, or a command-line merge tool like ``diff3``. The resolve
6080 setting, or a command-line merge tool like ``diff3``. The resolve
6080 command is used to manage the files involved in a merge, after
6081 command is used to manage the files involved in a merge, after
6081 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
6082 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
6082 working directory must have two parents). See :hg:`help
6083 working directory must have two parents). See :hg:`help
6083 merge-tools` for information on configuring merge tools.
6084 merge-tools` for information on configuring merge tools.
6084
6085
6085 The resolve command can be used in the following ways:
6086 The resolve command can be used in the following ways:
6086
6087
6087 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
6088 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
6088 files, discarding any previous merge attempts. Re-merging is not
6089 files, discarding any previous merge attempts. Re-merging is not
6089 performed for files already marked as resolved. Use ``--all/-a``
6090 performed for files already marked as resolved. Use ``--all/-a``
6090 to select all unresolved files. ``--tool`` can be used to specify
6091 to select all unresolved files. ``--tool`` can be used to specify
6091 the merge tool used for the given files. It overrides the HGMERGE
6092 the merge tool used for the given files. It overrides the HGMERGE
6092 environment variable and your configuration files. Previous file
6093 environment variable and your configuration files. Previous file
6093 contents are saved with a ``.orig`` suffix.
6094 contents are saved with a ``.orig`` suffix.
6094
6095
6095 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
6096 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
6096 (e.g. after having manually fixed-up the files). The default is
6097 (e.g. after having manually fixed-up the files). The default is
6097 to mark all unresolved files.
6098 to mark all unresolved files.
6098
6099
6099 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
6100 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
6100 default is to mark all resolved files.
6101 default is to mark all resolved files.
6101
6102
6102 - :hg:`resolve -l`: list files which had or still have conflicts.
6103 - :hg:`resolve -l`: list files which had or still have conflicts.
6103 In the printed list, ``U`` = unresolved and ``R`` = resolved.
6104 In the printed list, ``U`` = unresolved and ``R`` = resolved.
6104
6105
6105 .. note::
6106 .. note::
6106
6107
6107 Mercurial will not let you commit files with unresolved merge
6108 Mercurial will not let you commit files with unresolved merge
6108 conflicts. You must use :hg:`resolve -m ...` before you can
6109 conflicts. You must use :hg:`resolve -m ...` before you can
6109 commit after a conflicting merge.
6110 commit after a conflicting merge.
6110
6111
6111 Returns 0 on success, 1 if any files fail a resolve attempt.
6112 Returns 0 on success, 1 if any files fail a resolve attempt.
6112 """
6113 """
6113
6114
6114 flaglist = 'all mark unmark list no_status'.split()
6115 flaglist = 'all mark unmark list no_status'.split()
6115 all, mark, unmark, show, nostatus = \
6116 all, mark, unmark, show, nostatus = \
6116 [opts.get(o) for o in flaglist]
6117 [opts.get(o) for o in flaglist]
6117
6118
6118 if (show and (mark or unmark)) or (mark and unmark):
6119 if (show and (mark or unmark)) or (mark and unmark):
6119 raise error.Abort(_("too many options specified"))
6120 raise error.Abort(_("too many options specified"))
6120 if pats and all:
6121 if pats and all:
6121 raise error.Abort(_("can't specify --all and patterns"))
6122 raise error.Abort(_("can't specify --all and patterns"))
6122 if not (all or pats or show or mark or unmark):
6123 if not (all or pats or show or mark or unmark):
6123 raise error.Abort(_('no files or directories specified'),
6124 raise error.Abort(_('no files or directories specified'),
6124 hint=('use --all to re-merge all unresolved files'))
6125 hint=('use --all to re-merge all unresolved files'))
6125
6126
6126 if show:
6127 if show:
6127 fm = ui.formatter('resolve', opts)
6128 fm = ui.formatter('resolve', opts)
6128 ms = mergemod.mergestate.read(repo)
6129 ms = mergemod.mergestate.read(repo)
6129 m = scmutil.match(repo[None], pats, opts)
6130 m = scmutil.match(repo[None], pats, opts)
6130 for f in ms:
6131 for f in ms:
6131 if not m(f):
6132 if not m(f):
6132 continue
6133 continue
6133 l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
6134 l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
6134 'd': 'driverresolved'}[ms[f]]
6135 'd': 'driverresolved'}[ms[f]]
6135 fm.startitem()
6136 fm.startitem()
6136 fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
6137 fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
6137 fm.write('path', '%s\n', f, label=l)
6138 fm.write('path', '%s\n', f, label=l)
6138 fm.end()
6139 fm.end()
6139 return 0
6140 return 0
6140
6141
6141 with repo.wlock():
6142 with repo.wlock():
6142 ms = mergemod.mergestate.read(repo)
6143 ms = mergemod.mergestate.read(repo)
6143
6144
6144 if not (ms.active() or repo.dirstate.p2() != nullid):
6145 if not (ms.active() or repo.dirstate.p2() != nullid):
6145 raise error.Abort(
6146 raise error.Abort(
6146 _('resolve command not applicable when not merging'))
6147 _('resolve command not applicable when not merging'))
6147
6148
6148 wctx = repo[None]
6149 wctx = repo[None]
6149
6150
6150 if ms.mergedriver and ms.mdstate() == 'u':
6151 if ms.mergedriver and ms.mdstate() == 'u':
6151 proceed = mergemod.driverpreprocess(repo, ms, wctx)
6152 proceed = mergemod.driverpreprocess(repo, ms, wctx)
6152 ms.commit()
6153 ms.commit()
6153 # allow mark and unmark to go through
6154 # allow mark and unmark to go through
6154 if not mark and not unmark and not proceed:
6155 if not mark and not unmark and not proceed:
6155 return 1
6156 return 1
6156
6157
6157 m = scmutil.match(wctx, pats, opts)
6158 m = scmutil.match(wctx, pats, opts)
6158 ret = 0
6159 ret = 0
6159 didwork = False
6160 didwork = False
6160 runconclude = False
6161 runconclude = False
6161
6162
6162 tocomplete = []
6163 tocomplete = []
6163 for f in ms:
6164 for f in ms:
6164 if not m(f):
6165 if not m(f):
6165 continue
6166 continue
6166
6167
6167 didwork = True
6168 didwork = True
6168
6169
6169 # don't let driver-resolved files be marked, and run the conclude
6170 # don't let driver-resolved files be marked, and run the conclude
6170 # step if asked to resolve
6171 # step if asked to resolve
6171 if ms[f] == "d":
6172 if ms[f] == "d":
6172 exact = m.exact(f)
6173 exact = m.exact(f)
6173 if mark:
6174 if mark:
6174 if exact:
6175 if exact:
6175 ui.warn(_('not marking %s as it is driver-resolved\n')
6176 ui.warn(_('not marking %s as it is driver-resolved\n')
6176 % f)
6177 % f)
6177 elif unmark:
6178 elif unmark:
6178 if exact:
6179 if exact:
6179 ui.warn(_('not unmarking %s as it is driver-resolved\n')
6180 ui.warn(_('not unmarking %s as it is driver-resolved\n')
6180 % f)
6181 % f)
6181 else:
6182 else:
6182 runconclude = True
6183 runconclude = True
6183 continue
6184 continue
6184
6185
6185 if mark:
6186 if mark:
6186 ms.mark(f, "r")
6187 ms.mark(f, "r")
6187 elif unmark:
6188 elif unmark:
6188 ms.mark(f, "u")
6189 ms.mark(f, "u")
6189 else:
6190 else:
6190 # backup pre-resolve (merge uses .orig for its own purposes)
6191 # backup pre-resolve (merge uses .orig for its own purposes)
6191 a = repo.wjoin(f)
6192 a = repo.wjoin(f)
6192 try:
6193 try:
6193 util.copyfile(a, a + ".resolve")
6194 util.copyfile(a, a + ".resolve")
6194 except (IOError, OSError) as inst:
6195 except (IOError, OSError) as inst:
6195 if inst.errno != errno.ENOENT:
6196 if inst.errno != errno.ENOENT:
6196 raise
6197 raise
6197
6198
6198 try:
6199 try:
6199 # preresolve file
6200 # preresolve file
6200 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
6201 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
6201 'resolve')
6202 'resolve')
6202 complete, r = ms.preresolve(f, wctx)
6203 complete, r = ms.preresolve(f, wctx)
6203 if not complete:
6204 if not complete:
6204 tocomplete.append(f)
6205 tocomplete.append(f)
6205 elif r:
6206 elif r:
6206 ret = 1
6207 ret = 1
6207 finally:
6208 finally:
6208 ui.setconfig('ui', 'forcemerge', '', 'resolve')
6209 ui.setconfig('ui', 'forcemerge', '', 'resolve')
6209 ms.commit()
6210 ms.commit()
6210
6211
6211 # replace filemerge's .orig file with our resolve file, but only
6212 # replace filemerge's .orig file with our resolve file, but only
6212 # for merges that are complete
6213 # for merges that are complete
6213 if complete:
6214 if complete:
6214 try:
6215 try:
6215 util.rename(a + ".resolve",
6216 util.rename(a + ".resolve",
6216 scmutil.origpath(ui, repo, a))
6217 scmutil.origpath(ui, repo, a))
6217 except OSError as inst:
6218 except OSError as inst:
6218 if inst.errno != errno.ENOENT:
6219 if inst.errno != errno.ENOENT:
6219 raise
6220 raise
6220
6221
6221 for f in tocomplete:
6222 for f in tocomplete:
6222 try:
6223 try:
6223 # resolve file
6224 # resolve file
6224 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
6225 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
6225 'resolve')
6226 'resolve')
6226 r = ms.resolve(f, wctx)
6227 r = ms.resolve(f, wctx)
6227 if r:
6228 if r:
6228 ret = 1
6229 ret = 1
6229 finally:
6230 finally:
6230 ui.setconfig('ui', 'forcemerge', '', 'resolve')
6231 ui.setconfig('ui', 'forcemerge', '', 'resolve')
6231 ms.commit()
6232 ms.commit()
6232
6233
6233 # replace filemerge's .orig file with our resolve file
6234 # replace filemerge's .orig file with our resolve file
6234 a = repo.wjoin(f)
6235 a = repo.wjoin(f)
6235 try:
6236 try:
6236 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
6237 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
6237 except OSError as inst:
6238 except OSError as inst:
6238 if inst.errno != errno.ENOENT:
6239 if inst.errno != errno.ENOENT:
6239 raise
6240 raise
6240
6241
6241 ms.commit()
6242 ms.commit()
6242 ms.recordactions()
6243 ms.recordactions()
6243
6244
6244 if not didwork and pats:
6245 if not didwork and pats:
6245 hint = None
6246 hint = None
6246 if not any([p for p in pats if p.find(':') >= 0]):
6247 if not any([p for p in pats if p.find(':') >= 0]):
6247 pats = ['path:%s' % p for p in pats]
6248 pats = ['path:%s' % p for p in pats]
6248 m = scmutil.match(wctx, pats, opts)
6249 m = scmutil.match(wctx, pats, opts)
6249 for f in ms:
6250 for f in ms:
6250 if not m(f):
6251 if not m(f):
6251 continue
6252 continue
6252 flags = ''.join(['-%s ' % o[0] for o in flaglist
6253 flags = ''.join(['-%s ' % o[0] for o in flaglist
6253 if opts.get(o)])
6254 if opts.get(o)])
6254 hint = _("(try: hg resolve %s%s)\n") % (
6255 hint = _("(try: hg resolve %s%s)\n") % (
6255 flags,
6256 flags,
6256 ' '.join(pats))
6257 ' '.join(pats))
6257 break
6258 break
6258 ui.warn(_("arguments do not match paths that need resolving\n"))
6259 ui.warn(_("arguments do not match paths that need resolving\n"))
6259 if hint:
6260 if hint:
6260 ui.warn(hint)
6261 ui.warn(hint)
6261 elif ms.mergedriver and ms.mdstate() != 's':
6262 elif ms.mergedriver and ms.mdstate() != 's':
6262 # run conclude step when either a driver-resolved file is requested
6263 # run conclude step when either a driver-resolved file is requested
6263 # or there are no driver-resolved files
6264 # or there are no driver-resolved files
6264 # we can't use 'ret' to determine whether any files are unresolved
6265 # we can't use 'ret' to determine whether any files are unresolved
6265 # because we might not have tried to resolve some
6266 # because we might not have tried to resolve some
6266 if ((runconclude or not list(ms.driverresolved()))
6267 if ((runconclude or not list(ms.driverresolved()))
6267 and not list(ms.unresolved())):
6268 and not list(ms.unresolved())):
6268 proceed = mergemod.driverconclude(repo, ms, wctx)
6269 proceed = mergemod.driverconclude(repo, ms, wctx)
6269 ms.commit()
6270 ms.commit()
6270 if not proceed:
6271 if not proceed:
6271 return 1
6272 return 1
6272
6273
6273 # Nudge users into finishing an unfinished operation
6274 # Nudge users into finishing an unfinished operation
6274 unresolvedf = list(ms.unresolved())
6275 unresolvedf = list(ms.unresolved())
6275 driverresolvedf = list(ms.driverresolved())
6276 driverresolvedf = list(ms.driverresolved())
6276 if not unresolvedf and not driverresolvedf:
6277 if not unresolvedf and not driverresolvedf:
6277 ui.status(_('(no more unresolved files)\n'))
6278 ui.status(_('(no more unresolved files)\n'))
6278 cmdutil.checkafterresolved(repo)
6279 cmdutil.checkafterresolved(repo)
6279 elif not unresolvedf:
6280 elif not unresolvedf:
6280 ui.status(_('(no more unresolved files -- '
6281 ui.status(_('(no more unresolved files -- '
6281 'run "hg resolve --all" to conclude)\n'))
6282 'run "hg resolve --all" to conclude)\n'))
6282
6283
6283 return ret
6284 return ret
6284
6285
6285 @command('revert',
6286 @command('revert',
6286 [('a', 'all', None, _('revert all changes when no arguments given')),
6287 [('a', 'all', None, _('revert all changes when no arguments given')),
6287 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6288 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6288 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
6289 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
6289 ('C', 'no-backup', None, _('do not save backup copies of files')),
6290 ('C', 'no-backup', None, _('do not save backup copies of files')),
6290 ('i', 'interactive', None,
6291 ('i', 'interactive', None,
6291 _('interactively select the changes (EXPERIMENTAL)')),
6292 _('interactively select the changes (EXPERIMENTAL)')),
6292 ] + walkopts + dryrunopts,
6293 ] + walkopts + dryrunopts,
6293 _('[OPTION]... [-r REV] [NAME]...'))
6294 _('[OPTION]... [-r REV] [NAME]...'))
6294 def revert(ui, repo, *pats, **opts):
6295 def revert(ui, repo, *pats, **opts):
6295 """restore files to their checkout state
6296 """restore files to their checkout state
6296
6297
6297 .. note::
6298 .. note::
6298
6299
6299 To check out earlier revisions, you should use :hg:`update REV`.
6300 To check out earlier revisions, you should use :hg:`update REV`.
6300 To cancel an uncommitted merge (and lose your changes),
6301 To cancel an uncommitted merge (and lose your changes),
6301 use :hg:`update --clean .`.
6302 use :hg:`update --clean .`.
6302
6303
6303 With no revision specified, revert the specified files or directories
6304 With no revision specified, revert the specified files or directories
6304 to the contents they had in the parent of the working directory.
6305 to the contents they had in the parent of the working directory.
6305 This restores the contents of files to an unmodified
6306 This restores the contents of files to an unmodified
6306 state and unschedules adds, removes, copies, and renames. If the
6307 state and unschedules adds, removes, copies, and renames. If the
6307 working directory has two parents, you must explicitly specify a
6308 working directory has two parents, you must explicitly specify a
6308 revision.
6309 revision.
6309
6310
6310 Using the -r/--rev or -d/--date options, revert the given files or
6311 Using the -r/--rev or -d/--date options, revert the given files or
6311 directories to their states as of a specific revision. Because
6312 directories to their states as of a specific revision. Because
6312 revert does not change the working directory parents, this will
6313 revert does not change the working directory parents, this will
6313 cause these files to appear modified. This can be helpful to "back
6314 cause these files to appear modified. This can be helpful to "back
6314 out" some or all of an earlier change. See :hg:`backout` for a
6315 out" some or all of an earlier change. See :hg:`backout` for a
6315 related method.
6316 related method.
6316
6317
6317 Modified files are saved with a .orig suffix before reverting.
6318 Modified files are saved with a .orig suffix before reverting.
6318 To disable these backups, use --no-backup. It is possible to store
6319 To disable these backups, use --no-backup. It is possible to store
6319 the backup files in a custom directory relative to the root of the
6320 the backup files in a custom directory relative to the root of the
6320 repository by setting the ``ui.origbackuppath`` configuration
6321 repository by setting the ``ui.origbackuppath`` configuration
6321 option.
6322 option.
6322
6323
6323 See :hg:`help dates` for a list of formats valid for -d/--date.
6324 See :hg:`help dates` for a list of formats valid for -d/--date.
6324
6325
6325 See :hg:`help backout` for a way to reverse the effect of an
6326 See :hg:`help backout` for a way to reverse the effect of an
6326 earlier changeset.
6327 earlier changeset.
6327
6328
6328 Returns 0 on success.
6329 Returns 0 on success.
6329 """
6330 """
6330
6331
6331 if opts.get("date"):
6332 if opts.get("date"):
6332 if opts.get("rev"):
6333 if opts.get("rev"):
6333 raise error.Abort(_("you can't specify a revision and a date"))
6334 raise error.Abort(_("you can't specify a revision and a date"))
6334 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
6335 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
6335
6336
6336 parent, p2 = repo.dirstate.parents()
6337 parent, p2 = repo.dirstate.parents()
6337 if not opts.get('rev') and p2 != nullid:
6338 if not opts.get('rev') and p2 != nullid:
6338 # revert after merge is a trap for new users (issue2915)
6339 # revert after merge is a trap for new users (issue2915)
6339 raise error.Abort(_('uncommitted merge with no revision specified'),
6340 raise error.Abort(_('uncommitted merge with no revision specified'),
6340 hint=_("use 'hg update' or see 'hg help revert'"))
6341 hint=_("use 'hg update' or see 'hg help revert'"))
6341
6342
6342 ctx = scmutil.revsingle(repo, opts.get('rev'))
6343 ctx = scmutil.revsingle(repo, opts.get('rev'))
6343
6344
6344 if (not (pats or opts.get('include') or opts.get('exclude') or
6345 if (not (pats or opts.get('include') or opts.get('exclude') or
6345 opts.get('all') or opts.get('interactive'))):
6346 opts.get('all') or opts.get('interactive'))):
6346 msg = _("no files or directories specified")
6347 msg = _("no files or directories specified")
6347 if p2 != nullid:
6348 if p2 != nullid:
6348 hint = _("uncommitted merge, use --all to discard all changes,"
6349 hint = _("uncommitted merge, use --all to discard all changes,"
6349 " or 'hg update -C .' to abort the merge")
6350 " or 'hg update -C .' to abort the merge")
6350 raise error.Abort(msg, hint=hint)
6351 raise error.Abort(msg, hint=hint)
6351 dirty = any(repo.status())
6352 dirty = any(repo.status())
6352 node = ctx.node()
6353 node = ctx.node()
6353 if node != parent:
6354 if node != parent:
6354 if dirty:
6355 if dirty:
6355 hint = _("uncommitted changes, use --all to discard all"
6356 hint = _("uncommitted changes, use --all to discard all"
6356 " changes, or 'hg update %s' to update") % ctx.rev()
6357 " changes, or 'hg update %s' to update") % ctx.rev()
6357 else:
6358 else:
6358 hint = _("use --all to revert all files,"
6359 hint = _("use --all to revert all files,"
6359 " or 'hg update %s' to update") % ctx.rev()
6360 " or 'hg update %s' to update") % ctx.rev()
6360 elif dirty:
6361 elif dirty:
6361 hint = _("uncommitted changes, use --all to discard all changes")
6362 hint = _("uncommitted changes, use --all to discard all changes")
6362 else:
6363 else:
6363 hint = _("use --all to revert all files")
6364 hint = _("use --all to revert all files")
6364 raise error.Abort(msg, hint=hint)
6365 raise error.Abort(msg, hint=hint)
6365
6366
6366 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
6367 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
6367
6368
6368 @command('rollback', dryrunopts +
6369 @command('rollback', dryrunopts +
6369 [('f', 'force', False, _('ignore safety measures'))])
6370 [('f', 'force', False, _('ignore safety measures'))])
6370 def rollback(ui, repo, **opts):
6371 def rollback(ui, repo, **opts):
6371 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6372 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6372
6373
6373 Please use :hg:`commit --amend` instead of rollback to correct
6374 Please use :hg:`commit --amend` instead of rollback to correct
6374 mistakes in the last commit.
6375 mistakes in the last commit.
6375
6376
6376 This command should be used with care. There is only one level of
6377 This command should be used with care. There is only one level of
6377 rollback, and there is no way to undo a rollback. It will also
6378 rollback, and there is no way to undo a rollback. It will also
6378 restore the dirstate at the time of the last transaction, losing
6379 restore the dirstate at the time of the last transaction, losing
6379 any dirstate changes since that time. This command does not alter
6380 any dirstate changes since that time. This command does not alter
6380 the working directory.
6381 the working directory.
6381
6382
6382 Transactions are used to encapsulate the effects of all commands
6383 Transactions are used to encapsulate the effects of all commands
6383 that create new changesets or propagate existing changesets into a
6384 that create new changesets or propagate existing changesets into a
6384 repository.
6385 repository.
6385
6386
6386 .. container:: verbose
6387 .. container:: verbose
6387
6388
6388 For example, the following commands are transactional, and their
6389 For example, the following commands are transactional, and their
6389 effects can be rolled back:
6390 effects can be rolled back:
6390
6391
6391 - commit
6392 - commit
6392 - import
6393 - import
6393 - pull
6394 - pull
6394 - push (with this repository as the destination)
6395 - push (with this repository as the destination)
6395 - unbundle
6396 - unbundle
6396
6397
6397 To avoid permanent data loss, rollback will refuse to rollback a
6398 To avoid permanent data loss, rollback will refuse to rollback a
6398 commit transaction if it isn't checked out. Use --force to
6399 commit transaction if it isn't checked out. Use --force to
6399 override this protection.
6400 override this protection.
6400
6401
6401 The rollback command can be entirely disabled by setting the
6402 The rollback command can be entirely disabled by setting the
6402 ``ui.rollback`` configuration setting to false. If you're here
6403 ``ui.rollback`` configuration setting to false. If you're here
6403 because you want to use rollback and it's disabled, you can
6404 because you want to use rollback and it's disabled, you can
6404 re-enable the command by setting ``ui.rollback`` to true.
6405 re-enable the command by setting ``ui.rollback`` to true.
6405
6406
6406 This command is not intended for use on public repositories. Once
6407 This command is not intended for use on public repositories. Once
6407 changes are visible for pull by other users, rolling a transaction
6408 changes are visible for pull by other users, rolling a transaction
6408 back locally is ineffective (someone else may already have pulled
6409 back locally is ineffective (someone else may already have pulled
6409 the changes). Furthermore, a race is possible with readers of the
6410 the changes). Furthermore, a race is possible with readers of the
6410 repository; for example an in-progress pull from the repository
6411 repository; for example an in-progress pull from the repository
6411 may fail if a rollback is performed.
6412 may fail if a rollback is performed.
6412
6413
6413 Returns 0 on success, 1 if no rollback data is available.
6414 Returns 0 on success, 1 if no rollback data is available.
6414 """
6415 """
6415 if not ui.configbool('ui', 'rollback', True):
6416 if not ui.configbool('ui', 'rollback', True):
6416 raise error.Abort(_('rollback is disabled because it is unsafe'),
6417 raise error.Abort(_('rollback is disabled because it is unsafe'),
6417 hint=('see `hg help -v rollback` for information'))
6418 hint=('see `hg help -v rollback` for information'))
6418 return repo.rollback(dryrun=opts.get('dry_run'),
6419 return repo.rollback(dryrun=opts.get('dry_run'),
6419 force=opts.get('force'))
6420 force=opts.get('force'))
6420
6421
6421 @command('root', [])
6422 @command('root', [])
6422 def root(ui, repo):
6423 def root(ui, repo):
6423 """print the root (top) of the current working directory
6424 """print the root (top) of the current working directory
6424
6425
6425 Print the root directory of the current repository.
6426 Print the root directory of the current repository.
6426
6427
6427 Returns 0 on success.
6428 Returns 0 on success.
6428 """
6429 """
6429 ui.write(repo.root + "\n")
6430 ui.write(repo.root + "\n")
6430
6431
6431 @command('^serve',
6432 @command('^serve',
6432 [('A', 'accesslog', '', _('name of access log file to write to'),
6433 [('A', 'accesslog', '', _('name of access log file to write to'),
6433 _('FILE')),
6434 _('FILE')),
6434 ('d', 'daemon', None, _('run server in background')),
6435 ('d', 'daemon', None, _('run server in background')),
6435 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
6436 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
6436 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
6437 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
6437 # use string type, then we can check if something was passed
6438 # use string type, then we can check if something was passed
6438 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
6439 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
6439 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
6440 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
6440 _('ADDR')),
6441 _('ADDR')),
6441 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
6442 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
6442 _('PREFIX')),
6443 _('PREFIX')),
6443 ('n', 'name', '',
6444 ('n', 'name', '',
6444 _('name to show in web pages (default: working directory)'), _('NAME')),
6445 _('name to show in web pages (default: working directory)'), _('NAME')),
6445 ('', 'web-conf', '',
6446 ('', 'web-conf', '',
6446 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
6447 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
6447 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
6448 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
6448 _('FILE')),
6449 _('FILE')),
6449 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
6450 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
6450 ('', 'stdio', None, _('for remote clients')),
6451 ('', 'stdio', None, _('for remote clients')),
6451 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
6452 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
6452 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
6453 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
6453 ('', 'style', '', _('template style to use'), _('STYLE')),
6454 ('', 'style', '', _('template style to use'), _('STYLE')),
6454 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
6455 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
6455 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
6456 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
6456 _('[OPTION]...'),
6457 _('[OPTION]...'),
6457 optionalrepo=True)
6458 optionalrepo=True)
6458 def serve(ui, repo, **opts):
6459 def serve(ui, repo, **opts):
6459 """start stand-alone webserver
6460 """start stand-alone webserver
6460
6461
6461 Start a local HTTP repository browser and pull server. You can use
6462 Start a local HTTP repository browser and pull server. You can use
6462 this for ad-hoc sharing and browsing of repositories. It is
6463 this for ad-hoc sharing and browsing of repositories. It is
6463 recommended to use a real web server to serve a repository for
6464 recommended to use a real web server to serve a repository for
6464 longer periods of time.
6465 longer periods of time.
6465
6466
6466 Please note that the server does not implement access control.
6467 Please note that the server does not implement access control.
6467 This means that, by default, anybody can read from the server and
6468 This means that, by default, anybody can read from the server and
6468 nobody can write to it by default. Set the ``web.allow_push``
6469 nobody can write to it by default. Set the ``web.allow_push``
6469 option to ``*`` to allow everybody to push to the server. You
6470 option to ``*`` to allow everybody to push to the server. You
6470 should use a real web server if you need to authenticate users.
6471 should use a real web server if you need to authenticate users.
6471
6472
6472 By default, the server logs accesses to stdout and errors to
6473 By default, the server logs accesses to stdout and errors to
6473 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6474 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6474 files.
6475 files.
6475
6476
6476 To have the server choose a free port number to listen on, specify
6477 To have the server choose a free port number to listen on, specify
6477 a port number of 0; in this case, the server will print the port
6478 a port number of 0; in this case, the server will print the port
6478 number it uses.
6479 number it uses.
6479
6480
6480 Returns 0 on success.
6481 Returns 0 on success.
6481 """
6482 """
6482
6483
6483 if opts["stdio"] and opts["cmdserver"]:
6484 if opts["stdio"] and opts["cmdserver"]:
6484 raise error.Abort(_("cannot use --stdio with --cmdserver"))
6485 raise error.Abort(_("cannot use --stdio with --cmdserver"))
6485
6486
6486 if opts["stdio"]:
6487 if opts["stdio"]:
6487 if repo is None:
6488 if repo is None:
6488 raise error.RepoError(_("there is no Mercurial repository here"
6489 raise error.RepoError(_("there is no Mercurial repository here"
6489 " (.hg not found)"))
6490 " (.hg not found)"))
6490 s = sshserver.sshserver(ui, repo)
6491 s = sshserver.sshserver(ui, repo)
6491 s.serve_forever()
6492 s.serve_forever()
6492
6493
6493 if opts["cmdserver"]:
6494 if opts["cmdserver"]:
6494 service = commandserver.createservice(ui, repo, opts)
6495 service = commandserver.createservice(ui, repo, opts)
6495 else:
6496 else:
6496 service = hgweb.createservice(ui, repo, opts)
6497 service = hgweb.createservice(ui, repo, opts)
6497 return cmdutil.service(opts, initfn=service.init, runfn=service.run)
6498 return cmdutil.service(opts, initfn=service.init, runfn=service.run)
6498
6499
6499 @command('^status|st',
6500 @command('^status|st',
6500 [('A', 'all', None, _('show status of all files')),
6501 [('A', 'all', None, _('show status of all files')),
6501 ('m', 'modified', None, _('show only modified files')),
6502 ('m', 'modified', None, _('show only modified files')),
6502 ('a', 'added', None, _('show only added files')),
6503 ('a', 'added', None, _('show only added files')),
6503 ('r', 'removed', None, _('show only removed files')),
6504 ('r', 'removed', None, _('show only removed files')),
6504 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
6505 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
6505 ('c', 'clean', None, _('show only files without changes')),
6506 ('c', 'clean', None, _('show only files without changes')),
6506 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
6507 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
6507 ('i', 'ignored', None, _('show only ignored files')),
6508 ('i', 'ignored', None, _('show only ignored files')),
6508 ('n', 'no-status', None, _('hide status prefix')),
6509 ('n', 'no-status', None, _('hide status prefix')),
6509 ('C', 'copies', None, _('show source of copied files')),
6510 ('C', 'copies', None, _('show source of copied files')),
6510 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
6511 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
6511 ('', 'rev', [], _('show difference from revision'), _('REV')),
6512 ('', 'rev', [], _('show difference from revision'), _('REV')),
6512 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
6513 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
6513 ] + walkopts + subrepoopts + formatteropts,
6514 ] + walkopts + subrepoopts + formatteropts,
6514 _('[OPTION]... [FILE]...'),
6515 _('[OPTION]... [FILE]...'),
6515 inferrepo=True)
6516 inferrepo=True)
6516 def status(ui, repo, *pats, **opts):
6517 def status(ui, repo, *pats, **opts):
6517 """show changed files in the working directory
6518 """show changed files in the working directory
6518
6519
6519 Show status of files in the repository. If names are given, only
6520 Show status of files in the repository. If names are given, only
6520 files that match are shown. Files that are clean or ignored or
6521 files that match are shown. Files that are clean or ignored or
6521 the source of a copy/move operation, are not listed unless
6522 the source of a copy/move operation, are not listed unless
6522 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6523 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6523 Unless options described with "show only ..." are given, the
6524 Unless options described with "show only ..." are given, the
6524 options -mardu are used.
6525 options -mardu are used.
6525
6526
6526 Option -q/--quiet hides untracked (unknown and ignored) files
6527 Option -q/--quiet hides untracked (unknown and ignored) files
6527 unless explicitly requested with -u/--unknown or -i/--ignored.
6528 unless explicitly requested with -u/--unknown or -i/--ignored.
6528
6529
6529 .. note::
6530 .. note::
6530
6531
6531 :hg:`status` may appear to disagree with diff if permissions have
6532 :hg:`status` may appear to disagree with diff if permissions have
6532 changed or a merge has occurred. The standard diff format does
6533 changed or a merge has occurred. The standard diff format does
6533 not report permission changes and diff only reports changes
6534 not report permission changes and diff only reports changes
6534 relative to one merge parent.
6535 relative to one merge parent.
6535
6536
6536 If one revision is given, it is used as the base revision.
6537 If one revision is given, it is used as the base revision.
6537 If two revisions are given, the differences between them are
6538 If two revisions are given, the differences between them are
6538 shown. The --change option can also be used as a shortcut to list
6539 shown. The --change option can also be used as a shortcut to list
6539 the changed files of a revision from its first parent.
6540 the changed files of a revision from its first parent.
6540
6541
6541 The codes used to show the status of files are::
6542 The codes used to show the status of files are::
6542
6543
6543 M = modified
6544 M = modified
6544 A = added
6545 A = added
6545 R = removed
6546 R = removed
6546 C = clean
6547 C = clean
6547 ! = missing (deleted by non-hg command, but still tracked)
6548 ! = missing (deleted by non-hg command, but still tracked)
6548 ? = not tracked
6549 ? = not tracked
6549 I = ignored
6550 I = ignored
6550 = origin of the previous file (with --copies)
6551 = origin of the previous file (with --copies)
6551
6552
6552 .. container:: verbose
6553 .. container:: verbose
6553
6554
6554 Examples:
6555 Examples:
6555
6556
6556 - show changes in the working directory relative to a
6557 - show changes in the working directory relative to a
6557 changeset::
6558 changeset::
6558
6559
6559 hg status --rev 9353
6560 hg status --rev 9353
6560
6561
6561 - show changes in the working directory relative to the
6562 - show changes in the working directory relative to the
6562 current directory (see :hg:`help patterns` for more information)::
6563 current directory (see :hg:`help patterns` for more information)::
6563
6564
6564 hg status re:
6565 hg status re:
6565
6566
6566 - show all changes including copies in an existing changeset::
6567 - show all changes including copies in an existing changeset::
6567
6568
6568 hg status --copies --change 9353
6569 hg status --copies --change 9353
6569
6570
6570 - get a NUL separated list of added files, suitable for xargs::
6571 - get a NUL separated list of added files, suitable for xargs::
6571
6572
6572 hg status -an0
6573 hg status -an0
6573
6574
6574 Returns 0 on success.
6575 Returns 0 on success.
6575 """
6576 """
6576
6577
6577 revs = opts.get('rev')
6578 revs = opts.get('rev')
6578 change = opts.get('change')
6579 change = opts.get('change')
6579
6580
6580 if revs and change:
6581 if revs and change:
6581 msg = _('cannot specify --rev and --change at the same time')
6582 msg = _('cannot specify --rev and --change at the same time')
6582 raise error.Abort(msg)
6583 raise error.Abort(msg)
6583 elif change:
6584 elif change:
6584 node2 = scmutil.revsingle(repo, change, None).node()
6585 node2 = scmutil.revsingle(repo, change, None).node()
6585 node1 = repo[node2].p1().node()
6586 node1 = repo[node2].p1().node()
6586 else:
6587 else:
6587 node1, node2 = scmutil.revpair(repo, revs)
6588 node1, node2 = scmutil.revpair(repo, revs)
6588
6589
6589 if pats:
6590 if pats:
6590 cwd = repo.getcwd()
6591 cwd = repo.getcwd()
6591 else:
6592 else:
6592 cwd = ''
6593 cwd = ''
6593
6594
6594 if opts.get('print0'):
6595 if opts.get('print0'):
6595 end = '\0'
6596 end = '\0'
6596 else:
6597 else:
6597 end = '\n'
6598 end = '\n'
6598 copy = {}
6599 copy = {}
6599 states = 'modified added removed deleted unknown ignored clean'.split()
6600 states = 'modified added removed deleted unknown ignored clean'.split()
6600 show = [k for k in states if opts.get(k)]
6601 show = [k for k in states if opts.get(k)]
6601 if opts.get('all'):
6602 if opts.get('all'):
6602 show += ui.quiet and (states[:4] + ['clean']) or states
6603 show += ui.quiet and (states[:4] + ['clean']) or states
6603 if not show:
6604 if not show:
6604 if ui.quiet:
6605 if ui.quiet:
6605 show = states[:4]
6606 show = states[:4]
6606 else:
6607 else:
6607 show = states[:5]
6608 show = states[:5]
6608
6609
6609 m = scmutil.match(repo[node2], pats, opts)
6610 m = scmutil.match(repo[node2], pats, opts)
6610 stat = repo.status(node1, node2, m,
6611 stat = repo.status(node1, node2, m,
6611 'ignored' in show, 'clean' in show, 'unknown' in show,
6612 'ignored' in show, 'clean' in show, 'unknown' in show,
6612 opts.get('subrepos'))
6613 opts.get('subrepos'))
6613 changestates = zip(states, 'MAR!?IC', stat)
6614 changestates = zip(states, 'MAR!?IC', stat)
6614
6615
6615 if (opts.get('all') or opts.get('copies')
6616 if (opts.get('all') or opts.get('copies')
6616 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
6617 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
6617 copy = copies.pathcopies(repo[node1], repo[node2], m)
6618 copy = copies.pathcopies(repo[node1], repo[node2], m)
6618
6619
6619 fm = ui.formatter('status', opts)
6620 fm = ui.formatter('status', opts)
6620 fmt = '%s' + end
6621 fmt = '%s' + end
6621 showchar = not opts.get('no_status')
6622 showchar = not opts.get('no_status')
6622
6623
6623 for state, char, files in changestates:
6624 for state, char, files in changestates:
6624 if state in show:
6625 if state in show:
6625 label = 'status.' + state
6626 label = 'status.' + state
6626 for f in files:
6627 for f in files:
6627 fm.startitem()
6628 fm.startitem()
6628 fm.condwrite(showchar, 'status', '%s ', char, label=label)
6629 fm.condwrite(showchar, 'status', '%s ', char, label=label)
6629 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
6630 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
6630 if f in copy:
6631 if f in copy:
6631 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
6632 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
6632 label='status.copied')
6633 label='status.copied')
6633 fm.end()
6634 fm.end()
6634
6635
6635 @command('^summary|sum',
6636 @command('^summary|sum',
6636 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
6637 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
6637 def summary(ui, repo, **opts):
6638 def summary(ui, repo, **opts):
6638 """summarize working directory state
6639 """summarize working directory state
6639
6640
6640 This generates a brief summary of the working directory state,
6641 This generates a brief summary of the working directory state,
6641 including parents, branch, commit status, phase and available updates.
6642 including parents, branch, commit status, phase and available updates.
6642
6643
6643 With the --remote option, this will check the default paths for
6644 With the --remote option, this will check the default paths for
6644 incoming and outgoing changes. This can be time-consuming.
6645 incoming and outgoing changes. This can be time-consuming.
6645
6646
6646 Returns 0 on success.
6647 Returns 0 on success.
6647 """
6648 """
6648
6649
6649 ctx = repo[None]
6650 ctx = repo[None]
6650 parents = ctx.parents()
6651 parents = ctx.parents()
6651 pnode = parents[0].node()
6652 pnode = parents[0].node()
6652 marks = []
6653 marks = []
6653
6654
6654 ms = None
6655 ms = None
6655 try:
6656 try:
6656 ms = mergemod.mergestate.read(repo)
6657 ms = mergemod.mergestate.read(repo)
6657 except error.UnsupportedMergeRecords as e:
6658 except error.UnsupportedMergeRecords as e:
6658 s = ' '.join(e.recordtypes)
6659 s = ' '.join(e.recordtypes)
6659 ui.warn(
6660 ui.warn(
6660 _('warning: merge state has unsupported record types: %s\n') % s)
6661 _('warning: merge state has unsupported record types: %s\n') % s)
6661 unresolved = 0
6662 unresolved = 0
6662 else:
6663 else:
6663 unresolved = [f for f in ms if ms[f] == 'u']
6664 unresolved = [f for f in ms if ms[f] == 'u']
6664
6665
6665 for p in parents:
6666 for p in parents:
6666 # label with log.changeset (instead of log.parent) since this
6667 # label with log.changeset (instead of log.parent) since this
6667 # shows a working directory parent *changeset*:
6668 # shows a working directory parent *changeset*:
6668 # i18n: column positioning for "hg summary"
6669 # i18n: column positioning for "hg summary"
6669 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
6670 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
6670 label='log.changeset changeset.%s' % p.phasestr())
6671 label='log.changeset changeset.%s' % p.phasestr())
6671 ui.write(' '.join(p.tags()), label='log.tag')
6672 ui.write(' '.join(p.tags()), label='log.tag')
6672 if p.bookmarks():
6673 if p.bookmarks():
6673 marks.extend(p.bookmarks())
6674 marks.extend(p.bookmarks())
6674 if p.rev() == -1:
6675 if p.rev() == -1:
6675 if not len(repo):
6676 if not len(repo):
6676 ui.write(_(' (empty repository)'))
6677 ui.write(_(' (empty repository)'))
6677 else:
6678 else:
6678 ui.write(_(' (no revision checked out)'))
6679 ui.write(_(' (no revision checked out)'))
6679 ui.write('\n')
6680 ui.write('\n')
6680 if p.description():
6681 if p.description():
6681 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
6682 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
6682 label='log.summary')
6683 label='log.summary')
6683
6684
6684 branch = ctx.branch()
6685 branch = ctx.branch()
6685 bheads = repo.branchheads(branch)
6686 bheads = repo.branchheads(branch)
6686 # i18n: column positioning for "hg summary"
6687 # i18n: column positioning for "hg summary"
6687 m = _('branch: %s\n') % branch
6688 m = _('branch: %s\n') % branch
6688 if branch != 'default':
6689 if branch != 'default':
6689 ui.write(m, label='log.branch')
6690 ui.write(m, label='log.branch')
6690 else:
6691 else:
6691 ui.status(m, label='log.branch')
6692 ui.status(m, label='log.branch')
6692
6693
6693 if marks:
6694 if marks:
6694 active = repo._activebookmark
6695 active = repo._activebookmark
6695 # i18n: column positioning for "hg summary"
6696 # i18n: column positioning for "hg summary"
6696 ui.write(_('bookmarks:'), label='log.bookmark')
6697 ui.write(_('bookmarks:'), label='log.bookmark')
6697 if active is not None:
6698 if active is not None:
6698 if active in marks:
6699 if active in marks:
6699 ui.write(' *' + active, label=activebookmarklabel)
6700 ui.write(' *' + active, label=activebookmarklabel)
6700 marks.remove(active)
6701 marks.remove(active)
6701 else:
6702 else:
6702 ui.write(' [%s]' % active, label=activebookmarklabel)
6703 ui.write(' [%s]' % active, label=activebookmarklabel)
6703 for m in marks:
6704 for m in marks:
6704 ui.write(' ' + m, label='log.bookmark')
6705 ui.write(' ' + m, label='log.bookmark')
6705 ui.write('\n', label='log.bookmark')
6706 ui.write('\n', label='log.bookmark')
6706
6707
6707 status = repo.status(unknown=True)
6708 status = repo.status(unknown=True)
6708
6709
6709 c = repo.dirstate.copies()
6710 c = repo.dirstate.copies()
6710 copied, renamed = [], []
6711 copied, renamed = [], []
6711 for d, s in c.iteritems():
6712 for d, s in c.iteritems():
6712 if s in status.removed:
6713 if s in status.removed:
6713 status.removed.remove(s)
6714 status.removed.remove(s)
6714 renamed.append(d)
6715 renamed.append(d)
6715 else:
6716 else:
6716 copied.append(d)
6717 copied.append(d)
6717 if d in status.added:
6718 if d in status.added:
6718 status.added.remove(d)
6719 status.added.remove(d)
6719
6720
6720 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
6721 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
6721
6722
6722 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
6723 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
6723 (ui.label(_('%d added'), 'status.added'), status.added),
6724 (ui.label(_('%d added'), 'status.added'), status.added),
6724 (ui.label(_('%d removed'), 'status.removed'), status.removed),
6725 (ui.label(_('%d removed'), 'status.removed'), status.removed),
6725 (ui.label(_('%d renamed'), 'status.copied'), renamed),
6726 (ui.label(_('%d renamed'), 'status.copied'), renamed),
6726 (ui.label(_('%d copied'), 'status.copied'), copied),
6727 (ui.label(_('%d copied'), 'status.copied'), copied),
6727 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
6728 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
6728 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
6729 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
6729 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
6730 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
6730 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
6731 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
6731 t = []
6732 t = []
6732 for l, s in labels:
6733 for l, s in labels:
6733 if s:
6734 if s:
6734 t.append(l % len(s))
6735 t.append(l % len(s))
6735
6736
6736 t = ', '.join(t)
6737 t = ', '.join(t)
6737 cleanworkdir = False
6738 cleanworkdir = False
6738
6739
6739 if repo.vfs.exists('graftstate'):
6740 if repo.vfs.exists('graftstate'):
6740 t += _(' (graft in progress)')
6741 t += _(' (graft in progress)')
6741 if repo.vfs.exists('updatestate'):
6742 if repo.vfs.exists('updatestate'):
6742 t += _(' (interrupted update)')
6743 t += _(' (interrupted update)')
6743 elif len(parents) > 1:
6744 elif len(parents) > 1:
6744 t += _(' (merge)')
6745 t += _(' (merge)')
6745 elif branch != parents[0].branch():
6746 elif branch != parents[0].branch():
6746 t += _(' (new branch)')
6747 t += _(' (new branch)')
6747 elif (parents[0].closesbranch() and
6748 elif (parents[0].closesbranch() and
6748 pnode in repo.branchheads(branch, closed=True)):
6749 pnode in repo.branchheads(branch, closed=True)):
6749 t += _(' (head closed)')
6750 t += _(' (head closed)')
6750 elif not (status.modified or status.added or status.removed or renamed or
6751 elif not (status.modified or status.added or status.removed or renamed or
6751 copied or subs):
6752 copied or subs):
6752 t += _(' (clean)')
6753 t += _(' (clean)')
6753 cleanworkdir = True
6754 cleanworkdir = True
6754 elif pnode not in bheads:
6755 elif pnode not in bheads:
6755 t += _(' (new branch head)')
6756 t += _(' (new branch head)')
6756
6757
6757 if parents:
6758 if parents:
6758 pendingphase = max(p.phase() for p in parents)
6759 pendingphase = max(p.phase() for p in parents)
6759 else:
6760 else:
6760 pendingphase = phases.public
6761 pendingphase = phases.public
6761
6762
6762 if pendingphase > phases.newcommitphase(ui):
6763 if pendingphase > phases.newcommitphase(ui):
6763 t += ' (%s)' % phases.phasenames[pendingphase]
6764 t += ' (%s)' % phases.phasenames[pendingphase]
6764
6765
6765 if cleanworkdir:
6766 if cleanworkdir:
6766 # i18n: column positioning for "hg summary"
6767 # i18n: column positioning for "hg summary"
6767 ui.status(_('commit: %s\n') % t.strip())
6768 ui.status(_('commit: %s\n') % t.strip())
6768 else:
6769 else:
6769 # i18n: column positioning for "hg summary"
6770 # i18n: column positioning for "hg summary"
6770 ui.write(_('commit: %s\n') % t.strip())
6771 ui.write(_('commit: %s\n') % t.strip())
6771
6772
6772 # all ancestors of branch heads - all ancestors of parent = new csets
6773 # all ancestors of branch heads - all ancestors of parent = new csets
6773 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
6774 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
6774 bheads))
6775 bheads))
6775
6776
6776 if new == 0:
6777 if new == 0:
6777 # i18n: column positioning for "hg summary"
6778 # i18n: column positioning for "hg summary"
6778 ui.status(_('update: (current)\n'))
6779 ui.status(_('update: (current)\n'))
6779 elif pnode not in bheads:
6780 elif pnode not in bheads:
6780 # i18n: column positioning for "hg summary"
6781 # i18n: column positioning for "hg summary"
6781 ui.write(_('update: %d new changesets (update)\n') % new)
6782 ui.write(_('update: %d new changesets (update)\n') % new)
6782 else:
6783 else:
6783 # i18n: column positioning for "hg summary"
6784 # i18n: column positioning for "hg summary"
6784 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
6785 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
6785 (new, len(bheads)))
6786 (new, len(bheads)))
6786
6787
6787 t = []
6788 t = []
6788 draft = len(repo.revs('draft()'))
6789 draft = len(repo.revs('draft()'))
6789 if draft:
6790 if draft:
6790 t.append(_('%d draft') % draft)
6791 t.append(_('%d draft') % draft)
6791 secret = len(repo.revs('secret()'))
6792 secret = len(repo.revs('secret()'))
6792 if secret:
6793 if secret:
6793 t.append(_('%d secret') % secret)
6794 t.append(_('%d secret') % secret)
6794
6795
6795 if draft or secret:
6796 if draft or secret:
6796 ui.status(_('phases: %s\n') % ', '.join(t))
6797 ui.status(_('phases: %s\n') % ', '.join(t))
6797
6798
6798 if obsolete.isenabled(repo, obsolete.createmarkersopt):
6799 if obsolete.isenabled(repo, obsolete.createmarkersopt):
6799 for trouble in ("unstable", "divergent", "bumped"):
6800 for trouble in ("unstable", "divergent", "bumped"):
6800 numtrouble = len(repo.revs(trouble + "()"))
6801 numtrouble = len(repo.revs(trouble + "()"))
6801 # We write all the possibilities to ease translation
6802 # We write all the possibilities to ease translation
6802 troublemsg = {
6803 troublemsg = {
6803 "unstable": _("unstable: %d changesets"),
6804 "unstable": _("unstable: %d changesets"),
6804 "divergent": _("divergent: %d changesets"),
6805 "divergent": _("divergent: %d changesets"),
6805 "bumped": _("bumped: %d changesets"),
6806 "bumped": _("bumped: %d changesets"),
6806 }
6807 }
6807 if numtrouble > 0:
6808 if numtrouble > 0:
6808 ui.status(troublemsg[trouble] % numtrouble + "\n")
6809 ui.status(troublemsg[trouble] % numtrouble + "\n")
6809
6810
6810 cmdutil.summaryhooks(ui, repo)
6811 cmdutil.summaryhooks(ui, repo)
6811
6812
6812 if opts.get('remote'):
6813 if opts.get('remote'):
6813 needsincoming, needsoutgoing = True, True
6814 needsincoming, needsoutgoing = True, True
6814 else:
6815 else:
6815 needsincoming, needsoutgoing = False, False
6816 needsincoming, needsoutgoing = False, False
6816 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
6817 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
6817 if i:
6818 if i:
6818 needsincoming = True
6819 needsincoming = True
6819 if o:
6820 if o:
6820 needsoutgoing = True
6821 needsoutgoing = True
6821 if not needsincoming and not needsoutgoing:
6822 if not needsincoming and not needsoutgoing:
6822 return
6823 return
6823
6824
6824 def getincoming():
6825 def getincoming():
6825 source, branches = hg.parseurl(ui.expandpath('default'))
6826 source, branches = hg.parseurl(ui.expandpath('default'))
6826 sbranch = branches[0]
6827 sbranch = branches[0]
6827 try:
6828 try:
6828 other = hg.peer(repo, {}, source)
6829 other = hg.peer(repo, {}, source)
6829 except error.RepoError:
6830 except error.RepoError:
6830 if opts.get('remote'):
6831 if opts.get('remote'):
6831 raise
6832 raise
6832 return source, sbranch, None, None, None
6833 return source, sbranch, None, None, None
6833 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
6834 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
6834 if revs:
6835 if revs:
6835 revs = [other.lookup(rev) for rev in revs]
6836 revs = [other.lookup(rev) for rev in revs]
6836 ui.debug('comparing with %s\n' % util.hidepassword(source))
6837 ui.debug('comparing with %s\n' % util.hidepassword(source))
6837 repo.ui.pushbuffer()
6838 repo.ui.pushbuffer()
6838 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
6839 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
6839 repo.ui.popbuffer()
6840 repo.ui.popbuffer()
6840 return source, sbranch, other, commoninc, commoninc[1]
6841 return source, sbranch, other, commoninc, commoninc[1]
6841
6842
6842 if needsincoming:
6843 if needsincoming:
6843 source, sbranch, sother, commoninc, incoming = getincoming()
6844 source, sbranch, sother, commoninc, incoming = getincoming()
6844 else:
6845 else:
6845 source = sbranch = sother = commoninc = incoming = None
6846 source = sbranch = sother = commoninc = incoming = None
6846
6847
6847 def getoutgoing():
6848 def getoutgoing():
6848 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
6849 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
6849 dbranch = branches[0]
6850 dbranch = branches[0]
6850 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
6851 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
6851 if source != dest:
6852 if source != dest:
6852 try:
6853 try:
6853 dother = hg.peer(repo, {}, dest)
6854 dother = hg.peer(repo, {}, dest)
6854 except error.RepoError:
6855 except error.RepoError:
6855 if opts.get('remote'):
6856 if opts.get('remote'):
6856 raise
6857 raise
6857 return dest, dbranch, None, None
6858 return dest, dbranch, None, None
6858 ui.debug('comparing with %s\n' % util.hidepassword(dest))
6859 ui.debug('comparing with %s\n' % util.hidepassword(dest))
6859 elif sother is None:
6860 elif sother is None:
6860 # there is no explicit destination peer, but source one is invalid
6861 # there is no explicit destination peer, but source one is invalid
6861 return dest, dbranch, None, None
6862 return dest, dbranch, None, None
6862 else:
6863 else:
6863 dother = sother
6864 dother = sother
6864 if (source != dest or (sbranch is not None and sbranch != dbranch)):
6865 if (source != dest or (sbranch is not None and sbranch != dbranch)):
6865 common = None
6866 common = None
6866 else:
6867 else:
6867 common = commoninc
6868 common = commoninc
6868 if revs:
6869 if revs:
6869 revs = [repo.lookup(rev) for rev in revs]
6870 revs = [repo.lookup(rev) for rev in revs]
6870 repo.ui.pushbuffer()
6871 repo.ui.pushbuffer()
6871 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
6872 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
6872 commoninc=common)
6873 commoninc=common)
6873 repo.ui.popbuffer()
6874 repo.ui.popbuffer()
6874 return dest, dbranch, dother, outgoing
6875 return dest, dbranch, dother, outgoing
6875
6876
6876 if needsoutgoing:
6877 if needsoutgoing:
6877 dest, dbranch, dother, outgoing = getoutgoing()
6878 dest, dbranch, dother, outgoing = getoutgoing()
6878 else:
6879 else:
6879 dest = dbranch = dother = outgoing = None
6880 dest = dbranch = dother = outgoing = None
6880
6881
6881 if opts.get('remote'):
6882 if opts.get('remote'):
6882 t = []
6883 t = []
6883 if incoming:
6884 if incoming:
6884 t.append(_('1 or more incoming'))
6885 t.append(_('1 or more incoming'))
6885 o = outgoing.missing
6886 o = outgoing.missing
6886 if o:
6887 if o:
6887 t.append(_('%d outgoing') % len(o))
6888 t.append(_('%d outgoing') % len(o))
6888 other = dother or sother
6889 other = dother or sother
6889 if 'bookmarks' in other.listkeys('namespaces'):
6890 if 'bookmarks' in other.listkeys('namespaces'):
6890 counts = bookmarks.summary(repo, other)
6891 counts = bookmarks.summary(repo, other)
6891 if counts[0] > 0:
6892 if counts[0] > 0:
6892 t.append(_('%d incoming bookmarks') % counts[0])
6893 t.append(_('%d incoming bookmarks') % counts[0])
6893 if counts[1] > 0:
6894 if counts[1] > 0:
6894 t.append(_('%d outgoing bookmarks') % counts[1])
6895 t.append(_('%d outgoing bookmarks') % counts[1])
6895
6896
6896 if t:
6897 if t:
6897 # i18n: column positioning for "hg summary"
6898 # i18n: column positioning for "hg summary"
6898 ui.write(_('remote: %s\n') % (', '.join(t)))
6899 ui.write(_('remote: %s\n') % (', '.join(t)))
6899 else:
6900 else:
6900 # i18n: column positioning for "hg summary"
6901 # i18n: column positioning for "hg summary"
6901 ui.status(_('remote: (synced)\n'))
6902 ui.status(_('remote: (synced)\n'))
6902
6903
6903 cmdutil.summaryremotehooks(ui, repo, opts,
6904 cmdutil.summaryremotehooks(ui, repo, opts,
6904 ((source, sbranch, sother, commoninc),
6905 ((source, sbranch, sother, commoninc),
6905 (dest, dbranch, dother, outgoing)))
6906 (dest, dbranch, dother, outgoing)))
6906
6907
6907 @command('tag',
6908 @command('tag',
6908 [('f', 'force', None, _('force tag')),
6909 [('f', 'force', None, _('force tag')),
6909 ('l', 'local', None, _('make the tag local')),
6910 ('l', 'local', None, _('make the tag local')),
6910 ('r', 'rev', '', _('revision to tag'), _('REV')),
6911 ('r', 'rev', '', _('revision to tag'), _('REV')),
6911 ('', 'remove', None, _('remove a tag')),
6912 ('', 'remove', None, _('remove a tag')),
6912 # -l/--local is already there, commitopts cannot be used
6913 # -l/--local is already there, commitopts cannot be used
6913 ('e', 'edit', None, _('invoke editor on commit messages')),
6914 ('e', 'edit', None, _('invoke editor on commit messages')),
6914 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
6915 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
6915 ] + commitopts2,
6916 ] + commitopts2,
6916 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
6917 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
6917 def tag(ui, repo, name1, *names, **opts):
6918 def tag(ui, repo, name1, *names, **opts):
6918 """add one or more tags for the current or given revision
6919 """add one or more tags for the current or given revision
6919
6920
6920 Name a particular revision using <name>.
6921 Name a particular revision using <name>.
6921
6922
6922 Tags are used to name particular revisions of the repository and are
6923 Tags are used to name particular revisions of the repository and are
6923 very useful to compare different revisions, to go back to significant
6924 very useful to compare different revisions, to go back to significant
6924 earlier versions or to mark branch points as releases, etc. Changing
6925 earlier versions or to mark branch points as releases, etc. Changing
6925 an existing tag is normally disallowed; use -f/--force to override.
6926 an existing tag is normally disallowed; use -f/--force to override.
6926
6927
6927 If no revision is given, the parent of the working directory is
6928 If no revision is given, the parent of the working directory is
6928 used.
6929 used.
6929
6930
6930 To facilitate version control, distribution, and merging of tags,
6931 To facilitate version control, distribution, and merging of tags,
6931 they are stored as a file named ".hgtags" which is managed similarly
6932 they are stored as a file named ".hgtags" which is managed similarly
6932 to other project files and can be hand-edited if necessary. This
6933 to other project files and can be hand-edited if necessary. This
6933 also means that tagging creates a new commit. The file
6934 also means that tagging creates a new commit. The file
6934 ".hg/localtags" is used for local tags (not shared among
6935 ".hg/localtags" is used for local tags (not shared among
6935 repositories).
6936 repositories).
6936
6937
6937 Tag commits are usually made at the head of a branch. If the parent
6938 Tag commits are usually made at the head of a branch. If the parent
6938 of the working directory is not a branch head, :hg:`tag` aborts; use
6939 of the working directory is not a branch head, :hg:`tag` aborts; use
6939 -f/--force to force the tag commit to be based on a non-head
6940 -f/--force to force the tag commit to be based on a non-head
6940 changeset.
6941 changeset.
6941
6942
6942 See :hg:`help dates` for a list of formats valid for -d/--date.
6943 See :hg:`help dates` for a list of formats valid for -d/--date.
6943
6944
6944 Since tag names have priority over branch names during revision
6945 Since tag names have priority over branch names during revision
6945 lookup, using an existing branch name as a tag name is discouraged.
6946 lookup, using an existing branch name as a tag name is discouraged.
6946
6947
6947 Returns 0 on success.
6948 Returns 0 on success.
6948 """
6949 """
6949 wlock = lock = None
6950 wlock = lock = None
6950 try:
6951 try:
6951 wlock = repo.wlock()
6952 wlock = repo.wlock()
6952 lock = repo.lock()
6953 lock = repo.lock()
6953 rev_ = "."
6954 rev_ = "."
6954 names = [t.strip() for t in (name1,) + names]
6955 names = [t.strip() for t in (name1,) + names]
6955 if len(names) != len(set(names)):
6956 if len(names) != len(set(names)):
6956 raise error.Abort(_('tag names must be unique'))
6957 raise error.Abort(_('tag names must be unique'))
6957 for n in names:
6958 for n in names:
6958 scmutil.checknewlabel(repo, n, 'tag')
6959 scmutil.checknewlabel(repo, n, 'tag')
6959 if not n:
6960 if not n:
6960 raise error.Abort(_('tag names cannot consist entirely of '
6961 raise error.Abort(_('tag names cannot consist entirely of '
6961 'whitespace'))
6962 'whitespace'))
6962 if opts.get('rev') and opts.get('remove'):
6963 if opts.get('rev') and opts.get('remove'):
6963 raise error.Abort(_("--rev and --remove are incompatible"))
6964 raise error.Abort(_("--rev and --remove are incompatible"))
6964 if opts.get('rev'):
6965 if opts.get('rev'):
6965 rev_ = opts['rev']
6966 rev_ = opts['rev']
6966 message = opts.get('message')
6967 message = opts.get('message')
6967 if opts.get('remove'):
6968 if opts.get('remove'):
6968 if opts.get('local'):
6969 if opts.get('local'):
6969 expectedtype = 'local'
6970 expectedtype = 'local'
6970 else:
6971 else:
6971 expectedtype = 'global'
6972 expectedtype = 'global'
6972
6973
6973 for n in names:
6974 for n in names:
6974 if not repo.tagtype(n):
6975 if not repo.tagtype(n):
6975 raise error.Abort(_("tag '%s' does not exist") % n)
6976 raise error.Abort(_("tag '%s' does not exist") % n)
6976 if repo.tagtype(n) != expectedtype:
6977 if repo.tagtype(n) != expectedtype:
6977 if expectedtype == 'global':
6978 if expectedtype == 'global':
6978 raise error.Abort(_("tag '%s' is not a global tag") % n)
6979 raise error.Abort(_("tag '%s' is not a global tag") % n)
6979 else:
6980 else:
6980 raise error.Abort(_("tag '%s' is not a local tag") % n)
6981 raise error.Abort(_("tag '%s' is not a local tag") % n)
6981 rev_ = 'null'
6982 rev_ = 'null'
6982 if not message:
6983 if not message:
6983 # we don't translate commit messages
6984 # we don't translate commit messages
6984 message = 'Removed tag %s' % ', '.join(names)
6985 message = 'Removed tag %s' % ', '.join(names)
6985 elif not opts.get('force'):
6986 elif not opts.get('force'):
6986 for n in names:
6987 for n in names:
6987 if n in repo.tags():
6988 if n in repo.tags():
6988 raise error.Abort(_("tag '%s' already exists "
6989 raise error.Abort(_("tag '%s' already exists "
6989 "(use -f to force)") % n)
6990 "(use -f to force)") % n)
6990 if not opts.get('local'):
6991 if not opts.get('local'):
6991 p1, p2 = repo.dirstate.parents()
6992 p1, p2 = repo.dirstate.parents()
6992 if p2 != nullid:
6993 if p2 != nullid:
6993 raise error.Abort(_('uncommitted merge'))
6994 raise error.Abort(_('uncommitted merge'))
6994 bheads = repo.branchheads()
6995 bheads = repo.branchheads()
6995 if not opts.get('force') and bheads and p1 not in bheads:
6996 if not opts.get('force') and bheads and p1 not in bheads:
6996 raise error.Abort(_('not at a branch head (use -f to force)'))
6997 raise error.Abort(_('not at a branch head (use -f to force)'))
6997 r = scmutil.revsingle(repo, rev_).node()
6998 r = scmutil.revsingle(repo, rev_).node()
6998
6999
6999 if not message:
7000 if not message:
7000 # we don't translate commit messages
7001 # we don't translate commit messages
7001 message = ('Added tag %s for changeset %s' %
7002 message = ('Added tag %s for changeset %s' %
7002 (', '.join(names), short(r)))
7003 (', '.join(names), short(r)))
7003
7004
7004 date = opts.get('date')
7005 date = opts.get('date')
7005 if date:
7006 if date:
7006 date = util.parsedate(date)
7007 date = util.parsedate(date)
7007
7008
7008 if opts.get('remove'):
7009 if opts.get('remove'):
7009 editform = 'tag.remove'
7010 editform = 'tag.remove'
7010 else:
7011 else:
7011 editform = 'tag.add'
7012 editform = 'tag.add'
7012 editor = cmdutil.getcommiteditor(editform=editform, **opts)
7013 editor = cmdutil.getcommiteditor(editform=editform, **opts)
7013
7014
7014 # don't allow tagging the null rev
7015 # don't allow tagging the null rev
7015 if (not opts.get('remove') and
7016 if (not opts.get('remove') and
7016 scmutil.revsingle(repo, rev_).rev() == nullrev):
7017 scmutil.revsingle(repo, rev_).rev() == nullrev):
7017 raise error.Abort(_("cannot tag null revision"))
7018 raise error.Abort(_("cannot tag null revision"))
7018
7019
7019 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date,
7020 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date,
7020 editor=editor)
7021 editor=editor)
7021 finally:
7022 finally:
7022 release(lock, wlock)
7023 release(lock, wlock)
7023
7024
7024 @command('tags', formatteropts, '')
7025 @command('tags', formatteropts, '')
7025 def tags(ui, repo, **opts):
7026 def tags(ui, repo, **opts):
7026 """list repository tags
7027 """list repository tags
7027
7028
7028 This lists both regular and local tags. When the -v/--verbose
7029 This lists both regular and local tags. When the -v/--verbose
7029 switch is used, a third column "local" is printed for local tags.
7030 switch is used, a third column "local" is printed for local tags.
7030 When the -q/--quiet switch is used, only the tag name is printed.
7031 When the -q/--quiet switch is used, only the tag name is printed.
7031
7032
7032 Returns 0 on success.
7033 Returns 0 on success.
7033 """
7034 """
7034
7035
7035 fm = ui.formatter('tags', opts)
7036 fm = ui.formatter('tags', opts)
7036 hexfunc = fm.hexfunc
7037 hexfunc = fm.hexfunc
7037 tagtype = ""
7038 tagtype = ""
7038
7039
7039 for t, n in reversed(repo.tagslist()):
7040 for t, n in reversed(repo.tagslist()):
7040 hn = hexfunc(n)
7041 hn = hexfunc(n)
7041 label = 'tags.normal'
7042 label = 'tags.normal'
7042 tagtype = ''
7043 tagtype = ''
7043 if repo.tagtype(t) == 'local':
7044 if repo.tagtype(t) == 'local':
7044 label = 'tags.local'
7045 label = 'tags.local'
7045 tagtype = 'local'
7046 tagtype = 'local'
7046
7047
7047 fm.startitem()
7048 fm.startitem()
7048 fm.write('tag', '%s', t, label=label)
7049 fm.write('tag', '%s', t, label=label)
7049 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
7050 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
7050 fm.condwrite(not ui.quiet, 'rev node', fmt,
7051 fm.condwrite(not ui.quiet, 'rev node', fmt,
7051 repo.changelog.rev(n), hn, label=label)
7052 repo.changelog.rev(n), hn, label=label)
7052 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
7053 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
7053 tagtype, label=label)
7054 tagtype, label=label)
7054 fm.plain('\n')
7055 fm.plain('\n')
7055 fm.end()
7056 fm.end()
7056
7057
7057 @command('tip',
7058 @command('tip',
7058 [('p', 'patch', None, _('show patch')),
7059 [('p', 'patch', None, _('show patch')),
7059 ('g', 'git', None, _('use git extended diff format')),
7060 ('g', 'git', None, _('use git extended diff format')),
7060 ] + templateopts,
7061 ] + templateopts,
7061 _('[-p] [-g]'))
7062 _('[-p] [-g]'))
7062 def tip(ui, repo, **opts):
7063 def tip(ui, repo, **opts):
7063 """show the tip revision (DEPRECATED)
7064 """show the tip revision (DEPRECATED)
7064
7065
7065 The tip revision (usually just called the tip) is the changeset
7066 The tip revision (usually just called the tip) is the changeset
7066 most recently added to the repository (and therefore the most
7067 most recently added to the repository (and therefore the most
7067 recently changed head).
7068 recently changed head).
7068
7069
7069 If you have just made a commit, that commit will be the tip. If
7070 If you have just made a commit, that commit will be the tip. If
7070 you have just pulled changes from another repository, the tip of
7071 you have just pulled changes from another repository, the tip of
7071 that repository becomes the current tip. The "tip" tag is special
7072 that repository becomes the current tip. The "tip" tag is special
7072 and cannot be renamed or assigned to a different changeset.
7073 and cannot be renamed or assigned to a different changeset.
7073
7074
7074 This command is deprecated, please use :hg:`heads` instead.
7075 This command is deprecated, please use :hg:`heads` instead.
7075
7076
7076 Returns 0 on success.
7077 Returns 0 on success.
7077 """
7078 """
7078 displayer = cmdutil.show_changeset(ui, repo, opts)
7079 displayer = cmdutil.show_changeset(ui, repo, opts)
7079 displayer.show(repo['tip'])
7080 displayer.show(repo['tip'])
7080 displayer.close()
7081 displayer.close()
7081
7082
7082 @command('unbundle',
7083 @command('unbundle',
7083 [('u', 'update', None,
7084 [('u', 'update', None,
7084 _('update to new branch head if changesets were unbundled'))],
7085 _('update to new branch head if changesets were unbundled'))],
7085 _('[-u] FILE...'))
7086 _('[-u] FILE...'))
7086 def unbundle(ui, repo, fname1, *fnames, **opts):
7087 def unbundle(ui, repo, fname1, *fnames, **opts):
7087 """apply one or more changegroup files
7088 """apply one or more changegroup files
7088
7089
7089 Apply one or more compressed changegroup files generated by the
7090 Apply one or more compressed changegroup files generated by the
7090 bundle command.
7091 bundle command.
7091
7092
7092 Returns 0 on success, 1 if an update has unresolved files.
7093 Returns 0 on success, 1 if an update has unresolved files.
7093 """
7094 """
7094 fnames = (fname1,) + fnames
7095 fnames = (fname1,) + fnames
7095
7096
7096 with repo.lock():
7097 with repo.lock():
7097 for fname in fnames:
7098 for fname in fnames:
7098 f = hg.openpath(ui, fname)
7099 f = hg.openpath(ui, fname)
7099 gen = exchange.readbundle(ui, f, fname)
7100 gen = exchange.readbundle(ui, f, fname)
7100 if isinstance(gen, bundle2.unbundle20):
7101 if isinstance(gen, bundle2.unbundle20):
7101 tr = repo.transaction('unbundle')
7102 tr = repo.transaction('unbundle')
7102 try:
7103 try:
7103 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
7104 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
7104 url='bundle:' + fname)
7105 url='bundle:' + fname)
7105 tr.close()
7106 tr.close()
7106 except error.BundleUnknownFeatureError as exc:
7107 except error.BundleUnknownFeatureError as exc:
7107 raise error.Abort(_('%s: unknown bundle feature, %s')
7108 raise error.Abort(_('%s: unknown bundle feature, %s')
7108 % (fname, exc),
7109 % (fname, exc),
7109 hint=_("see https://mercurial-scm.org/"
7110 hint=_("see https://mercurial-scm.org/"
7110 "wiki/BundleFeature for more "
7111 "wiki/BundleFeature for more "
7111 "information"))
7112 "information"))
7112 finally:
7113 finally:
7113 if tr:
7114 if tr:
7114 tr.release()
7115 tr.release()
7115 changes = [r.get('return', 0)
7116 changes = [r.get('return', 0)
7116 for r in op.records['changegroup']]
7117 for r in op.records['changegroup']]
7117 modheads = changegroup.combineresults(changes)
7118 modheads = changegroup.combineresults(changes)
7118 elif isinstance(gen, streamclone.streamcloneapplier):
7119 elif isinstance(gen, streamclone.streamcloneapplier):
7119 raise error.Abort(
7120 raise error.Abort(
7120 _('packed bundles cannot be applied with '
7121 _('packed bundles cannot be applied with '
7121 '"hg unbundle"'),
7122 '"hg unbundle"'),
7122 hint=_('use "hg debugapplystreamclonebundle"'))
7123 hint=_('use "hg debugapplystreamclonebundle"'))
7123 else:
7124 else:
7124 modheads = gen.apply(repo, 'unbundle', 'bundle:' + fname)
7125 modheads = gen.apply(repo, 'unbundle', 'bundle:' + fname)
7125
7126
7126 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7127 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7127
7128
7128 @command('^update|up|checkout|co',
7129 @command('^update|up|checkout|co',
7129 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
7130 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
7130 ('c', 'check', None, _('require clean working directory')),
7131 ('c', 'check', None, _('require clean working directory')),
7131 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
7132 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
7132 ('r', 'rev', '', _('revision'), _('REV'))
7133 ('r', 'rev', '', _('revision'), _('REV'))
7133 ] + mergetoolopts,
7134 ] + mergetoolopts,
7134 _('[-c] [-C] [-d DATE] [[-r] REV]'))
7135 _('[-c] [-C] [-d DATE] [[-r] REV]'))
7135 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
7136 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
7136 tool=None):
7137 tool=None):
7137 """update working directory (or switch revisions)
7138 """update working directory (or switch revisions)
7138
7139
7139 Update the repository's working directory to the specified
7140 Update the repository's working directory to the specified
7140 changeset. If no changeset is specified, update to the tip of the
7141 changeset. If no changeset is specified, update to the tip of the
7141 current named branch and move the active bookmark (see :hg:`help
7142 current named branch and move the active bookmark (see :hg:`help
7142 bookmarks`).
7143 bookmarks`).
7143
7144
7144 Update sets the working directory's parent revision to the specified
7145 Update sets the working directory's parent revision to the specified
7145 changeset (see :hg:`help parents`).
7146 changeset (see :hg:`help parents`).
7146
7147
7147 If the changeset is not a descendant or ancestor of the working
7148 If the changeset is not a descendant or ancestor of the working
7148 directory's parent, the update is aborted. With the -c/--check
7149 directory's parent, the update is aborted. With the -c/--check
7149 option, the working directory is checked for uncommitted changes; if
7150 option, the working directory is checked for uncommitted changes; if
7150 none are found, the working directory is updated to the specified
7151 none are found, the working directory is updated to the specified
7151 changeset.
7152 changeset.
7152
7153
7153 .. container:: verbose
7154 .. container:: verbose
7154
7155
7155 The following rules apply when the working directory contains
7156 The following rules apply when the working directory contains
7156 uncommitted changes:
7157 uncommitted changes:
7157
7158
7158 1. If neither -c/--check nor -C/--clean is specified, and if
7159 1. If neither -c/--check nor -C/--clean is specified, and if
7159 the requested changeset is an ancestor or descendant of
7160 the requested changeset is an ancestor or descendant of
7160 the working directory's parent, the uncommitted changes
7161 the working directory's parent, the uncommitted changes
7161 are merged into the requested changeset and the merged
7162 are merged into the requested changeset and the merged
7162 result is left uncommitted. If the requested changeset is
7163 result is left uncommitted. If the requested changeset is
7163 not an ancestor or descendant (that is, it is on another
7164 not an ancestor or descendant (that is, it is on another
7164 branch), the update is aborted and the uncommitted changes
7165 branch), the update is aborted and the uncommitted changes
7165 are preserved.
7166 are preserved.
7166
7167
7167 2. With the -c/--check option, the update is aborted and the
7168 2. With the -c/--check option, the update is aborted and the
7168 uncommitted changes are preserved.
7169 uncommitted changes are preserved.
7169
7170
7170 3. With the -C/--clean option, uncommitted changes are discarded and
7171 3. With the -C/--clean option, uncommitted changes are discarded and
7171 the working directory is updated to the requested changeset.
7172 the working directory is updated to the requested changeset.
7172
7173
7173 To cancel an uncommitted merge (and lose your changes), use
7174 To cancel an uncommitted merge (and lose your changes), use
7174 :hg:`update --clean .`.
7175 :hg:`update --clean .`.
7175
7176
7176 Use null as the changeset to remove the working directory (like
7177 Use null as the changeset to remove the working directory (like
7177 :hg:`clone -U`).
7178 :hg:`clone -U`).
7178
7179
7179 If you want to revert just one file to an older revision, use
7180 If you want to revert just one file to an older revision, use
7180 :hg:`revert [-r REV] NAME`.
7181 :hg:`revert [-r REV] NAME`.
7181
7182
7182 See :hg:`help dates` for a list of formats valid for -d/--date.
7183 See :hg:`help dates` for a list of formats valid for -d/--date.
7183
7184
7184 Returns 0 on success, 1 if there are unresolved files.
7185 Returns 0 on success, 1 if there are unresolved files.
7185 """
7186 """
7186 if rev and node:
7187 if rev and node:
7187 raise error.Abort(_("please specify just one revision"))
7188 raise error.Abort(_("please specify just one revision"))
7188
7189
7189 if rev is None or rev == '':
7190 if rev is None or rev == '':
7190 rev = node
7191 rev = node
7191
7192
7192 if date and rev is not None:
7193 if date and rev is not None:
7193 raise error.Abort(_("you can't specify a revision and a date"))
7194 raise error.Abort(_("you can't specify a revision and a date"))
7194
7195
7195 if check and clean:
7196 if check and clean:
7196 raise error.Abort(_("cannot specify both -c/--check and -C/--clean"))
7197 raise error.Abort(_("cannot specify both -c/--check and -C/--clean"))
7197
7198
7198 with repo.wlock():
7199 with repo.wlock():
7199 cmdutil.clearunfinished(repo)
7200 cmdutil.clearunfinished(repo)
7200
7201
7201 if date:
7202 if date:
7202 rev = cmdutil.finddate(ui, repo, date)
7203 rev = cmdutil.finddate(ui, repo, date)
7203
7204
7204 # if we defined a bookmark, we have to remember the original name
7205 # if we defined a bookmark, we have to remember the original name
7205 brev = rev
7206 brev = rev
7206 rev = scmutil.revsingle(repo, rev, rev).rev()
7207 rev = scmutil.revsingle(repo, rev, rev).rev()
7207
7208
7208 if check:
7209 if check:
7209 cmdutil.bailifchanged(repo, merge=False)
7210 cmdutil.bailifchanged(repo, merge=False)
7210
7211
7211 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
7212 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
7212
7213
7213 return hg.updatetotally(ui, repo, rev, brev, clean=clean, check=check)
7214 return hg.updatetotally(ui, repo, rev, brev, clean=clean, check=check)
7214
7215
7215 @command('verify', [])
7216 @command('verify', [])
7216 def verify(ui, repo):
7217 def verify(ui, repo):
7217 """verify the integrity of the repository
7218 """verify the integrity of the repository
7218
7219
7219 Verify the integrity of the current repository.
7220 Verify the integrity of the current repository.
7220
7221
7221 This will perform an extensive check of the repository's
7222 This will perform an extensive check of the repository's
7222 integrity, validating the hashes and checksums of each entry in
7223 integrity, validating the hashes and checksums of each entry in
7223 the changelog, manifest, and tracked files, as well as the
7224 the changelog, manifest, and tracked files, as well as the
7224 integrity of their crosslinks and indices.
7225 integrity of their crosslinks and indices.
7225
7226
7226 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7227 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7227 for more information about recovery from corruption of the
7228 for more information about recovery from corruption of the
7228 repository.
7229 repository.
7229
7230
7230 Returns 0 on success, 1 if errors are encountered.
7231 Returns 0 on success, 1 if errors are encountered.
7231 """
7232 """
7232 return hg.verify(repo)
7233 return hg.verify(repo)
7233
7234
7234 @command('version', [], norepo=True)
7235 @command('version', [], norepo=True)
7235 def version_(ui):
7236 def version_(ui):
7236 """output version and copyright information"""
7237 """output version and copyright information"""
7237 ui.write(_("Mercurial Distributed SCM (version %s)\n")
7238 ui.write(_("Mercurial Distributed SCM (version %s)\n")
7238 % util.version())
7239 % util.version())
7239 ui.status(_(
7240 ui.status(_(
7240 "(see https://mercurial-scm.org for more information)\n"
7241 "(see https://mercurial-scm.org for more information)\n"
7241 "\nCopyright (C) 2005-2016 Matt Mackall and others\n"
7242 "\nCopyright (C) 2005-2016 Matt Mackall and others\n"
7242 "This is free software; see the source for copying conditions. "
7243 "This is free software; see the source for copying conditions. "
7243 "There is NO\nwarranty; "
7244 "There is NO\nwarranty; "
7244 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7245 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7245 ))
7246 ))
7246
7247
7247 ui.note(_("\nEnabled extensions:\n\n"))
7248 ui.note(_("\nEnabled extensions:\n\n"))
7248 if ui.verbose:
7249 if ui.verbose:
7249 # format names and versions into columns
7250 # format names and versions into columns
7250 names = []
7251 names = []
7251 vers = []
7252 vers = []
7252 place = []
7253 place = []
7253 for name, module in extensions.extensions():
7254 for name, module in extensions.extensions():
7254 names.append(name)
7255 names.append(name)
7255 vers.append(extensions.moduleversion(module))
7256 vers.append(extensions.moduleversion(module))
7256 if extensions.ismoduleinternal(module):
7257 if extensions.ismoduleinternal(module):
7257 place.append(_("internal"))
7258 place.append(_("internal"))
7258 else:
7259 else:
7259 place.append(_("external"))
7260 place.append(_("external"))
7260 if names:
7261 if names:
7261 maxnamelen = max(len(n) for n in names)
7262 maxnamelen = max(len(n) for n in names)
7262 for i, name in enumerate(names):
7263 for i, name in enumerate(names):
7263 ui.write(" %-*s %s %s\n" %
7264 ui.write(" %-*s %s %s\n" %
7264 (maxnamelen, name, place[i], vers[i]))
7265 (maxnamelen, name, place[i], vers[i]))
7265
7266
7266 def loadcmdtable(ui, name, cmdtable):
7267 def loadcmdtable(ui, name, cmdtable):
7267 """Load command functions from specified cmdtable
7268 """Load command functions from specified cmdtable
7268 """
7269 """
7269 overrides = [cmd for cmd in cmdtable if cmd in table]
7270 overrides = [cmd for cmd in cmdtable if cmd in table]
7270 if overrides:
7271 if overrides:
7271 ui.warn(_("extension '%s' overrides commands: %s\n")
7272 ui.warn(_("extension '%s' overrides commands: %s\n")
7272 % (name, " ".join(overrides)))
7273 % (name, " ".join(overrides)))
7273 table.update(cmdtable)
7274 table.update(cmdtable)
@@ -1,290 +1,310 b''
1 $ cat > correct.py <<EOF
1 $ cat > correct.py <<EOF
2 > def toto(arg1, arg2):
2 > def toto(arg1, arg2):
3 > del arg2
3 > del arg2
4 > return (5 + 6, 9)
4 > return (5 + 6, 9)
5 > EOF
5 > EOF
6 $ cat > wrong.py <<EOF
6 $ cat > wrong.py <<EOF
7 > def toto( arg1, arg2):
7 > def toto( arg1, arg2):
8 > del(arg2)
8 > del(arg2)
9 > return ( 5+6, 9)
9 > return ( 5+6, 9)
10 > EOF
10 > EOF
11 $ cat > quote.py <<EOF
11 $ cat > quote.py <<EOF
12 > # let's use quote in comments
12 > # let's use quote in comments
13 > (''' ( 4x5 )
13 > (''' ( 4x5 )
14 > but """\\''' and finally''',
14 > but """\\''' and finally''',
15 > """let's fool checkpatch""", '1+2',
15 > """let's fool checkpatch""", '1+2',
16 > '"""', 42+1, """and
16 > '"""', 42+1, """and
17 > ( 4-1 ) """, "( 1+1 )\" and ")
17 > ( 4-1 ) """, "( 1+1 )\" and ")
18 > a, '\\\\\\\\', "\\\\\\" x-2", "c-1"
18 > a, '\\\\\\\\', "\\\\\\" x-2", "c-1"
19 > EOF
19 > EOF
20 $ cat > classstyle.py <<EOF
20 $ cat > classstyle.py <<EOF
21 > class newstyle_class(object):
21 > class newstyle_class(object):
22 > pass
22 > pass
23 >
23 >
24 > class oldstyle_class:
24 > class oldstyle_class:
25 > pass
25 > pass
26 >
26 >
27 > class empty():
27 > class empty():
28 > pass
28 > pass
29 >
29 >
30 > no_class = 1:
30 > no_class = 1:
31 > pass
31 > pass
32 > EOF
32 > EOF
33 $ check_code="$TESTDIR"/../contrib/check-code.py
33 $ check_code="$TESTDIR"/../contrib/check-code.py
34 $ "$check_code" ./wrong.py ./correct.py ./quote.py ./classstyle.py
34 $ "$check_code" ./wrong.py ./correct.py ./quote.py ./classstyle.py
35 ./wrong.py:1:
35 ./wrong.py:1:
36 > def toto( arg1, arg2):
36 > def toto( arg1, arg2):
37 gratuitous whitespace in () or []
37 gratuitous whitespace in () or []
38 ./wrong.py:2:
38 ./wrong.py:2:
39 > del(arg2)
39 > del(arg2)
40 Python keyword is not a function
40 Python keyword is not a function
41 ./wrong.py:3:
41 ./wrong.py:3:
42 > return ( 5+6, 9)
42 > return ( 5+6, 9)
43 gratuitous whitespace in () or []
43 gratuitous whitespace in () or []
44 missing whitespace in expression
44 missing whitespace in expression
45 ./quote.py:5:
45 ./quote.py:5:
46 > '"""', 42+1, """and
46 > '"""', 42+1, """and
47 missing whitespace in expression
47 missing whitespace in expression
48 ./classstyle.py:4:
48 ./classstyle.py:4:
49 > class oldstyle_class:
49 > class oldstyle_class:
50 old-style class, use class foo(object)
50 old-style class, use class foo(object)
51 ./classstyle.py:7:
51 ./classstyle.py:7:
52 > class empty():
52 > class empty():
53 class foo() creates old style object, use class foo(object)
53 class foo() creates old style object, use class foo(object)
54 [1]
54 [1]
55 $ cat > python3-compat.py << EOF
55 $ cat > python3-compat.py << EOF
56 > foo <> bar
56 > foo <> bar
57 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
57 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
58 > dict(key=value)
58 > dict(key=value)
59 > EOF
59 > EOF
60 $ "$check_code" python3-compat.py
60 $ "$check_code" python3-compat.py
61 python3-compat.py:1:
61 python3-compat.py:1:
62 > foo <> bar
62 > foo <> bar
63 <> operator is not available in Python 3+, use !=
63 <> operator is not available in Python 3+, use !=
64 python3-compat.py:2:
64 python3-compat.py:2:
65 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
65 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
66 reduce is not available in Python 3+
66 reduce is not available in Python 3+
67 python3-compat.py:3:
67 python3-compat.py:3:
68 > dict(key=value)
68 > dict(key=value)
69 dict() is different in Py2 and 3 and is slower than {}
69 dict() is different in Py2 and 3 and is slower than {}
70 [1]
70 [1]
71
71
72 $ cat > foo.c <<EOF
72 $ cat > foo.c <<EOF
73 > void narf() {
73 > void narf() {
74 > strcpy(foo, bar);
74 > strcpy(foo, bar);
75 > // strcpy_s is okay, but this comment is not
75 > // strcpy_s is okay, but this comment is not
76 > strcpy_s(foo, bar);
76 > strcpy_s(foo, bar);
77 > }
77 > }
78 > EOF
78 > EOF
79 $ "$check_code" ./foo.c
79 $ "$check_code" ./foo.c
80 ./foo.c:2:
80 ./foo.c:2:
81 > strcpy(foo, bar);
81 > strcpy(foo, bar);
82 don't use strcpy, use strlcpy or memcpy
82 don't use strcpy, use strlcpy or memcpy
83 ./foo.c:3:
83 ./foo.c:3:
84 > // strcpy_s is okay, but this comment is not
84 > // strcpy_s is okay, but this comment is not
85 don't use //-style comments
85 don't use //-style comments
86 [1]
86 [1]
87
87
88 $ cat > is-op.py <<EOF
88 $ cat > is-op.py <<EOF
89 > # is-operator comparing number or string literal
89 > # is-operator comparing number or string literal
90 > x = None
90 > x = None
91 > y = x is 'foo'
91 > y = x is 'foo'
92 > y = x is "foo"
92 > y = x is "foo"
93 > y = x is 5346
93 > y = x is 5346
94 > y = x is -6
94 > y = x is -6
95 > y = x is not 'foo'
95 > y = x is not 'foo'
96 > y = x is not "foo"
96 > y = x is not "foo"
97 > y = x is not 5346
97 > y = x is not 5346
98 > y = x is not -6
98 > y = x is not -6
99 > EOF
99 > EOF
100
100
101 $ "$check_code" ./is-op.py
101 $ "$check_code" ./is-op.py
102 ./is-op.py:3:
102 ./is-op.py:3:
103 > y = x is 'foo'
103 > y = x is 'foo'
104 object comparison with literal
104 object comparison with literal
105 ./is-op.py:4:
105 ./is-op.py:4:
106 > y = x is "foo"
106 > y = x is "foo"
107 object comparison with literal
107 object comparison with literal
108 ./is-op.py:5:
108 ./is-op.py:5:
109 > y = x is 5346
109 > y = x is 5346
110 object comparison with literal
110 object comparison with literal
111 ./is-op.py:6:
111 ./is-op.py:6:
112 > y = x is -6
112 > y = x is -6
113 object comparison with literal
113 object comparison with literal
114 ./is-op.py:7:
114 ./is-op.py:7:
115 > y = x is not 'foo'
115 > y = x is not 'foo'
116 object comparison with literal
116 object comparison with literal
117 ./is-op.py:8:
117 ./is-op.py:8:
118 > y = x is not "foo"
118 > y = x is not "foo"
119 object comparison with literal
119 object comparison with literal
120 ./is-op.py:9:
120 ./is-op.py:9:
121 > y = x is not 5346
121 > y = x is not 5346
122 object comparison with literal
122 object comparison with literal
123 ./is-op.py:10:
123 ./is-op.py:10:
124 > y = x is not -6
124 > y = x is not -6
125 object comparison with literal
125 object comparison with literal
126 [1]
126 [1]
127
127
128 $ cat > for-nolineno.py <<EOF
128 $ cat > for-nolineno.py <<EOF
129 > except:
129 > except:
130 > EOF
130 > EOF
131 $ "$check_code" for-nolineno.py --nolineno
131 $ "$check_code" for-nolineno.py --nolineno
132 for-nolineno.py:0:
132 for-nolineno.py:0:
133 > except:
133 > except:
134 naked except clause
134 naked except clause
135 [1]
135 [1]
136
136
137 $ cat > warning.t <<EOF
137 $ cat > warning.t <<EOF
138 > $ function warnonly {
138 > $ function warnonly {
139 > > }
139 > > }
140 > $ diff -N aaa
140 > $ diff -N aaa
141 > $ function onwarn {}
141 > $ function onwarn {}
142 > EOF
142 > EOF
143 $ "$check_code" warning.t
143 $ "$check_code" warning.t
144 $ "$check_code" --warn warning.t
144 $ "$check_code" --warn warning.t
145 warning.t:1:
145 warning.t:1:
146 > $ function warnonly {
146 > $ function warnonly {
147 warning: don't use 'function', use old style
147 warning: don't use 'function', use old style
148 warning.t:3:
148 warning.t:3:
149 > $ diff -N aaa
149 > $ diff -N aaa
150 warning: don't use 'diff -N'
150 warning: don't use 'diff -N'
151 warning.t:4:
151 warning.t:4:
152 > $ function onwarn {}
152 > $ function onwarn {}
153 warning: don't use 'function', use old style
153 warning: don't use 'function', use old style
154 [1]
154 [1]
155 $ cat > raise-format.py <<EOF
155 $ cat > raise-format.py <<EOF
156 > raise SomeException, message
156 > raise SomeException, message
157 > # this next line is okay
157 > # this next line is okay
158 > raise SomeException(arg1, arg2)
158 > raise SomeException(arg1, arg2)
159 > EOF
159 > EOF
160 $ "$check_code" not-existing.py raise-format.py
160 $ "$check_code" not-existing.py raise-format.py
161 Skipping*not-existing.py* (glob)
161 Skipping*not-existing.py* (glob)
162 raise-format.py:1:
162 raise-format.py:1:
163 > raise SomeException, message
163 > raise SomeException, message
164 don't use old-style two-argument raise, use Exception(message)
164 don't use old-style two-argument raise, use Exception(message)
165 [1]
165 [1]
166
166
167 $ cat > rst.py <<EOF
167 $ cat > rst.py <<EOF
168 > """problematic rst text
168 > """problematic rst text
169 >
169 >
170 > .. note::
170 > .. note::
171 > wrong
171 > wrong
172 > """
172 > """
173 >
173 >
174 > '''
174 > '''
175 >
175 >
176 > .. note::
176 > .. note::
177 >
177 >
178 > valid
178 > valid
179 >
179 >
180 > new text
180 > new text
181 >
181 >
182 > .. note::
182 > .. note::
183 >
183 >
184 > also valid
184 > also valid
185 > '''
185 > '''
186 >
186 >
187 > """mixed
187 > """mixed
188 >
188 >
189 > .. note::
189 > .. note::
190 >
190 >
191 > good
191 > good
192 >
192 >
193 > .. note::
193 > .. note::
194 > plus bad
194 > plus bad
195 > """
195 > """
196 > EOF
196 > EOF
197 $ $check_code -w rst.py
197 $ $check_code -w rst.py
198 rst.py:3:
198 rst.py:3:
199 > .. note::
199 > .. note::
200 warning: add two newlines after '.. note::'
200 warning: add two newlines after '.. note::'
201 rst.py:26:
201 rst.py:26:
202 > .. note::
202 > .. note::
203 warning: add two newlines after '.. note::'
203 warning: add two newlines after '.. note::'
204 [1]
204 [1]
205
205
206 $ cat > ./map-inside-gettext.py <<EOF
206 $ cat > ./map-inside-gettext.py <<EOF
207 > print _("map inside gettext %s" % v)
207 > print _("map inside gettext %s" % v)
208 >
208 >
209 > print _("concatenating " " by " " space %s" % v)
209 > print _("concatenating " " by " " space %s" % v)
210 > print _("concatenating " + " by " + " '+' %s" % v)
210 > print _("concatenating " + " by " + " '+' %s" % v)
211 >
211 >
212 > print _("mapping operation in different line %s"
212 > print _("mapping operation in different line %s"
213 > % v)
213 > % v)
214 >
214 >
215 > print _(
215 > print _(
216 > "leading spaces inside of '(' %s" % v)
216 > "leading spaces inside of '(' %s" % v)
217 > EOF
217 > EOF
218 $ "$check_code" ./map-inside-gettext.py
218 $ "$check_code" ./map-inside-gettext.py
219 ./map-inside-gettext.py:1:
219 ./map-inside-gettext.py:1:
220 > print _("map inside gettext %s" % v)
220 > print _("map inside gettext %s" % v)
221 don't use % inside _()
221 don't use % inside _()
222 ./map-inside-gettext.py:3:
222 ./map-inside-gettext.py:3:
223 > print _("concatenating " " by " " space %s" % v)
223 > print _("concatenating " " by " " space %s" % v)
224 don't use % inside _()
224 don't use % inside _()
225 ./map-inside-gettext.py:4:
225 ./map-inside-gettext.py:4:
226 > print _("concatenating " + " by " + " '+' %s" % v)
226 > print _("concatenating " + " by " + " '+' %s" % v)
227 don't use % inside _()
227 don't use % inside _()
228 ./map-inside-gettext.py:6:
228 ./map-inside-gettext.py:6:
229 > print _("mapping operation in different line %s"
229 > print _("mapping operation in different line %s"
230 don't use % inside _()
230 don't use % inside _()
231 ./map-inside-gettext.py:9:
231 ./map-inside-gettext.py:9:
232 > print _(
232 > print _(
233 don't use % inside _()
233 don't use % inside _()
234 [1]
234 [1]
235
235
236 web templates
236 web templates
237
237
238 $ mkdir -p mercurial/templates
238 $ mkdir -p mercurial/templates
239 $ cat > mercurial/templates/example.tmpl <<EOF
239 $ cat > mercurial/templates/example.tmpl <<EOF
240 > {desc}
240 > {desc}
241 > {desc|escape}
241 > {desc|escape}
242 > {desc|firstline}
242 > {desc|firstline}
243 > {desc|websub}
243 > {desc|websub}
244 > EOF
244 > EOF
245
245
246 $ "$check_code" --warnings mercurial/templates/example.tmpl
246 $ "$check_code" --warnings mercurial/templates/example.tmpl
247 mercurial/templates/example.tmpl:2:
247 mercurial/templates/example.tmpl:2:
248 > {desc|escape}
248 > {desc|escape}
249 warning: follow desc keyword with either firstline or websub
249 warning: follow desc keyword with either firstline or websub
250 [1]
250 [1]
251
251
252 'string join across lines with no space' detection
252 'string join across lines with no space' detection
253
253
254 $ cat > stringjoin.py <<EOF
254 $ cat > stringjoin.py <<EOF
255 > foo = (' foo'
255 > foo = (' foo'
256 > 'bar foo.'
256 > 'bar foo.'
257 > 'bar foo:'
257 > 'bar foo:'
258 > 'bar foo@'
258 > 'bar foo@'
259 > 'bar foo%'
259 > 'bar foo%'
260 > 'bar foo*'
260 > 'bar foo*'
261 > 'bar foo+'
261 > 'bar foo+'
262 > 'bar foo-'
262 > 'bar foo-'
263 > 'bar')
263 > 'bar')
264 > EOF
264 > EOF
265
266 'missing _() in ui message' detection
267
268 $ cat > uigettext.py <<EOF
269 > ui.status("% 10s %05d % -3.2f %*s %%"
270 > # this use '\\\\' instead of '\\', because the latter in
271 > # heredoc on shell becomes just '\'
272 > '\\\\ \n \t \0'
273 > """12345
274 > """
275 > '''.:*+-=
276 > ''' "%-6d \n 123456 .:*+-= foobar")
277 > EOF
278
265 $ "$check_code" stringjoin.py
279 $ "$check_code" stringjoin.py
266 stringjoin.py:1:
280 stringjoin.py:1:
267 > foo = (' foo'
281 > foo = (' foo'
268 string join across lines with no space
282 string join across lines with no space
269 stringjoin.py:2:
283 stringjoin.py:2:
270 > 'bar foo.'
284 > 'bar foo.'
271 string join across lines with no space
285 string join across lines with no space
272 stringjoin.py:3:
286 stringjoin.py:3:
273 > 'bar foo:'
287 > 'bar foo:'
274 string join across lines with no space
288 string join across lines with no space
275 stringjoin.py:4:
289 stringjoin.py:4:
276 > 'bar foo@'
290 > 'bar foo@'
277 string join across lines with no space
291 string join across lines with no space
278 stringjoin.py:5:
292 stringjoin.py:5:
279 > 'bar foo%'
293 > 'bar foo%'
280 string join across lines with no space
294 string join across lines with no space
281 stringjoin.py:6:
295 stringjoin.py:6:
282 > 'bar foo*'
296 > 'bar foo*'
283 string join across lines with no space
297 string join across lines with no space
284 stringjoin.py:7:
298 stringjoin.py:7:
285 > 'bar foo+'
299 > 'bar foo+'
286 string join across lines with no space
300 string join across lines with no space
287 stringjoin.py:8:
301 stringjoin.py:8:
288 > 'bar foo-'
302 > 'bar foo-'
289 string join across lines with no space
303 string join across lines with no space
290 [1]
304 [1]
305
306 $ "$check_code" uigettext.py
307 uigettext.py:1:
308 > ui.status("% 10s %05d % -3.2f %*s %%"
309 missing _() in ui message (use () to hide false-positives)
310 [1]
General Comments 0
You need to be logged in to leave comments. Login now