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