##// END OF EJS Templates
tests: convert the 'file://\$TESTTMP' rule to an automatic substitution...
Matt Harbison -
r35462:991e4404 default
parent child Browse files
Show More
@@ -1,739 +1,737 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 # check "rules depending on implementation of repquote()" in each
53 # check "rules depending on implementation of repquote()" in each
54 # patterns (especially pypats), before changing around repquote()
54 # patterns (especially pypats), before changing around repquote()
55 _repquotefixedmap = {' ': ' ', '\n': '\n', '.': 'p', ':': 'q',
55 _repquotefixedmap = {' ': ' ', '\n': '\n', '.': 'p', ':': 'q',
56 '%': '%', '\\': 'b', '*': 'A', '+': 'P', '-': 'M'}
56 '%': '%', '\\': 'b', '*': 'A', '+': 'P', '-': 'M'}
57 def _repquoteencodechr(i):
57 def _repquoteencodechr(i):
58 if i > 255:
58 if i > 255:
59 return 'u'
59 return 'u'
60 c = chr(i)
60 c = chr(i)
61 if c in _repquotefixedmap:
61 if c in _repquotefixedmap:
62 return _repquotefixedmap[c]
62 return _repquotefixedmap[c]
63 if c.isalpha():
63 if c.isalpha():
64 return 'x'
64 return 'x'
65 if c.isdigit():
65 if c.isdigit():
66 return 'n'
66 return 'n'
67 return 'o'
67 return 'o'
68 _repquotett = ''.join(_repquoteencodechr(i) for i in xrange(256))
68 _repquotett = ''.join(_repquoteencodechr(i) for i in xrange(256))
69
69
70 def repquote(m):
70 def repquote(m):
71 t = m.group('text')
71 t = m.group('text')
72 t = t.translate(_repquotett)
72 t = t.translate(_repquotett)
73 return m.group('quote') + t + m.group('quote')
73 return m.group('quote') + t + m.group('quote')
74
74
75 def reppython(m):
75 def reppython(m):
76 comment = m.group('comment')
76 comment = m.group('comment')
77 if comment:
77 if comment:
78 l = len(comment.rstrip())
78 l = len(comment.rstrip())
79 return "#" * l + comment[l:]
79 return "#" * l + comment[l:]
80 return repquote(m)
80 return repquote(m)
81
81
82 def repcomment(m):
82 def repcomment(m):
83 return m.group(1) + "#" * len(m.group(2))
83 return m.group(1) + "#" * len(m.group(2))
84
84
85 def repccomment(m):
85 def repccomment(m):
86 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
86 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
87 return m.group(1) + t + "*/"
87 return m.group(1) + t + "*/"
88
88
89 def repcallspaces(m):
89 def repcallspaces(m):
90 t = re.sub(r"\n\s+", "\n", m.group(2))
90 t = re.sub(r"\n\s+", "\n", m.group(2))
91 return m.group(1) + t
91 return m.group(1) + t
92
92
93 def repinclude(m):
93 def repinclude(m):
94 return m.group(1) + "<foo>"
94 return m.group(1) + "<foo>"
95
95
96 def rephere(m):
96 def rephere(m):
97 t = re.sub(r"\S", "x", m.group(2))
97 t = re.sub(r"\S", "x", m.group(2))
98 return m.group(1) + t
98 return m.group(1) + t
99
99
100
100
101 testpats = [
101 testpats = [
102 [
102 [
103 (r'\b(push|pop)d\b', "don't use 'pushd' or 'popd', use 'cd'"),
103 (r'\b(push|pop)d\b', "don't use 'pushd' or 'popd', use 'cd'"),
104 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
104 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
105 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
105 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
106 (r'(?<!hg )grep.* -a', "don't use 'grep -a', use in-line python"),
106 (r'(?<!hg )grep.* -a', "don't use 'grep -a', use in-line python"),
107 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
107 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
108 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
108 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
109 (r'echo -n', "don't use 'echo -n', use printf"),
109 (r'echo -n', "don't use 'echo -n', use printf"),
110 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
110 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
111 (r'head -c', "don't use 'head -c', use 'dd'"),
111 (r'head -c', "don't use 'head -c', use 'dd'"),
112 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
112 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
113 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
113 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
114 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
114 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
115 (r'printf.*[^\\]\\([1-9]|0\d)', r"don't use 'printf \NNN', use Python"),
115 (r'printf.*[^\\]\\([1-9]|0\d)', r"don't use 'printf \NNN', use Python"),
116 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
116 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
117 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
117 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
118 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
118 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
119 (r'\[[^\]]+==', '[ foo == bar ] is a bashism, use [ foo = bar ] instead'),
119 (r'\[[^\]]+==', '[ foo == bar ] is a bashism, use [ foo = bar ] instead'),
120 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
120 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
121 "use egrep for extended grep syntax"),
121 "use egrep for extended grep syntax"),
122 (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"),
122 (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"),
123 (r'(?<!!)/bin/', "don't use explicit paths for tools"),
123 (r'(?<!!)/bin/', "don't use explicit paths for tools"),
124 (r'#!.*/bash', "don't use bash in shebang, use sh"),
124 (r'#!.*/bash', "don't use bash in shebang, use sh"),
125 (r'[^\n]\Z', "no trailing newline"),
125 (r'[^\n]\Z', "no trailing newline"),
126 (r'export .*=', "don't export and assign at once"),
126 (r'export .*=', "don't export and assign at once"),
127 (r'^source\b', "don't use 'source', use '.'"),
127 (r'^source\b', "don't use 'source', use '.'"),
128 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
128 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
129 (r'\bls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
129 (r'\bls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
130 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
130 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
131 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
131 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
132 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
132 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
133 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
133 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
134 (r'^alias\b.*=', "don't use alias, use a function"),
134 (r'^alias\b.*=', "don't use alias, use a function"),
135 (r'if\s*!', "don't use '!' to negate exit status"),
135 (r'if\s*!', "don't use '!' to negate exit status"),
136 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
136 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
137 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
137 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
138 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
138 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
139 "put a backslash-escaped newline after sed 'i' command"),
139 "put a backslash-escaped newline after sed 'i' command"),
140 (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"),
140 (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"),
141 (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"),
141 (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"),
142 (r'[\s="`\']python\s(?!bindings)', "don't use 'python', use '$PYTHON'"),
142 (r'[\s="`\']python\s(?!bindings)', "don't use 'python', use '$PYTHON'"),
143 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"),
143 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"),
144 (r'\butil\.Abort\b', "directly use error.Abort"),
144 (r'\butil\.Abort\b', "directly use error.Abort"),
145 (r'\|&', "don't use |&, use 2>&1"),
145 (r'\|&', "don't use |&, use 2>&1"),
146 (r'\w = +\w', "only one space after = allowed"),
146 (r'\w = +\w', "only one space after = allowed"),
147 (r'\bsed\b.*[^\\]\\n', "don't use 'sed ... \\n', use a \\ and a newline"),
147 (r'\bsed\b.*[^\\]\\n', "don't use 'sed ... \\n', use a \\ and a newline"),
148 (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'"),
148 (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'"),
149 (r'cp.* -r ', "don't use 'cp -r', use 'cp -R'"),
149 (r'cp.* -r ', "don't use 'cp -r', use 'cp -R'"),
150 (r'grep.* -[ABC]', "don't use grep's context flags"),
150 (r'grep.* -[ABC]', "don't use grep's context flags"),
151 (r'find.*-printf',
151 (r'find.*-printf',
152 "don't use 'find -printf', it doesn't exist on BSD find(1)"),
152 "don't use 'find -printf', it doesn't exist on BSD find(1)"),
153 ],
153 ],
154 # warnings
154 # warnings
155 [
155 [
156 (r'^function', "don't use 'function', use old style"),
156 (r'^function', "don't use 'function', use old style"),
157 (r'^diff.*-\w*N', "don't use 'diff -N'"),
157 (r'^diff.*-\w*N', "don't use 'diff -N'"),
158 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
158 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
159 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
159 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
160 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
160 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
161 ]
161 ]
162 ]
162 ]
163
163
164 testfilters = [
164 testfilters = [
165 (r"( *)(#([^!][^\n]*\S)?)", repcomment),
165 (r"( *)(#([^!][^\n]*\S)?)", repcomment),
166 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
166 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
167 ]
167 ]
168
168
169 uprefix = r"^ \$ "
169 uprefix = r"^ \$ "
170 utestpats = [
170 utestpats = [
171 [
171 [
172 (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"),
172 (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"),
173 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
173 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
174 "use regex test output patterns instead of sed"),
174 "use regex test output patterns instead of sed"),
175 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
175 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
176 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
176 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
177 (uprefix + r'.*\|\| echo.*(fail|error)',
177 (uprefix + r'.*\|\| echo.*(fail|error)',
178 "explicit exit code checks unnecessary"),
178 "explicit exit code checks unnecessary"),
179 (uprefix + r'set -e', "don't use set -e"),
179 (uprefix + r'set -e', "don't use set -e"),
180 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
180 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
181 (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite "
181 (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite "
182 "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx
182 "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx
183 '# no-msys'), # in test-pull.t which is skipped on windows
183 '# no-msys'), # in test-pull.t which is skipped on windows
184 (r'^ .*file://\$TESTTMP',
185 'write "file:/*/$TESTTMP" + (glob) to match on windows too'),
186 (r'^ [^$>].*27\.0\.0\.1',
184 (r'^ [^$>].*27\.0\.0\.1',
187 'use $LOCALIP not an explicit loopback address'),
185 'use $LOCALIP not an explicit loopback address'),
188 (r'^ (?![>$] ).*\$LOCALIP.*[^)]$',
186 (r'^ (?![>$] ).*\$LOCALIP.*[^)]$',
189 'mark $LOCALIP output lines with (glob) to help tests in BSD jails'),
187 'mark $LOCALIP output lines with (glob) to help tests in BSD jails'),
190 (r'^ (cat|find): .*: No such file or directory',
188 (r'^ (cat|find): .*: No such file or directory',
191 'use test -f to test for file existence'),
189 'use test -f to test for file existence'),
192 (r'^ diff -[^ -]*p',
190 (r'^ diff -[^ -]*p',
193 "don't use (external) diff with -p for portability"),
191 "don't use (external) diff with -p for portability"),
194 (r' readlink ', 'use readlink.py instead of readlink'),
192 (r' readlink ', 'use readlink.py instead of readlink'),
195 (r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
193 (r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
196 "glob timezone field in diff output for portability"),
194 "glob timezone field in diff output for portability"),
197 (r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
195 (r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
198 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability"),
196 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability"),
199 (r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
197 (r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
200 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability"),
198 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability"),
201 (r'^ @@ -[0-9]+ [+][0-9]+ @@',
199 (r'^ @@ -[0-9]+ [+][0-9]+ @@',
202 "use '@@ -N* +N* @@ (glob)' style chunk header for portability"),
200 "use '@@ -N* +N* @@ (glob)' style chunk header for portability"),
203 (uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
201 (uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
204 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
202 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
205 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)"),
203 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)"),
206 ],
204 ],
207 # warnings
205 # warnings
208 [
206 [
209 (r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$',
207 (r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$',
210 "glob match with no glob string (?, *, /, and $LOCALIP)"),
208 "glob match with no glob string (?, *, /, and $LOCALIP)"),
211 ]
209 ]
212 ]
210 ]
213
211
214 # transform plain test rules to unified test's
212 # transform plain test rules to unified test's
215 for i in [0, 1]:
213 for i in [0, 1]:
216 for tp in testpats[i]:
214 for tp in testpats[i]:
217 p = tp[0]
215 p = tp[0]
218 m = tp[1]
216 m = tp[1]
219 if p.startswith(r'^'):
217 if p.startswith(r'^'):
220 p = r"^ [$>] (%s)" % p[1:]
218 p = r"^ [$>] (%s)" % p[1:]
221 else:
219 else:
222 p = r"^ [$>] .*(%s)" % p
220 p = r"^ [$>] .*(%s)" % p
223 utestpats[i].append((p, m) + tp[2:])
221 utestpats[i].append((p, m) + tp[2:])
224
222
225 # don't transform the following rules:
223 # don't transform the following rules:
226 # " > \t" and " \t" should be allowed in unified tests
224 # " > \t" and " \t" should be allowed in unified tests
227 testpats[0].append((r'^( *)\t', "don't use tabs to indent"))
225 testpats[0].append((r'^( *)\t', "don't use tabs to indent"))
228 utestpats[0].append((r'^( ?)\t', "don't use tabs to indent"))
226 utestpats[0].append((r'^( ?)\t', "don't use tabs to indent"))
229
227
230 utestfilters = [
228 utestfilters = [
231 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
229 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
232 (r"( +)(#([^!][^\n]*\S)?)", repcomment),
230 (r"( +)(#([^!][^\n]*\S)?)", repcomment),
233 ]
231 ]
234
232
235 pypats = [
233 pypats = [
236 [
234 [
237 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
235 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
238 "tuple parameter unpacking not available in Python 3+"),
236 "tuple parameter unpacking not available in Python 3+"),
239 (r'lambda\s*\(.*,.*\)',
237 (r'lambda\s*\(.*,.*\)',
240 "tuple parameter unpacking not available in Python 3+"),
238 "tuple parameter unpacking not available in Python 3+"),
241 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
239 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
242 (r'(?<!\.)\breduce\s*\(.*', "reduce is not available in Python 3+"),
240 (r'(?<!\.)\breduce\s*\(.*', "reduce is not available in Python 3+"),
243 (r'\bdict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
241 (r'\bdict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
244 'dict-from-generator'),
242 'dict-from-generator'),
245 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
243 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
246 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
244 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
247 (r'^\s*\t', "don't use tabs"),
245 (r'^\s*\t', "don't use tabs"),
248 (r'\S;\s*\n', "semicolon"),
246 (r'\S;\s*\n', "semicolon"),
249 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
247 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
250 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
248 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
251 (r'(\w|\)),\w', "missing whitespace after ,"),
249 (r'(\w|\)),\w', "missing whitespace after ,"),
252 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
250 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
253 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
251 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
254 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
252 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
255 ((
253 ((
256 # a line ending with a colon, potentially with trailing comments
254 # a line ending with a colon, potentially with trailing comments
257 r':([ \t]*#[^\n]*)?\n'
255 r':([ \t]*#[^\n]*)?\n'
258 # one that is not a pass and not only a comment
256 # one that is not a pass and not only a comment
259 r'(?P<indent>[ \t]+)[^#][^\n]+\n'
257 r'(?P<indent>[ \t]+)[^#][^\n]+\n'
260 # more lines at the same indent level
258 # more lines at the same indent level
261 r'((?P=indent)[^\n]+\n)*'
259 r'((?P=indent)[^\n]+\n)*'
262 # a pass at the same indent level, which is bogus
260 # a pass at the same indent level, which is bogus
263 r'(?P=indent)pass[ \t\n#]'
261 r'(?P=indent)pass[ \t\n#]'
264 ), 'omit superfluous pass'),
262 ), 'omit superfluous pass'),
265 (r'.{81}', "line too long"),
263 (r'.{81}', "line too long"),
266 (r'[^\n]\Z', "no trailing newline"),
264 (r'[^\n]\Z', "no trailing newline"),
267 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
265 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
268 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
266 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
269 # "don't use underbars in identifiers"),
267 # "don't use underbars in identifiers"),
270 (r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ',
268 (r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ',
271 "don't use camelcase in identifiers", r'#.*camelcase-required'),
269 "don't use camelcase in identifiers", r'#.*camelcase-required'),
272 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
270 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
273 "linebreak after :"),
271 "linebreak after :"),
274 (r'class\s[^( \n]+:', "old-style class, use class foo(object)",
272 (r'class\s[^( \n]+:', "old-style class, use class foo(object)",
275 r'#.*old-style'),
273 r'#.*old-style'),
276 (r'class\s[^( \n]+\(\):',
274 (r'class\s[^( \n]+\(\):',
277 "class foo() creates old style object, use class foo(object)",
275 "class foo() creates old style object, use class foo(object)",
278 r'#.*old-style'),
276 r'#.*old-style'),
279 (r'\b(%s)\(' % '|'.join(k for k in keyword.kwlist
277 (r'\b(%s)\(' % '|'.join(k for k in keyword.kwlist
280 if k not in ('print', 'exec')),
278 if k not in ('print', 'exec')),
281 "Python keyword is not a function"),
279 "Python keyword is not a function"),
282 (r',]', "unneeded trailing ',' in list"),
280 (r',]', "unneeded trailing ',' in list"),
283 # (r'class\s[A-Z][^\(]*\((?!Exception)',
281 # (r'class\s[A-Z][^\(]*\((?!Exception)',
284 # "don't capitalize non-exception classes"),
282 # "don't capitalize non-exception classes"),
285 # (r'in range\(', "use xrange"),
283 # (r'in range\(', "use xrange"),
286 # (r'^\s*print\s+', "avoid using print in core and extensions"),
284 # (r'^\s*print\s+', "avoid using print in core and extensions"),
287 (r'[\x80-\xff]', "non-ASCII character literal"),
285 (r'[\x80-\xff]', "non-ASCII character literal"),
288 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
286 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
289 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
287 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
290 "gratuitous whitespace after Python keyword"),
288 "gratuitous whitespace after Python keyword"),
291 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
289 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
292 # (r'\s\s=', "gratuitous whitespace before ="),
290 # (r'\s\s=', "gratuitous whitespace before ="),
293 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
291 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
294 "missing whitespace around operator"),
292 "missing whitespace around operator"),
295 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
293 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
296 "missing whitespace around operator"),
294 "missing whitespace around operator"),
297 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
295 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
298 "missing whitespace around operator"),
296 "missing whitespace around operator"),
299 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
297 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
300 "wrong whitespace around ="),
298 "wrong whitespace around ="),
301 (r'\([^()]*( =[^=]|[^<>!=]= )',
299 (r'\([^()]*( =[^=]|[^<>!=]= )',
302 "no whitespace around = for named parameters"),
300 "no whitespace around = for named parameters"),
303 (r'raise Exception', "don't raise generic exceptions"),
301 (r'raise Exception', "don't raise generic exceptions"),
304 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
302 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
305 "don't use old-style two-argument raise, use Exception(message)"),
303 "don't use old-style two-argument raise, use Exception(message)"),
306 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
304 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
307 (r' [=!]=\s+(True|False|None)',
305 (r' [=!]=\s+(True|False|None)',
308 "comparison with singleton, use 'is' or 'is not' instead"),
306 "comparison with singleton, use 'is' or 'is not' instead"),
309 (r'^\s*(while|if) [01]:',
307 (r'^\s*(while|if) [01]:',
310 "use True/False for constant Boolean expression"),
308 "use True/False for constant Boolean expression"),
311 (r'^\s*if False(:| +and)', 'Remove code instead of using `if False`'),
309 (r'^\s*if False(:| +and)', 'Remove code instead of using `if False`'),
312 (r'(?:(?<!def)\s+|\()hasattr\(',
310 (r'(?:(?<!def)\s+|\()hasattr\(',
313 'hasattr(foo, bar) is broken on py2, use util.safehasattr(foo, bar) '
311 'hasattr(foo, bar) is broken on py2, use util.safehasattr(foo, bar) '
314 'instead', r'#.*hasattr-py3-only'),
312 'instead', r'#.*hasattr-py3-only'),
315 (r'opener\([^)]*\).read\(',
313 (r'opener\([^)]*\).read\(',
316 "use opener.read() instead"),
314 "use opener.read() instead"),
317 (r'opener\([^)]*\).write\(',
315 (r'opener\([^)]*\).write\(',
318 "use opener.write() instead"),
316 "use opener.write() instead"),
319 (r'[\s\(](open|file)\([^)]*\)\.read\(',
317 (r'[\s\(](open|file)\([^)]*\)\.read\(',
320 "use util.readfile() instead"),
318 "use util.readfile() instead"),
321 (r'[\s\(](open|file)\([^)]*\)\.write\(',
319 (r'[\s\(](open|file)\([^)]*\)\.write\(',
322 "use util.writefile() instead"),
320 "use util.writefile() instead"),
323 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
321 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
324 "always assign an opened file to a variable, and close it afterwards"),
322 "always assign an opened file to a variable, and close it afterwards"),
325 (r'[\s\(](open|file)\([^)]*\)\.',
323 (r'[\s\(](open|file)\([^)]*\)\.',
326 "always assign an opened file to a variable, and close it afterwards"),
324 "always assign an opened file to a variable, and close it afterwards"),
327 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
325 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
328 (r'\.debug\(\_', "don't mark debug messages for translation"),
326 (r'\.debug\(\_', "don't mark debug messages for translation"),
329 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
327 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
330 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
328 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
331 (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
329 (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
332 'legacy exception syntax; use "as" instead of ","'),
330 'legacy exception syntax; use "as" instead of ","'),
333 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
331 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
334 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
332 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
335 (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
333 (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
336 (r'os\.path\.join\(.*, *(""|\'\')\)',
334 (r'os\.path\.join\(.*, *(""|\'\')\)',
337 "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
335 "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
338 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
336 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
339 # XXX only catch mutable arguments on the first line of the definition
337 # XXX only catch mutable arguments on the first line of the definition
340 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
338 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
341 (r'\butil\.Abort\b', "directly use error.Abort"),
339 (r'\butil\.Abort\b', "directly use error.Abort"),
342 (r'^@(\w*\.)?cachefunc', "module-level @cachefunc is risky, please avoid"),
340 (r'^@(\w*\.)?cachefunc', "module-level @cachefunc is risky, please avoid"),
343 (r'^import atexit', "don't use atexit, use ui.atexit"),
341 (r'^import atexit', "don't use atexit, use ui.atexit"),
344 (r'^import Queue', "don't use Queue, use util.queue + util.empty"),
342 (r'^import Queue', "don't use Queue, use util.queue + util.empty"),
345 (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
343 (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
346 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
344 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
347 (r'^import SocketServer', "don't use SockerServer, use util.socketserver"),
345 (r'^import SocketServer', "don't use SockerServer, use util.socketserver"),
348 (r'^import urlparse', "don't use urlparse, use util.urlreq"),
346 (r'^import urlparse', "don't use urlparse, use util.urlreq"),
349 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
347 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
350 (r'^import cPickle', "don't use cPickle, use util.pickle"),
348 (r'^import cPickle', "don't use cPickle, use util.pickle"),
351 (r'^import pickle', "don't use pickle, use util.pickle"),
349 (r'^import pickle', "don't use pickle, use util.pickle"),
352 (r'^import httplib', "don't use httplib, use util.httplib"),
350 (r'^import httplib', "don't use httplib, use util.httplib"),
353 (r'^import BaseHTTPServer', "use util.httpserver instead"),
351 (r'^import BaseHTTPServer', "use util.httpserver instead"),
354 (r'^(from|import) mercurial\.(cext|pure|cffi)',
352 (r'^(from|import) mercurial\.(cext|pure|cffi)',
355 "use mercurial.policy.importmod instead"),
353 "use mercurial.policy.importmod instead"),
356 (r'\.next\(\)', "don't use .next(), use next(...)"),
354 (r'\.next\(\)', "don't use .next(), use next(...)"),
357 (r'([a-z]*).revision\(\1\.node\(',
355 (r'([a-z]*).revision\(\1\.node\(',
358 "don't convert rev to node before passing to revision(nodeorrev)"),
356 "don't convert rev to node before passing to revision(nodeorrev)"),
359 (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
357 (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
360
358
361 # rules depending on implementation of repquote()
359 # rules depending on implementation of repquote()
362 (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
360 (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
363 'string join across lines with no space'),
361 'string join across lines with no space'),
364 (r'''(?x)ui\.(status|progress|write|note|warn)\(
362 (r'''(?x)ui\.(status|progress|write|note|warn)\(
365 [ \t\n#]*
363 [ \t\n#]*
366 (?# any strings/comments might precede a string, which
364 (?# any strings/comments might precede a string, which
367 # contains translatable message)
365 # contains translatable message)
368 ((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
366 ((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
369 (?# sequence consisting of below might precede translatable message
367 (?# sequence consisting of below might precede translatable message
370 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
368 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
371 # - escaped character: "\\", "\n", "\0" ...
369 # - escaped character: "\\", "\n", "\0" ...
372 # - character other than '%', 'b' as '\', and 'x' as alphabet)
370 # - character other than '%', 'b' as '\', and 'x' as alphabet)
373 (['"]|\'\'\'|""")
371 (['"]|\'\'\'|""")
374 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
372 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
375 (?# this regexp can't use [^...] style,
373 (?# this regexp can't use [^...] style,
376 # because _preparepats forcibly adds "\n" into [^...],
374 # because _preparepats forcibly adds "\n" into [^...],
377 # even though this regexp wants match it against "\n")''',
375 # even though this regexp wants match it against "\n")''',
378 "missing _() in ui message (use () to hide false-positives)"),
376 "missing _() in ui message (use () to hide false-positives)"),
379 ],
377 ],
380 # warnings
378 # warnings
381 [
379 [
382 # rules depending on implementation of repquote()
380 # rules depending on implementation of repquote()
383 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
381 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
384 ]
382 ]
385 ]
383 ]
386
384
387 pyfilters = [
385 pyfilters = [
388 (r"""(?msx)(?P<comment>\#.*?$)|
386 (r"""(?msx)(?P<comment>\#.*?$)|
389 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
387 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
390 (?P<text>(([^\\]|\\.)*?))
388 (?P<text>(([^\\]|\\.)*?))
391 (?P=quote))""", reppython),
389 (?P=quote))""", reppython),
392 ]
390 ]
393
391
394 # non-filter patterns
392 # non-filter patterns
395 pynfpats = [
393 pynfpats = [
396 [
394 [
397 (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
395 (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
398 (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"),
396 (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"),
399 (r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]',
397 (r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]',
400 "use pycompat.isdarwin"),
398 "use pycompat.isdarwin"),
401 ],
399 ],
402 # warnings
400 # warnings
403 [],
401 [],
404 ]
402 ]
405
403
406 # extension non-filter patterns
404 # extension non-filter patterns
407 pyextnfpats = [
405 pyextnfpats = [
408 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
406 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
409 # warnings
407 # warnings
410 [],
408 [],
411 ]
409 ]
412
410
413 txtfilters = []
411 txtfilters = []
414
412
415 txtpats = [
413 txtpats = [
416 [
414 [
417 ('\s$', 'trailing whitespace'),
415 ('\s$', 'trailing whitespace'),
418 ('.. note::[ \n][^\n]', 'add two newlines after note::')
416 ('.. note::[ \n][^\n]', 'add two newlines after note::')
419 ],
417 ],
420 []
418 []
421 ]
419 ]
422
420
423 cpats = [
421 cpats = [
424 [
422 [
425 (r'//', "don't use //-style comments"),
423 (r'//', "don't use //-style comments"),
426 (r'\S\t', "don't use tabs except for indent"),
424 (r'\S\t', "don't use tabs except for indent"),
427 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
425 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
428 (r'.{81}', "line too long"),
426 (r'.{81}', "line too long"),
429 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
427 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
430 (r'return\(', "return is not a function"),
428 (r'return\(', "return is not a function"),
431 (r' ;', "no space before ;"),
429 (r' ;', "no space before ;"),
432 (r'[^;] \)', "no space before )"),
430 (r'[^;] \)', "no space before )"),
433 (r'[)][{]', "space between ) and {"),
431 (r'[)][{]', "space between ) and {"),
434 (r'\w+\* \w+', "use int *foo, not int* foo"),
432 (r'\w+\* \w+', "use int *foo, not int* foo"),
435 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
433 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
436 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
434 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
437 (r'\w,\w', "missing whitespace after ,"),
435 (r'\w,\w', "missing whitespace after ,"),
438 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
436 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
439 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
437 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
440 (r'^#\s+\w', "use #foo, not # foo"),
438 (r'^#\s+\w', "use #foo, not # foo"),
441 (r'[^\n]\Z', "no trailing newline"),
439 (r'[^\n]\Z', "no trailing newline"),
442 (r'^\s*#import\b', "use only #include in standard C code"),
440 (r'^\s*#import\b', "use only #include in standard C code"),
443 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
441 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
444 (r'strcat\(', "don't use strcat"),
442 (r'strcat\(', "don't use strcat"),
445
443
446 # rules depending on implementation of repquote()
444 # rules depending on implementation of repquote()
447 ],
445 ],
448 # warnings
446 # warnings
449 [
447 [
450 # rules depending on implementation of repquote()
448 # rules depending on implementation of repquote()
451 ]
449 ]
452 ]
450 ]
453
451
454 cfilters = [
452 cfilters = [
455 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
453 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
456 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
454 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
457 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
455 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
458 (r'(\()([^)]+\))', repcallspaces),
456 (r'(\()([^)]+\))', repcallspaces),
459 ]
457 ]
460
458
461 inutilpats = [
459 inutilpats = [
462 [
460 [
463 (r'\bui\.', "don't use ui in util"),
461 (r'\bui\.', "don't use ui in util"),
464 ],
462 ],
465 # warnings
463 # warnings
466 []
464 []
467 ]
465 ]
468
466
469 inrevlogpats = [
467 inrevlogpats = [
470 [
468 [
471 (r'\brepo\.', "don't use repo in revlog"),
469 (r'\brepo\.', "don't use repo in revlog"),
472 ],
470 ],
473 # warnings
471 # warnings
474 []
472 []
475 ]
473 ]
476
474
477 webtemplatefilters = []
475 webtemplatefilters = []
478
476
479 webtemplatepats = [
477 webtemplatepats = [
480 [],
478 [],
481 [
479 [
482 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
480 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
483 'follow desc keyword with either firstline or websub'),
481 'follow desc keyword with either firstline or websub'),
484 ]
482 ]
485 ]
483 ]
486
484
487 allfilesfilters = []
485 allfilesfilters = []
488
486
489 allfilespats = [
487 allfilespats = [
490 [
488 [
491 (r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
489 (r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
492 'use mercurial-scm.org domain URL'),
490 'use mercurial-scm.org domain URL'),
493 (r'mercurial@selenic\.com',
491 (r'mercurial@selenic\.com',
494 'use mercurial-scm.org domain for mercurial ML address'),
492 'use mercurial-scm.org domain for mercurial ML address'),
495 (r'mercurial-devel@selenic\.com',
493 (r'mercurial-devel@selenic\.com',
496 'use mercurial-scm.org domain for mercurial-devel ML address'),
494 'use mercurial-scm.org domain for mercurial-devel ML address'),
497 ],
495 ],
498 # warnings
496 # warnings
499 [],
497 [],
500 ]
498 ]
501
499
502 py3pats = [
500 py3pats = [
503 [
501 [
504 (r'os\.environ', "use encoding.environ instead (py3)", r'#.*re-exports'),
502 (r'os\.environ', "use encoding.environ instead (py3)", r'#.*re-exports'),
505 (r'os\.name', "use pycompat.osname instead (py3)"),
503 (r'os\.name', "use pycompat.osname instead (py3)"),
506 (r'os\.getcwd', "use pycompat.getcwd instead (py3)"),
504 (r'os\.getcwd', "use pycompat.getcwd instead (py3)"),
507 (r'os\.sep', "use pycompat.ossep instead (py3)"),
505 (r'os\.sep', "use pycompat.ossep instead (py3)"),
508 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
506 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
509 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
507 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
510 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
508 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
511 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
509 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
512 (r'os\.getenv', "use encoding.environ.get instead"),
510 (r'os\.getenv', "use encoding.environ.get instead"),
513 (r'os\.setenv', "modifying the environ dict is not preferred"),
511 (r'os\.setenv', "modifying the environ dict is not preferred"),
514 ],
512 ],
515 # warnings
513 # warnings
516 [],
514 [],
517 ]
515 ]
518
516
519 checks = [
517 checks = [
520 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
518 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
521 ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats),
519 ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats),
522 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
520 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
523 ('python 3', r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
521 ('python 3', r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
524 '', pyfilters, py3pats),
522 '', pyfilters, py3pats),
525 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
523 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
526 ('c', r'.*\.[ch]$', '', cfilters, cpats),
524 ('c', r'.*\.[ch]$', '', cfilters, cpats),
527 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
525 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
528 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
526 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
529 pyfilters, inrevlogpats),
527 pyfilters, inrevlogpats),
530 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
528 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
531 inutilpats),
529 inutilpats),
532 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
530 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
533 ('web template', r'mercurial/templates/.*\.tmpl', '',
531 ('web template', r'mercurial/templates/.*\.tmpl', '',
534 webtemplatefilters, webtemplatepats),
532 webtemplatefilters, webtemplatepats),
535 ('all except for .po', r'.*(?<!\.po)$', '',
533 ('all except for .po', r'.*(?<!\.po)$', '',
536 allfilesfilters, allfilespats),
534 allfilesfilters, allfilespats),
537 ]
535 ]
538
536
539 def _preparepats():
537 def _preparepats():
540 for c in checks:
538 for c in checks:
541 failandwarn = c[-1]
539 failandwarn = c[-1]
542 for pats in failandwarn:
540 for pats in failandwarn:
543 for i, pseq in enumerate(pats):
541 for i, pseq in enumerate(pats):
544 # fix-up regexes for multi-line searches
542 # fix-up regexes for multi-line searches
545 p = pseq[0]
543 p = pseq[0]
546 # \s doesn't match \n
544 # \s doesn't match \n
547 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
545 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
548 # [^...] doesn't match newline
546 # [^...] doesn't match newline
549 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
547 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
550
548
551 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
549 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
552 filters = c[3]
550 filters = c[3]
553 for i, flt in enumerate(filters):
551 for i, flt in enumerate(filters):
554 filters[i] = re.compile(flt[0]), flt[1]
552 filters[i] = re.compile(flt[0]), flt[1]
555
553
556 class norepeatlogger(object):
554 class norepeatlogger(object):
557 def __init__(self):
555 def __init__(self):
558 self._lastseen = None
556 self._lastseen = None
559
557
560 def log(self, fname, lineno, line, msg, blame):
558 def log(self, fname, lineno, line, msg, blame):
561 """print error related a to given line of a given file.
559 """print error related a to given line of a given file.
562
560
563 The faulty line will also be printed but only once in the case
561 The faulty line will also be printed but only once in the case
564 of multiple errors.
562 of multiple errors.
565
563
566 :fname: filename
564 :fname: filename
567 :lineno: line number
565 :lineno: line number
568 :line: actual content of the line
566 :line: actual content of the line
569 :msg: error message
567 :msg: error message
570 """
568 """
571 msgid = fname, lineno, line
569 msgid = fname, lineno, line
572 if msgid != self._lastseen:
570 if msgid != self._lastseen:
573 if blame:
571 if blame:
574 print("%s:%d (%s):" % (fname, lineno, blame))
572 print("%s:%d (%s):" % (fname, lineno, blame))
575 else:
573 else:
576 print("%s:%d:" % (fname, lineno))
574 print("%s:%d:" % (fname, lineno))
577 print(" > %s" % line)
575 print(" > %s" % line)
578 self._lastseen = msgid
576 self._lastseen = msgid
579 print(" " + msg)
577 print(" " + msg)
580
578
581 _defaultlogger = norepeatlogger()
579 _defaultlogger = norepeatlogger()
582
580
583 def getblame(f):
581 def getblame(f):
584 lines = []
582 lines = []
585 for l in os.popen('hg annotate -un %s' % f):
583 for l in os.popen('hg annotate -un %s' % f):
586 start, line = l.split(':', 1)
584 start, line = l.split(':', 1)
587 user, rev = start.split()
585 user, rev = start.split()
588 lines.append((line[1:-1], user, rev))
586 lines.append((line[1:-1], user, rev))
589 return lines
587 return lines
590
588
591 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
589 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
592 blame=False, debug=False, lineno=True):
590 blame=False, debug=False, lineno=True):
593 """checks style and portability of a given file
591 """checks style and portability of a given file
594
592
595 :f: filepath
593 :f: filepath
596 :logfunc: function used to report error
594 :logfunc: function used to report error
597 logfunc(filename, linenumber, linecontent, errormessage)
595 logfunc(filename, linenumber, linecontent, errormessage)
598 :maxerr: number of error to display before aborting.
596 :maxerr: number of error to display before aborting.
599 Set to false (default) to report all errors
597 Set to false (default) to report all errors
600
598
601 return True if no error is found, False otherwise.
599 return True if no error is found, False otherwise.
602 """
600 """
603 blamecache = None
601 blamecache = None
604 result = True
602 result = True
605
603
606 try:
604 try:
607 with opentext(f) as fp:
605 with opentext(f) as fp:
608 try:
606 try:
609 pre = post = fp.read()
607 pre = post = fp.read()
610 except UnicodeDecodeError as e:
608 except UnicodeDecodeError as e:
611 print("%s while reading %s" % (e, f))
609 print("%s while reading %s" % (e, f))
612 return result
610 return result
613 except IOError as e:
611 except IOError as e:
614 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
612 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
615 return result
613 return result
616
614
617 for name, match, magic, filters, pats in checks:
615 for name, match, magic, filters, pats in checks:
618 post = pre # discard filtering result of previous check
616 post = pre # discard filtering result of previous check
619 if debug:
617 if debug:
620 print(name, f)
618 print(name, f)
621 fc = 0
619 fc = 0
622 if not (re.match(match, f) or (magic and re.search(magic, pre))):
620 if not (re.match(match, f) or (magic and re.search(magic, pre))):
623 if debug:
621 if debug:
624 print("Skipping %s for %s it doesn't match %s" % (
622 print("Skipping %s for %s it doesn't match %s" % (
625 name, match, f))
623 name, match, f))
626 continue
624 continue
627 if "no-" "check-code" in pre:
625 if "no-" "check-code" in pre:
628 # If you're looking at this line, it's because a file has:
626 # If you're looking at this line, it's because a file has:
629 # no- check- code
627 # no- check- code
630 # but the reason to output skipping is to make life for
628 # but the reason to output skipping is to make life for
631 # tests easier. So, instead of writing it with a normal
629 # tests easier. So, instead of writing it with a normal
632 # spelling, we write it with the expected spelling from
630 # spelling, we write it with the expected spelling from
633 # tests/test-check-code.t
631 # tests/test-check-code.t
634 print("Skipping %s it has no-che?k-code (glob)" % f)
632 print("Skipping %s it has no-che?k-code (glob)" % f)
635 return "Skip" # skip checking this file
633 return "Skip" # skip checking this file
636 for p, r in filters:
634 for p, r in filters:
637 post = re.sub(p, r, post)
635 post = re.sub(p, r, post)
638 nerrs = len(pats[0]) # nerr elements are errors
636 nerrs = len(pats[0]) # nerr elements are errors
639 if warnings:
637 if warnings:
640 pats = pats[0] + pats[1]
638 pats = pats[0] + pats[1]
641 else:
639 else:
642 pats = pats[0]
640 pats = pats[0]
643 # print post # uncomment to show filtered version
641 # print post # uncomment to show filtered version
644
642
645 if debug:
643 if debug:
646 print("Checking %s for %s" % (name, f))
644 print("Checking %s for %s" % (name, f))
647
645
648 prelines = None
646 prelines = None
649 errors = []
647 errors = []
650 for i, pat in enumerate(pats):
648 for i, pat in enumerate(pats):
651 if len(pat) == 3:
649 if len(pat) == 3:
652 p, msg, ignore = pat
650 p, msg, ignore = pat
653 else:
651 else:
654 p, msg = pat
652 p, msg = pat
655 ignore = None
653 ignore = None
656 if i >= nerrs:
654 if i >= nerrs:
657 msg = "warning: " + msg
655 msg = "warning: " + msg
658
656
659 pos = 0
657 pos = 0
660 n = 0
658 n = 0
661 for m in p.finditer(post):
659 for m in p.finditer(post):
662 if prelines is None:
660 if prelines is None:
663 prelines = pre.splitlines()
661 prelines = pre.splitlines()
664 postlines = post.splitlines(True)
662 postlines = post.splitlines(True)
665
663
666 start = m.start()
664 start = m.start()
667 while n < len(postlines):
665 while n < len(postlines):
668 step = len(postlines[n])
666 step = len(postlines[n])
669 if pos + step > start:
667 if pos + step > start:
670 break
668 break
671 pos += step
669 pos += step
672 n += 1
670 n += 1
673 l = prelines[n]
671 l = prelines[n]
674
672
675 if ignore and re.search(ignore, l, re.MULTILINE):
673 if ignore and re.search(ignore, l, re.MULTILINE):
676 if debug:
674 if debug:
677 print("Skipping %s for %s:%s (ignore pattern)" % (
675 print("Skipping %s for %s:%s (ignore pattern)" % (
678 name, f, n))
676 name, f, n))
679 continue
677 continue
680 bd = ""
678 bd = ""
681 if blame:
679 if blame:
682 bd = 'working directory'
680 bd = 'working directory'
683 if not blamecache:
681 if not blamecache:
684 blamecache = getblame(f)
682 blamecache = getblame(f)
685 if n < len(blamecache):
683 if n < len(blamecache):
686 bl, bu, br = blamecache[n]
684 bl, bu, br = blamecache[n]
687 if bl == l:
685 if bl == l:
688 bd = '%s@%s' % (bu, br)
686 bd = '%s@%s' % (bu, br)
689
687
690 errors.append((f, lineno and n + 1, l, msg, bd))
688 errors.append((f, lineno and n + 1, l, msg, bd))
691 result = False
689 result = False
692
690
693 errors.sort()
691 errors.sort()
694 for e in errors:
692 for e in errors:
695 logfunc(*e)
693 logfunc(*e)
696 fc += 1
694 fc += 1
697 if maxerr and fc >= maxerr:
695 if maxerr and fc >= maxerr:
698 print(" (too many errors, giving up)")
696 print(" (too many errors, giving up)")
699 break
697 break
700
698
701 return result
699 return result
702
700
703 def main():
701 def main():
704 parser = optparse.OptionParser("%prog [options] [files | -]")
702 parser = optparse.OptionParser("%prog [options] [files | -]")
705 parser.add_option("-w", "--warnings", action="store_true",
703 parser.add_option("-w", "--warnings", action="store_true",
706 help="include warning-level checks")
704 help="include warning-level checks")
707 parser.add_option("-p", "--per-file", type="int",
705 parser.add_option("-p", "--per-file", type="int",
708 help="max warnings per file")
706 help="max warnings per file")
709 parser.add_option("-b", "--blame", action="store_true",
707 parser.add_option("-b", "--blame", action="store_true",
710 help="use annotate to generate blame info")
708 help="use annotate to generate blame info")
711 parser.add_option("", "--debug", action="store_true",
709 parser.add_option("", "--debug", action="store_true",
712 help="show debug information")
710 help="show debug information")
713 parser.add_option("", "--nolineno", action="store_false",
711 parser.add_option("", "--nolineno", action="store_false",
714 dest='lineno', help="don't show line numbers")
712 dest='lineno', help="don't show line numbers")
715
713
716 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
714 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
717 lineno=True)
715 lineno=True)
718 (options, args) = parser.parse_args()
716 (options, args) = parser.parse_args()
719
717
720 if len(args) == 0:
718 if len(args) == 0:
721 check = glob.glob("*")
719 check = glob.glob("*")
722 elif args == ['-']:
720 elif args == ['-']:
723 # read file list from stdin
721 # read file list from stdin
724 check = sys.stdin.read().splitlines()
722 check = sys.stdin.read().splitlines()
725 else:
723 else:
726 check = args
724 check = args
727
725
728 _preparepats()
726 _preparepats()
729
727
730 ret = 0
728 ret = 0
731 for f in check:
729 for f in check:
732 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
730 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
733 blame=options.blame, debug=options.debug,
731 blame=options.blame, debug=options.debug,
734 lineno=options.lineno):
732 lineno=options.lineno):
735 ret = 1
733 ret = 1
736 return ret
734 return ret
737
735
738 if __name__ == "__main__":
736 if __name__ == "__main__":
739 sys.exit(main())
737 sys.exit(main())
@@ -1,77 +1,85 b''
1 # common patterns in test at can safely be replaced
1 # common patterns in test at can safely be replaced
2 from __future__ import absolute_import
2 from __future__ import absolute_import
3
3
4 substitutions = [
4 substitutions = [
5 # list of possible compressions
5 # list of possible compressions
6 (br'(zstd,)?zlib,none,bzip2',
6 (br'(zstd,)?zlib,none,bzip2',
7 br'$USUAL_COMPRESSIONS$'
7 br'$USUAL_COMPRESSIONS$'
8 ),
8 ),
9 # capabilities sent through http
9 # capabilities sent through http
10 (br'bundlecaps=HG20%2Cbundle2%3DHG20%250A'
10 (br'bundlecaps=HG20%2Cbundle2%3DHG20%250A'
11 br'bookmarks%250A'
11 br'bookmarks%250A'
12 br'changegroup%253D01%252C02%250A'
12 br'changegroup%253D01%252C02%250A'
13 br'digests%253Dmd5%252Csha1%252Csha512%250A'
13 br'digests%253Dmd5%252Csha1%252Csha512%250A'
14 br'error%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250A'
14 br'error%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250A'
15 br'hgtagsfnodes%250A'
15 br'hgtagsfnodes%250A'
16 br'listkeys%250A'
16 br'listkeys%250A'
17 br'phases%253Dheads%250A'
17 br'phases%253Dheads%250A'
18 br'pushkey%250A'
18 br'pushkey%250A'
19 br'remote-changegroup%253Dhttp%252Chttps',
19 br'remote-changegroup%253Dhttp%252Chttps',
20 # (the replacement patterns)
20 # (the replacement patterns)
21 br'$USUAL_BUNDLE_CAPS$'
21 br'$USUAL_BUNDLE_CAPS$'
22 ),
22 ),
23 # bundle2 capabilities sent through ssh
23 # bundle2 capabilities sent through ssh
24 (br'bundle2=HG20%0A'
24 (br'bundle2=HG20%0A'
25 br'bookmarks%0A'
25 br'bookmarks%0A'
26 br'changegroup%3D01%2C02%0A'
26 br'changegroup%3D01%2C02%0A'
27 br'digests%3Dmd5%2Csha1%2Csha512%0A'
27 br'digests%3Dmd5%2Csha1%2Csha512%0A'
28 br'error%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0A'
28 br'error%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0A'
29 br'hgtagsfnodes%0A'
29 br'hgtagsfnodes%0A'
30 br'listkeys%0A'
30 br'listkeys%0A'
31 br'phases%3Dheads%0A'
31 br'phases%3Dheads%0A'
32 br'pushkey%0A'
32 br'pushkey%0A'
33 br'remote-changegroup%3Dhttp%2Chttps',
33 br'remote-changegroup%3Dhttp%2Chttps',
34 # (replacement patterns)
34 # (replacement patterns)
35 br'$USUAL_BUNDLE2_CAPS$'
35 br'$USUAL_BUNDLE2_CAPS$'
36 ),
36 ),
37 # HTTP log dates
37 # HTTP log dates
38 (br' - - \[\d\d/.../2\d\d\d \d\d:\d\d:\d\d] "GET',
38 (br' - - \[\d\d/.../2\d\d\d \d\d:\d\d:\d\d] "GET',
39 br' - - [$LOGDATE$] "GET'
39 br' - - [$LOGDATE$] "GET'
40 ),
40 ),
41 # Windows has an extra '/' in the following lines that get globbed away:
42 # pushing to file:/*/$TESTTMP/r2 (glob)
43 # comparing with file:/*/$TESTTMP/r2 (glob)
44 # sub/maybelarge.dat: largefile 34..9c not available from
45 # file:/*/$TESTTMP/largefiles-repo (glob)
46 (br'(.*file:/)/?(/\$TESTTMP.*)',
47 lambda m: m.group(1) + b'*' + m.group(2) + b' (glob)'
48 ),
41 ]
49 ]
42
50
43 # Various platform error strings, keyed on a common replacement string
51 # Various platform error strings, keyed on a common replacement string
44 _errors = {
52 _errors = {
45 br'$ENOENT$': (
53 br'$ENOENT$': (
46 # strerror()
54 # strerror()
47 br'No such file or directory',
55 br'No such file or directory',
48
56
49 # FormatMessage(ERROR_FILE_NOT_FOUND)
57 # FormatMessage(ERROR_FILE_NOT_FOUND)
50 br'The system cannot find the file specified',
58 br'The system cannot find the file specified',
51 ),
59 ),
52 br'$ENOTDIR$': (
60 br'$ENOTDIR$': (
53 # strerror()
61 # strerror()
54 br'Not a directory',
62 br'Not a directory',
55
63
56 # FormatMessage(ERROR_PATH_NOT_FOUND)
64 # FormatMessage(ERROR_PATH_NOT_FOUND)
57 br'The system cannot find the path specified',
65 br'The system cannot find the path specified',
58 ),
66 ),
59 br'$ECONNRESET$': (
67 br'$ECONNRESET$': (
60 # strerror()
68 # strerror()
61 br'Connection reset by peer',
69 br'Connection reset by peer',
62
70
63 # FormatMessage(WSAECONNRESET)
71 # FormatMessage(WSAECONNRESET)
64 br'An existing connection was forcibly closed by the remote host',
72 br'An existing connection was forcibly closed by the remote host',
65 ),
73 ),
66 br'$EADDRINUSE$': (
74 br'$EADDRINUSE$': (
67 # strerror()
75 # strerror()
68 br'Address already in use',
76 br'Address already in use',
69
77
70 # FormatMessage(WSAEADDRINUSE)
78 # FormatMessage(WSAEADDRINUSE)
71 br'Only one usage of each socket address'
79 br'Only one usage of each socket address'
72 br' \(protocol/network address/port\) is normally permitted',
80 br' \(protocol/network address/port\) is normally permitted',
73 ),
81 ),
74 }
82 }
75
83
76 for replace, msgs in _errors.items():
84 for replace, msgs in _errors.items():
77 substitutions.extend((m, replace) for m in msgs)
85 substitutions.extend((m, replace) for m in msgs)
@@ -1,690 +1,690 b''
1 #require svn15
1 #require svn15
2
2
3 $ SVNREPOPATH=`pwd`/svn-repo
3 $ SVNREPOPATH=`pwd`/svn-repo
4 #if windows
4 #if windows
5 $ SVNREPOURL=file:///`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
5 $ SVNREPOURL=file:///`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
6 #else
6 #else
7 $ SVNREPOURL=file://`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
7 $ SVNREPOURL=file://`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
8 #endif
8 #endif
9
9
10 $ filter_svn_output () {
10 $ filter_svn_output () {
11 > egrep -v 'Committing|Transmitting|Updating|(^$)' || true
11 > egrep -v 'Committing|Transmitting|Updating|(^$)' || true
12 > }
12 > }
13
13
14 create subversion repo
14 create subversion repo
15
15
16 $ WCROOT="`pwd`/svn-wc"
16 $ WCROOT="`pwd`/svn-wc"
17 $ svnadmin create svn-repo
17 $ svnadmin create svn-repo
18 $ svn co "$SVNREPOURL" svn-wc
18 $ svn co "$SVNREPOURL" svn-wc
19 Checked out revision 0.
19 Checked out revision 0.
20 $ cd svn-wc
20 $ cd svn-wc
21 $ mkdir src
21 $ mkdir src
22 $ echo alpha > src/alpha
22 $ echo alpha > src/alpha
23 $ svn add src
23 $ svn add src
24 A src
24 A src
25 A src/alpha
25 A src/alpha
26 $ mkdir externals
26 $ mkdir externals
27 $ echo other > externals/other
27 $ echo other > externals/other
28 $ svn add externals
28 $ svn add externals
29 A externals
29 A externals
30 A externals/other
30 A externals/other
31 $ svn ci -qm 'Add alpha'
31 $ svn ci -qm 'Add alpha'
32 $ svn up -q
32 $ svn up -q
33 $ echo "externals -r1 $SVNREPOURL/externals" > extdef
33 $ echo "externals -r1 $SVNREPOURL/externals" > extdef
34 $ svn propset -F extdef svn:externals src
34 $ svn propset -F extdef svn:externals src
35 property 'svn:externals' set on 'src'
35 property 'svn:externals' set on 'src'
36 $ svn ci -qm 'Setting externals'
36 $ svn ci -qm 'Setting externals'
37 $ cd ..
37 $ cd ..
38
38
39 create hg repo
39 create hg repo
40
40
41 $ mkdir sub
41 $ mkdir sub
42 $ cd sub
42 $ cd sub
43 $ hg init t
43 $ hg init t
44 $ cd t
44 $ cd t
45
45
46 first revision, no sub
46 first revision, no sub
47
47
48 $ echo a > a
48 $ echo a > a
49 $ hg ci -Am0
49 $ hg ci -Am0
50 adding a
50 adding a
51
51
52 add first svn sub with leading whitespaces
52 add first svn sub with leading whitespaces
53
53
54 $ echo "s = [svn] $SVNREPOURL/src" >> .hgsub
54 $ echo "s = [svn] $SVNREPOURL/src" >> .hgsub
55 $ echo "subdir/s = [svn] $SVNREPOURL/src" >> .hgsub
55 $ echo "subdir/s = [svn] $SVNREPOURL/src" >> .hgsub
56 $ svn co --quiet "$SVNREPOURL"/src s
56 $ svn co --quiet "$SVNREPOURL"/src s
57 $ mkdir subdir
57 $ mkdir subdir
58 $ svn co --quiet "$SVNREPOURL"/src subdir/s
58 $ svn co --quiet "$SVNREPOURL"/src subdir/s
59 $ hg add .hgsub
59 $ hg add .hgsub
60
60
61 svn subrepo is disabled by default
61 svn subrepo is disabled by default
62
62
63 $ hg ci -m1
63 $ hg ci -m1
64 abort: svn subrepos not allowed
64 abort: svn subrepos not allowed
65 (see 'hg help config.subrepos' for details)
65 (see 'hg help config.subrepos' for details)
66 [255]
66 [255]
67
67
68 so enable it
68 so enable it
69
69
70 $ cat >> $HGRCPATH <<EOF
70 $ cat >> $HGRCPATH <<EOF
71 > [subrepos]
71 > [subrepos]
72 > svn:allowed = true
72 > svn:allowed = true
73 > EOF
73 > EOF
74
74
75 $ hg ci -m1
75 $ hg ci -m1
76
76
77 make sure we avoid empty commits (issue2445)
77 make sure we avoid empty commits (issue2445)
78
78
79 $ hg sum
79 $ hg sum
80 parent: 1:* tip (glob)
80 parent: 1:* tip (glob)
81 1
81 1
82 branch: default
82 branch: default
83 commit: (clean)
83 commit: (clean)
84 update: (current)
84 update: (current)
85 phases: 2 draft
85 phases: 2 draft
86 $ hg ci -moops
86 $ hg ci -moops
87 nothing changed
87 nothing changed
88 [1]
88 [1]
89
89
90 debugsub
90 debugsub
91
91
92 $ hg debugsub
92 $ hg debugsub
93 path s
93 path s
94 source file://*/svn-repo/src (glob)
94 source file:/*/$TESTTMP/svn-repo/src (glob)
95 revision 2
95 revision 2
96 path subdir/s
96 path subdir/s
97 source file://*/svn-repo/src (glob)
97 source file:/*/$TESTTMP/svn-repo/src (glob)
98 revision 2
98 revision 2
99
99
100 change file in svn and hg, commit
100 change file in svn and hg, commit
101
101
102 $ echo a >> a
102 $ echo a >> a
103 $ echo alpha >> s/alpha
103 $ echo alpha >> s/alpha
104 $ hg sum
104 $ hg sum
105 parent: 1:* tip (glob)
105 parent: 1:* tip (glob)
106 1
106 1
107 branch: default
107 branch: default
108 commit: 1 modified, 1 subrepos
108 commit: 1 modified, 1 subrepos
109 update: (current)
109 update: (current)
110 phases: 2 draft
110 phases: 2 draft
111 $ hg commit --subrepos -m 'Message!' | filter_svn_output
111 $ hg commit --subrepos -m 'Message!' | filter_svn_output
112 committing subrepository s
112 committing subrepository s
113 Sending*s/alpha (glob)
113 Sending*s/alpha (glob)
114 Committed revision 3.
114 Committed revision 3.
115 Fetching external item into '*s/externals'* (glob)
115 Fetching external item into '*s/externals'* (glob)
116 External at revision 1.
116 External at revision 1.
117 At revision 3.
117 At revision 3.
118 $ hg debugsub
118 $ hg debugsub
119 path s
119 path s
120 source file://*/svn-repo/src (glob)
120 source file:/*/$TESTTMP/svn-repo/src (glob)
121 revision 3
121 revision 3
122 path subdir/s
122 path subdir/s
123 source file://*/svn-repo/src (glob)
123 source file:/*/$TESTTMP/svn-repo/src (glob)
124 revision 2
124 revision 2
125
125
126 missing svn file, commit should fail
126 missing svn file, commit should fail
127
127
128 $ rm s/alpha
128 $ rm s/alpha
129 $ hg commit --subrepos -m 'abort on missing file'
129 $ hg commit --subrepos -m 'abort on missing file'
130 committing subrepository s
130 committing subrepository s
131 abort: cannot commit missing svn entries (in subrepository "s")
131 abort: cannot commit missing svn entries (in subrepository "s")
132 [255]
132 [255]
133 $ svn revert s/alpha > /dev/null
133 $ svn revert s/alpha > /dev/null
134
134
135 add an unrelated revision in svn and update the subrepo to without
135 add an unrelated revision in svn and update the subrepo to without
136 bringing any changes.
136 bringing any changes.
137
137
138 $ svn mkdir "$SVNREPOURL/unrelated" -qm 'create unrelated'
138 $ svn mkdir "$SVNREPOURL/unrelated" -qm 'create unrelated'
139 $ svn up -q s
139 $ svn up -q s
140 $ hg sum
140 $ hg sum
141 parent: 2:* tip (glob)
141 parent: 2:* tip (glob)
142 Message!
142 Message!
143 branch: default
143 branch: default
144 commit: (clean)
144 commit: (clean)
145 update: (current)
145 update: (current)
146 phases: 3 draft
146 phases: 3 draft
147
147
148 $ echo a > s/a
148 $ echo a > s/a
149
149
150 should be empty despite change to s/a
150 should be empty despite change to s/a
151
151
152 $ hg st
152 $ hg st
153
153
154 add a commit from svn
154 add a commit from svn
155
155
156 $ cd "$WCROOT/src"
156 $ cd "$WCROOT/src"
157 $ svn up -q
157 $ svn up -q
158 $ echo xyz >> alpha
158 $ echo xyz >> alpha
159 $ svn propset svn:mime-type 'text/xml' alpha
159 $ svn propset svn:mime-type 'text/xml' alpha
160 property 'svn:mime-type' set on 'alpha'
160 property 'svn:mime-type' set on 'alpha'
161 $ svn ci -qm 'amend a from svn'
161 $ svn ci -qm 'amend a from svn'
162 $ cd ../../sub/t
162 $ cd ../../sub/t
163
163
164 this commit from hg will fail
164 this commit from hg will fail
165
165
166 $ echo zzz >> s/alpha
166 $ echo zzz >> s/alpha
167 $ (hg ci --subrepos -m 'amend alpha from hg' 2>&1; echo "[$?]") | grep -vi 'out of date'
167 $ (hg ci --subrepos -m 'amend alpha from hg' 2>&1; echo "[$?]") | grep -vi 'out of date'
168 committing subrepository s
168 committing subrepository s
169 abort: svn:*Commit failed (details follow): (glob)
169 abort: svn:*Commit failed (details follow): (glob)
170 [255]
170 [255]
171 $ svn revert -q s/alpha
171 $ svn revert -q s/alpha
172
172
173 this commit fails because of meta changes
173 this commit fails because of meta changes
174
174
175 $ svn propset svn:mime-type 'text/html' s/alpha
175 $ svn propset svn:mime-type 'text/html' s/alpha
176 property 'svn:mime-type' set on 's/alpha'
176 property 'svn:mime-type' set on 's/alpha'
177 $ (hg ci --subrepos -m 'amend alpha from hg' 2>&1; echo "[$?]") | grep -vi 'out of date'
177 $ (hg ci --subrepos -m 'amend alpha from hg' 2>&1; echo "[$?]") | grep -vi 'out of date'
178 committing subrepository s
178 committing subrepository s
179 abort: svn:*Commit failed (details follow): (glob)
179 abort: svn:*Commit failed (details follow): (glob)
180 [255]
180 [255]
181 $ svn revert -q s/alpha
181 $ svn revert -q s/alpha
182
182
183 this commit fails because of externals changes
183 this commit fails because of externals changes
184
184
185 $ echo zzz > s/externals/other
185 $ echo zzz > s/externals/other
186 $ hg ci --subrepos -m 'amend externals from hg'
186 $ hg ci --subrepos -m 'amend externals from hg'
187 committing subrepository s
187 committing subrepository s
188 abort: cannot commit svn externals (in subrepository "s")
188 abort: cannot commit svn externals (in subrepository "s")
189 [255]
189 [255]
190 $ hg diff --subrepos -r 1:2 | grep -v diff
190 $ hg diff --subrepos -r 1:2 | grep -v diff
191 --- a/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
191 --- a/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
192 +++ b/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
192 +++ b/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
193 @@ -1,2 +1,2 @@
193 @@ -1,2 +1,2 @@
194 -2 s
194 -2 s
195 +3 s
195 +3 s
196 2 subdir/s
196 2 subdir/s
197 --- a/a Thu Jan 01 00:00:00 1970 +0000
197 --- a/a Thu Jan 01 00:00:00 1970 +0000
198 +++ b/a Thu Jan 01 00:00:00 1970 +0000
198 +++ b/a Thu Jan 01 00:00:00 1970 +0000
199 @@ -1,1 +1,2 @@
199 @@ -1,1 +1,2 @@
200 a
200 a
201 +a
201 +a
202 $ svn revert -q s/externals/other
202 $ svn revert -q s/externals/other
203
203
204 this commit fails because of externals meta changes
204 this commit fails because of externals meta changes
205
205
206 $ svn propset svn:mime-type 'text/html' s/externals/other
206 $ svn propset svn:mime-type 'text/html' s/externals/other
207 property 'svn:mime-type' set on 's/externals/other'
207 property 'svn:mime-type' set on 's/externals/other'
208 $ hg ci --subrepos -m 'amend externals from hg'
208 $ hg ci --subrepos -m 'amend externals from hg'
209 committing subrepository s
209 committing subrepository s
210 abort: cannot commit svn externals (in subrepository "s")
210 abort: cannot commit svn externals (in subrepository "s")
211 [255]
211 [255]
212 $ svn revert -q s/externals/other
212 $ svn revert -q s/externals/other
213
213
214 clone
214 clone
215
215
216 $ cd ..
216 $ cd ..
217 $ hg clone t tc
217 $ hg clone t tc
218 updating to branch default
218 updating to branch default
219 A tc/s/alpha
219 A tc/s/alpha
220 U tc/s
220 U tc/s
221
221
222 Fetching external item into 'tc/s/externals'* (glob)
222 Fetching external item into 'tc/s/externals'* (glob)
223 A tc/s/externals/other
223 A tc/s/externals/other
224 Checked out external at revision 1.
224 Checked out external at revision 1.
225
225
226 Checked out revision 3.
226 Checked out revision 3.
227 A tc/subdir/s/alpha
227 A tc/subdir/s/alpha
228 U tc/subdir/s
228 U tc/subdir/s
229
229
230 Fetching external item into 'tc/subdir/s/externals'* (glob)
230 Fetching external item into 'tc/subdir/s/externals'* (glob)
231 A tc/subdir/s/externals/other
231 A tc/subdir/s/externals/other
232 Checked out external at revision 1.
232 Checked out external at revision 1.
233
233
234 Checked out revision 2.
234 Checked out revision 2.
235 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
235 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
236 $ cd tc
236 $ cd tc
237
237
238 debugsub in clone
238 debugsub in clone
239
239
240 $ hg debugsub
240 $ hg debugsub
241 path s
241 path s
242 source file://*/svn-repo/src (glob)
242 source file:/*/$TESTTMP/svn-repo/src (glob)
243 revision 3
243 revision 3
244 path subdir/s
244 path subdir/s
245 source file://*/svn-repo/src (glob)
245 source file:/*/$TESTTMP/svn-repo/src (glob)
246 revision 2
246 revision 2
247
247
248 verify subrepo is contained within the repo directory
248 verify subrepo is contained within the repo directory
249
249
250 $ $PYTHON -c "import os.path; print os.path.exists('s')"
250 $ $PYTHON -c "import os.path; print os.path.exists('s')"
251 True
251 True
252
252
253 update to nullrev (must delete the subrepo)
253 update to nullrev (must delete the subrepo)
254
254
255 $ hg up null
255 $ hg up null
256 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
256 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
257 $ ls
257 $ ls
258
258
259 Check hg update --clean
259 Check hg update --clean
260 $ cd "$TESTTMP/sub/t"
260 $ cd "$TESTTMP/sub/t"
261 $ cd s
261 $ cd s
262 $ echo c0 > alpha
262 $ echo c0 > alpha
263 $ echo c1 > f1
263 $ echo c1 > f1
264 $ echo c1 > f2
264 $ echo c1 > f2
265 $ svn add f1 -q
265 $ svn add f1 -q
266 $ svn status | sort
266 $ svn status | sort
267
267
268 ? * a (glob)
268 ? * a (glob)
269 ? * f2 (glob)
269 ? * f2 (glob)
270 A * f1 (glob)
270 A * f1 (glob)
271 M * alpha (glob)
271 M * alpha (glob)
272 Performing status on external item at 'externals'* (glob)
272 Performing status on external item at 'externals'* (glob)
273 X * externals (glob)
273 X * externals (glob)
274 $ cd ../..
274 $ cd ../..
275 $ hg -R t update -C
275 $ hg -R t update -C
276
276
277 Fetching external item into 't/s/externals'* (glob)
277 Fetching external item into 't/s/externals'* (glob)
278 Checked out external at revision 1.
278 Checked out external at revision 1.
279
279
280 Checked out revision 3.
280 Checked out revision 3.
281 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
281 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
282 $ cd t/s
282 $ cd t/s
283 $ svn status | sort
283 $ svn status | sort
284
284
285 ? * a (glob)
285 ? * a (glob)
286 ? * f1 (glob)
286 ? * f1 (glob)
287 ? * f2 (glob)
287 ? * f2 (glob)
288 Performing status on external item at 'externals'* (glob)
288 Performing status on external item at 'externals'* (glob)
289 X * externals (glob)
289 X * externals (glob)
290
290
291 Sticky subrepositories, no changes
291 Sticky subrepositories, no changes
292 $ cd "$TESTTMP/sub/t"
292 $ cd "$TESTTMP/sub/t"
293 $ hg id -n
293 $ hg id -n
294 2
294 2
295 $ cd s
295 $ cd s
296 $ svnversion
296 $ svnversion
297 3
297 3
298 $ cd ..
298 $ cd ..
299 $ hg update 1
299 $ hg update 1
300 U *s/alpha (glob)
300 U *s/alpha (glob)
301
301
302 Fetching external item into '*s/externals'* (glob)
302 Fetching external item into '*s/externals'* (glob)
303 Checked out external at revision 1.
303 Checked out external at revision 1.
304
304
305 Checked out revision 2.
305 Checked out revision 2.
306 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
306 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
307 $ hg id -n
307 $ hg id -n
308 1
308 1
309 $ cd s
309 $ cd s
310 $ svnversion
310 $ svnversion
311 2
311 2
312 $ cd ..
312 $ cd ..
313
313
314 Sticky subrepositories, file changes
314 Sticky subrepositories, file changes
315 $ touch s/f1
315 $ touch s/f1
316 $ cd s
316 $ cd s
317 $ svn add f1
317 $ svn add f1
318 A f1
318 A f1
319 $ cd ..
319 $ cd ..
320 $ hg id -n
320 $ hg id -n
321 1+
321 1+
322 $ cd s
322 $ cd s
323 $ svnversion
323 $ svnversion
324 2M
324 2M
325 $ cd ..
325 $ cd ..
326 $ hg update tip
326 $ hg update tip
327 subrepository s diverged (local revision: 2, remote revision: 3)
327 subrepository s diverged (local revision: 2, remote revision: 3)
328 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
328 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
329 subrepository sources for s differ
329 subrepository sources for s differ
330 use (l)ocal source (2) or (r)emote source (3)? l
330 use (l)ocal source (2) or (r)emote source (3)? l
331 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
331 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
332 $ hg id -n
332 $ hg id -n
333 2+
333 2+
334 $ cd s
334 $ cd s
335 $ svnversion
335 $ svnversion
336 2M
336 2M
337 $ cd ..
337 $ cd ..
338 $ hg update --clean tip
338 $ hg update --clean tip
339 U *s/alpha (glob)
339 U *s/alpha (glob)
340
340
341 Fetching external item into '*s/externals'* (glob)
341 Fetching external item into '*s/externals'* (glob)
342 Checked out external at revision 1.
342 Checked out external at revision 1.
343
343
344 Checked out revision 3.
344 Checked out revision 3.
345 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
345 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
346
346
347 Sticky subrepository, revision updates
347 Sticky subrepository, revision updates
348 $ hg id -n
348 $ hg id -n
349 2
349 2
350 $ cd s
350 $ cd s
351 $ svnversion
351 $ svnversion
352 3
352 3
353 $ cd ..
353 $ cd ..
354 $ cd s
354 $ cd s
355 $ svn update -qr 1
355 $ svn update -qr 1
356 $ cd ..
356 $ cd ..
357 $ hg update 1
357 $ hg update 1
358 subrepository s diverged (local revision: 3, remote revision: 2)
358 subrepository s diverged (local revision: 3, remote revision: 2)
359 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
359 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
360 subrepository sources for s differ (in checked out version)
360 subrepository sources for s differ (in checked out version)
361 use (l)ocal source (1) or (r)emote source (2)? l
361 use (l)ocal source (1) or (r)emote source (2)? l
362 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
362 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
363 $ hg id -n
363 $ hg id -n
364 1+
364 1+
365 $ cd s
365 $ cd s
366 $ svnversion
366 $ svnversion
367 1
367 1
368 $ cd ..
368 $ cd ..
369
369
370 Sticky subrepository, file changes and revision updates
370 Sticky subrepository, file changes and revision updates
371 $ touch s/f1
371 $ touch s/f1
372 $ cd s
372 $ cd s
373 $ svn add f1
373 $ svn add f1
374 A f1
374 A f1
375 $ svnversion
375 $ svnversion
376 1M
376 1M
377 $ cd ..
377 $ cd ..
378 $ hg id -n
378 $ hg id -n
379 1+
379 1+
380 $ hg update tip
380 $ hg update tip
381 subrepository s diverged (local revision: 3, remote revision: 3)
381 subrepository s diverged (local revision: 3, remote revision: 3)
382 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
382 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
383 subrepository sources for s differ
383 subrepository sources for s differ
384 use (l)ocal source (1) or (r)emote source (3)? l
384 use (l)ocal source (1) or (r)emote source (3)? l
385 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
385 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
386 $ hg id -n
386 $ hg id -n
387 2+
387 2+
388 $ cd s
388 $ cd s
389 $ svnversion
389 $ svnversion
390 1M
390 1M
391 $ cd ..
391 $ cd ..
392
392
393 Sticky repository, update --clean
393 Sticky repository, update --clean
394 $ hg update --clean tip | grep -v 's[/\]externals[/\]other'
394 $ hg update --clean tip | grep -v 's[/\]externals[/\]other'
395 U *s/alpha (glob)
395 U *s/alpha (glob)
396 U *s (glob)
396 U *s (glob)
397
397
398 Fetching external item into '*s/externals'* (glob)
398 Fetching external item into '*s/externals'* (glob)
399 Checked out external at revision 1.
399 Checked out external at revision 1.
400
400
401 Checked out revision 3.
401 Checked out revision 3.
402 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
402 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
403 $ hg id -n
403 $ hg id -n
404 2
404 2
405 $ cd s
405 $ cd s
406 $ svnversion
406 $ svnversion
407 3
407 3
408 $ cd ..
408 $ cd ..
409
409
410 Test subrepo already at intended revision:
410 Test subrepo already at intended revision:
411 $ cd s
411 $ cd s
412 $ svn update -qr 2
412 $ svn update -qr 2
413 $ cd ..
413 $ cd ..
414 $ hg update 1
414 $ hg update 1
415 subrepository s diverged (local revision: 3, remote revision: 2)
415 subrepository s diverged (local revision: 3, remote revision: 2)
416 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
416 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
417 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
417 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
418 $ hg id -n
418 $ hg id -n
419 1+
419 1+
420 $ cd s
420 $ cd s
421 $ svnversion
421 $ svnversion
422 2
422 2
423 $ cd ..
423 $ cd ..
424
424
425 Test case where subversion would fail to update the subrepo because there
425 Test case where subversion would fail to update the subrepo because there
426 are unknown directories being replaced by tracked ones (happens with rebase).
426 are unknown directories being replaced by tracked ones (happens with rebase).
427
427
428 $ cd "$WCROOT/src"
428 $ cd "$WCROOT/src"
429 $ mkdir dir
429 $ mkdir dir
430 $ echo epsilon.py > dir/epsilon.py
430 $ echo epsilon.py > dir/epsilon.py
431 $ svn add dir
431 $ svn add dir
432 A dir
432 A dir
433 A dir/epsilon.py
433 A dir/epsilon.py
434 $ svn ci -qm 'Add dir/epsilon.py'
434 $ svn ci -qm 'Add dir/epsilon.py'
435 $ cd ../..
435 $ cd ../..
436 $ hg init rebaserepo
436 $ hg init rebaserepo
437 $ cd rebaserepo
437 $ cd rebaserepo
438 $ svn co -r5 --quiet "$SVNREPOURL"/src s
438 $ svn co -r5 --quiet "$SVNREPOURL"/src s
439 $ echo "s = [svn] $SVNREPOURL/src" >> .hgsub
439 $ echo "s = [svn] $SVNREPOURL/src" >> .hgsub
440 $ hg add .hgsub
440 $ hg add .hgsub
441 $ hg ci -m addsub
441 $ hg ci -m addsub
442 $ echo a > a
442 $ echo a > a
443 $ hg add .
443 $ hg add .
444 adding a
444 adding a
445 $ hg ci -m adda
445 $ hg ci -m adda
446 $ hg up 0
446 $ hg up 0
447 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
447 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
448 $ svn up -qr6 s
448 $ svn up -qr6 s
449 $ hg ci -m updatesub
449 $ hg ci -m updatesub
450 created new head
450 created new head
451 $ echo pyc > s/dir/epsilon.pyc
451 $ echo pyc > s/dir/epsilon.pyc
452 $ hg up 1
452 $ hg up 1
453 D *s/dir (glob)
453 D *s/dir (glob)
454
454
455 Fetching external item into '*s/externals'* (glob)
455 Fetching external item into '*s/externals'* (glob)
456 Checked out external at revision 1.
456 Checked out external at revision 1.
457
457
458 Checked out revision 5.
458 Checked out revision 5.
459 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
459 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
460 $ hg up -q 2
460 $ hg up -q 2
461
461
462 Modify one of the externals to point to a different path so we can
462 Modify one of the externals to point to a different path so we can
463 test having obstructions when switching branches on checkout:
463 test having obstructions when switching branches on checkout:
464 $ hg checkout tip
464 $ hg checkout tip
465 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
465 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
466 $ echo "obstruct = [svn] $SVNREPOURL/externals" >> .hgsub
466 $ echo "obstruct = [svn] $SVNREPOURL/externals" >> .hgsub
467 $ svn co -r5 --quiet "$SVNREPOURL"/externals obstruct
467 $ svn co -r5 --quiet "$SVNREPOURL"/externals obstruct
468 $ hg commit -m 'Start making obstructed working copy'
468 $ hg commit -m 'Start making obstructed working copy'
469 $ hg book other
469 $ hg book other
470 $ hg co -r 'p1(tip)'
470 $ hg co -r 'p1(tip)'
471 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
471 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
472 (leaving bookmark other)
472 (leaving bookmark other)
473 $ echo "obstruct = [svn] $SVNREPOURL/src" >> .hgsub
473 $ echo "obstruct = [svn] $SVNREPOURL/src" >> .hgsub
474 $ svn co -r5 --quiet "$SVNREPOURL"/src obstruct
474 $ svn co -r5 --quiet "$SVNREPOURL"/src obstruct
475 $ hg commit -m 'Other branch which will be obstructed'
475 $ hg commit -m 'Other branch which will be obstructed'
476 created new head
476 created new head
477
477
478 Switching back to the head where we have another path mapped to the
478 Switching back to the head where we have another path mapped to the
479 same subrepo should work if the subrepo is clean.
479 same subrepo should work if the subrepo is clean.
480 $ hg co other
480 $ hg co other
481 A *obstruct/other (glob)
481 A *obstruct/other (glob)
482 Checked out revision 1.
482 Checked out revision 1.
483 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
483 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
484 (activating bookmark other)
484 (activating bookmark other)
485
485
486 This is surprising, but is also correct based on the current code:
486 This is surprising, but is also correct based on the current code:
487 $ echo "updating should (maybe) fail" > obstruct/other
487 $ echo "updating should (maybe) fail" > obstruct/other
488 $ hg co tip
488 $ hg co tip
489 abort: uncommitted changes
489 abort: uncommitted changes
490 (commit or update --clean to discard changes)
490 (commit or update --clean to discard changes)
491 [255]
491 [255]
492
492
493 Point to a Subversion branch which has since been deleted and recreated
493 Point to a Subversion branch which has since been deleted and recreated
494 First, create that condition in the repository.
494 First, create that condition in the repository.
495
495
496 $ hg ci --subrepos -m cleanup | filter_svn_output
496 $ hg ci --subrepos -m cleanup | filter_svn_output
497 committing subrepository obstruct
497 committing subrepository obstruct
498 Sending obstruct/other
498 Sending obstruct/other
499 Committed revision 7.
499 Committed revision 7.
500 At revision 7.
500 At revision 7.
501 $ svn mkdir -qm "baseline" $SVNREPOURL/trunk
501 $ svn mkdir -qm "baseline" $SVNREPOURL/trunk
502 $ svn copy -qm "initial branch" $SVNREPOURL/trunk $SVNREPOURL/branch
502 $ svn copy -qm "initial branch" $SVNREPOURL/trunk $SVNREPOURL/branch
503 $ svn co --quiet "$SVNREPOURL"/branch tempwc
503 $ svn co --quiet "$SVNREPOURL"/branch tempwc
504 $ cd tempwc
504 $ cd tempwc
505 $ echo "something old" > somethingold
505 $ echo "something old" > somethingold
506 $ svn add somethingold
506 $ svn add somethingold
507 A somethingold
507 A somethingold
508 $ svn ci -qm 'Something old'
508 $ svn ci -qm 'Something old'
509 $ svn rm -qm "remove branch" $SVNREPOURL/branch
509 $ svn rm -qm "remove branch" $SVNREPOURL/branch
510 $ svn copy -qm "recreate branch" $SVNREPOURL/trunk $SVNREPOURL/branch
510 $ svn copy -qm "recreate branch" $SVNREPOURL/trunk $SVNREPOURL/branch
511 $ svn up -q
511 $ svn up -q
512 $ echo "something new" > somethingnew
512 $ echo "something new" > somethingnew
513 $ svn add somethingnew
513 $ svn add somethingnew
514 A somethingnew
514 A somethingnew
515 $ svn ci -qm 'Something new'
515 $ svn ci -qm 'Something new'
516 $ cd ..
516 $ cd ..
517 $ rm -rf tempwc
517 $ rm -rf tempwc
518 $ svn co "$SVNREPOURL/branch"@10 recreated
518 $ svn co "$SVNREPOURL/branch"@10 recreated
519 A recreated/somethingold
519 A recreated/somethingold
520 Checked out revision 10.
520 Checked out revision 10.
521 $ echo "recreated = [svn] $SVNREPOURL/branch" >> .hgsub
521 $ echo "recreated = [svn] $SVNREPOURL/branch" >> .hgsub
522 $ hg ci -m addsub
522 $ hg ci -m addsub
523 $ cd recreated
523 $ cd recreated
524 $ svn up -q
524 $ svn up -q
525 $ cd ..
525 $ cd ..
526 $ hg ci -m updatesub
526 $ hg ci -m updatesub
527 $ hg up -r-2
527 $ hg up -r-2
528 D *recreated/somethingnew (glob)
528 D *recreated/somethingnew (glob)
529 A *recreated/somethingold (glob)
529 A *recreated/somethingold (glob)
530 Checked out revision 10.
530 Checked out revision 10.
531 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
531 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
532 (leaving bookmark other)
532 (leaving bookmark other)
533 $ test -f recreated/somethingold
533 $ test -f recreated/somethingold
534
534
535 Test archive
535 Test archive
536
536
537 $ hg archive -S ../archive-all --debug --config progress.debug=true
537 $ hg archive -S ../archive-all --debug --config progress.debug=true
538 archiving: 0/2 files (0.00%)
538 archiving: 0/2 files (0.00%)
539 archiving: .hgsub 1/2 files (50.00%)
539 archiving: .hgsub 1/2 files (50.00%)
540 archiving: .hgsubstate 2/2 files (100.00%)
540 archiving: .hgsubstate 2/2 files (100.00%)
541 archiving (obstruct): 0/1 files (0.00%)
541 archiving (obstruct): 0/1 files (0.00%)
542 archiving (obstruct): 1/1 files (100.00%)
542 archiving (obstruct): 1/1 files (100.00%)
543 archiving (recreated): 0/1 files (0.00%)
543 archiving (recreated): 0/1 files (0.00%)
544 archiving (recreated): 1/1 files (100.00%)
544 archiving (recreated): 1/1 files (100.00%)
545 archiving (s): 0/2 files (0.00%)
545 archiving (s): 0/2 files (0.00%)
546 archiving (s): 1/2 files (50.00%)
546 archiving (s): 1/2 files (50.00%)
547 archiving (s): 2/2 files (100.00%)
547 archiving (s): 2/2 files (100.00%)
548
548
549 $ hg archive -S ../archive-exclude --debug --config progress.debug=true -X **old
549 $ hg archive -S ../archive-exclude --debug --config progress.debug=true -X **old
550 archiving: 0/2 files (0.00%)
550 archiving: 0/2 files (0.00%)
551 archiving: .hgsub 1/2 files (50.00%)
551 archiving: .hgsub 1/2 files (50.00%)
552 archiving: .hgsubstate 2/2 files (100.00%)
552 archiving: .hgsubstate 2/2 files (100.00%)
553 archiving (obstruct): 0/1 files (0.00%)
553 archiving (obstruct): 0/1 files (0.00%)
554 archiving (obstruct): 1/1 files (100.00%)
554 archiving (obstruct): 1/1 files (100.00%)
555 archiving (recreated): 0 files
555 archiving (recreated): 0 files
556 archiving (s): 0/2 files (0.00%)
556 archiving (s): 0/2 files (0.00%)
557 archiving (s): 1/2 files (50.00%)
557 archiving (s): 1/2 files (50.00%)
558 archiving (s): 2/2 files (100.00%)
558 archiving (s): 2/2 files (100.00%)
559 $ find ../archive-exclude | sort
559 $ find ../archive-exclude | sort
560 ../archive-exclude
560 ../archive-exclude
561 ../archive-exclude/.hg_archival.txt
561 ../archive-exclude/.hg_archival.txt
562 ../archive-exclude/.hgsub
562 ../archive-exclude/.hgsub
563 ../archive-exclude/.hgsubstate
563 ../archive-exclude/.hgsubstate
564 ../archive-exclude/obstruct
564 ../archive-exclude/obstruct
565 ../archive-exclude/obstruct/other
565 ../archive-exclude/obstruct/other
566 ../archive-exclude/s
566 ../archive-exclude/s
567 ../archive-exclude/s/alpha
567 ../archive-exclude/s/alpha
568 ../archive-exclude/s/dir
568 ../archive-exclude/s/dir
569 ../archive-exclude/s/dir/epsilon.py
569 ../archive-exclude/s/dir/epsilon.py
570
570
571 Test forgetting files, not implemented in svn subrepo, used to
571 Test forgetting files, not implemented in svn subrepo, used to
572 traceback
572 traceback
573
573
574 $ hg forget 'notafile*'
574 $ hg forget 'notafile*'
575 notafile*: $ENOENT$
575 notafile*: $ENOENT$
576 [1]
576 [1]
577
577
578 Test a subrepo referencing a just moved svn path. Last commit rev will
578 Test a subrepo referencing a just moved svn path. Last commit rev will
579 be different from the revision, and the path will be different as
579 be different from the revision, and the path will be different as
580 well.
580 well.
581
581
582 $ cd "$WCROOT"
582 $ cd "$WCROOT"
583 $ svn up > /dev/null
583 $ svn up > /dev/null
584 $ mkdir trunk/subdir branches
584 $ mkdir trunk/subdir branches
585 $ echo a > trunk/subdir/a
585 $ echo a > trunk/subdir/a
586 $ svn add trunk/subdir branches
586 $ svn add trunk/subdir branches
587 A trunk/subdir
587 A trunk/subdir
588 A trunk/subdir/a
588 A trunk/subdir/a
589 A branches
589 A branches
590 $ svn ci -qm addsubdir
590 $ svn ci -qm addsubdir
591 $ svn cp -qm branchtrunk $SVNREPOURL/trunk $SVNREPOURL/branches/somebranch
591 $ svn cp -qm branchtrunk $SVNREPOURL/trunk $SVNREPOURL/branches/somebranch
592 $ cd ..
592 $ cd ..
593
593
594 $ hg init repo2
594 $ hg init repo2
595 $ cd repo2
595 $ cd repo2
596 $ svn co $SVNREPOURL/branches/somebranch/subdir
596 $ svn co $SVNREPOURL/branches/somebranch/subdir
597 A subdir/a
597 A subdir/a
598 Checked out revision 15.
598 Checked out revision 15.
599 $ echo "subdir = [svn] $SVNREPOURL/branches/somebranch/subdir" > .hgsub
599 $ echo "subdir = [svn] $SVNREPOURL/branches/somebranch/subdir" > .hgsub
600 $ hg add .hgsub
600 $ hg add .hgsub
601 $ hg ci -m addsub
601 $ hg ci -m addsub
602 $ hg up null
602 $ hg up null
603 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
603 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
604 $ hg up
604 $ hg up
605 A *subdir/a (glob)
605 A *subdir/a (glob)
606 Checked out revision 15.
606 Checked out revision 15.
607 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
607 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
608 $ cd ..
608 $ cd ..
609
609
610 Test sanitizing ".hg/hgrc" in subrepo
610 Test sanitizing ".hg/hgrc" in subrepo
611
611
612 $ cd sub/t
612 $ cd sub/t
613 $ hg update -q -C tip
613 $ hg update -q -C tip
614 $ cd s
614 $ cd s
615 $ mkdir .hg
615 $ mkdir .hg
616 $ echo '.hg/hgrc in svn repo' > .hg/hgrc
616 $ echo '.hg/hgrc in svn repo' > .hg/hgrc
617 $ mkdir -p sub/.hg
617 $ mkdir -p sub/.hg
618 $ echo 'sub/.hg/hgrc in svn repo' > sub/.hg/hgrc
618 $ echo 'sub/.hg/hgrc in svn repo' > sub/.hg/hgrc
619 $ svn add .hg sub
619 $ svn add .hg sub
620 A .hg
620 A .hg
621 A .hg/hgrc
621 A .hg/hgrc
622 A sub
622 A sub
623 A sub/.hg
623 A sub/.hg
624 A sub/.hg/hgrc
624 A sub/.hg/hgrc
625 $ svn ci -qm 'add .hg/hgrc to be sanitized at hg update'
625 $ svn ci -qm 'add .hg/hgrc to be sanitized at hg update'
626 $ svn up -q
626 $ svn up -q
627 $ cd ..
627 $ cd ..
628 $ hg commit -S -m 'commit with svn revision including .hg/hgrc'
628 $ hg commit -S -m 'commit with svn revision including .hg/hgrc'
629 $ grep ' s$' .hgsubstate
629 $ grep ' s$' .hgsubstate
630 16 s
630 16 s
631 $ cd ..
631 $ cd ..
632
632
633 $ hg -R tc pull -u -q 2>&1 | sort
633 $ hg -R tc pull -u -q 2>&1 | sort
634 warning: removing potentially hostile 'hgrc' in '$TESTTMP/sub/tc/s/.hg'
634 warning: removing potentially hostile 'hgrc' in '$TESTTMP/sub/tc/s/.hg'
635 warning: removing potentially hostile 'hgrc' in '$TESTTMP/sub/tc/s/sub/.hg'
635 warning: removing potentially hostile 'hgrc' in '$TESTTMP/sub/tc/s/sub/.hg'
636 $ cd tc
636 $ cd tc
637 $ grep ' s$' .hgsubstate
637 $ grep ' s$' .hgsubstate
638 16 s
638 16 s
639 $ test -f s/.hg/hgrc
639 $ test -f s/.hg/hgrc
640 [1]
640 [1]
641 $ test -f s/sub/.hg/hgrc
641 $ test -f s/sub/.hg/hgrc
642 [1]
642 [1]
643
643
644 Test that sanitizing is omitted in meta data area:
644 Test that sanitizing is omitted in meta data area:
645
645
646 $ mkdir s/.svn/.hg
646 $ mkdir s/.svn/.hg
647 $ echo '.hg/hgrc in svn metadata area' > s/.svn/.hg/hgrc
647 $ echo '.hg/hgrc in svn metadata area' > s/.svn/.hg/hgrc
648 $ hg update -q -C '.^1'
648 $ hg update -q -C '.^1'
649
649
650 $ cd ../..
650 $ cd ../..
651
651
652 SEC: test for ssh exploit
652 SEC: test for ssh exploit
653
653
654 $ hg init ssh-vuln
654 $ hg init ssh-vuln
655 $ cd ssh-vuln
655 $ cd ssh-vuln
656 $ echo "s = [svn]$SVNREPOURL/src" >> .hgsub
656 $ echo "s = [svn]$SVNREPOURL/src" >> .hgsub
657 $ svn co --quiet "$SVNREPOURL"/src s
657 $ svn co --quiet "$SVNREPOURL"/src s
658 $ hg add .hgsub
658 $ hg add .hgsub
659 $ hg ci -m1
659 $ hg ci -m1
660 $ echo "s = [svn]svn+ssh://-oProxyCommand=touch%20owned%20nested" > .hgsub
660 $ echo "s = [svn]svn+ssh://-oProxyCommand=touch%20owned%20nested" > .hgsub
661 $ hg ci -m2
661 $ hg ci -m2
662 $ cd ..
662 $ cd ..
663 $ hg clone ssh-vuln ssh-vuln-clone
663 $ hg clone ssh-vuln ssh-vuln-clone
664 updating to branch default
664 updating to branch default
665 abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned nested' (in subrepository "s")
665 abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned nested' (in subrepository "s")
666 [255]
666 [255]
667
667
668 also check that a percent encoded '-' (%2D) doesn't work
668 also check that a percent encoded '-' (%2D) doesn't work
669
669
670 $ cd ssh-vuln
670 $ cd ssh-vuln
671 $ echo "s = [svn]svn+ssh://%2DoProxyCommand=touch%20owned%20nested" > .hgsub
671 $ echo "s = [svn]svn+ssh://%2DoProxyCommand=touch%20owned%20nested" > .hgsub
672 $ hg ci -m3
672 $ hg ci -m3
673 $ cd ..
673 $ cd ..
674 $ rm -r ssh-vuln-clone
674 $ rm -r ssh-vuln-clone
675 $ hg clone ssh-vuln ssh-vuln-clone
675 $ hg clone ssh-vuln ssh-vuln-clone
676 updating to branch default
676 updating to branch default
677 abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned nested' (in subrepository "s")
677 abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned nested' (in subrepository "s")
678 [255]
678 [255]
679
679
680 also check that hiding the attack in the username doesn't work:
680 also check that hiding the attack in the username doesn't work:
681
681
682 $ cd ssh-vuln
682 $ cd ssh-vuln
683 $ echo "s = [svn]svn+ssh://%2DoProxyCommand=touch%20owned%20foo@example.com/nested" > .hgsub
683 $ echo "s = [svn]svn+ssh://%2DoProxyCommand=touch%20owned%20foo@example.com/nested" > .hgsub
684 $ hg ci -m3
684 $ hg ci -m3
685 $ cd ..
685 $ cd ..
686 $ rm -r ssh-vuln-clone
686 $ rm -r ssh-vuln-clone
687 $ hg clone ssh-vuln ssh-vuln-clone
687 $ hg clone ssh-vuln ssh-vuln-clone
688 updating to branch default
688 updating to branch default
689 abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned foo@example.com/nested' (in subrepository "s")
689 abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned foo@example.com/nested' (in subrepository "s")
690 [255]
690 [255]
General Comments 0
You need to be logged in to leave comments. Login now