##// END OF EJS Templates
tests: use grep -F instead of obsolescent fgrep...
Mads Kiilerich -
r51619:65f949da stable
parent child Browse files
Show More
@@ -1,1088 +1,1089 b''
1 1 #!/usr/bin/env python3
2 2 #
3 3 # check-code - a style and portability checker for Mercurial
4 4 #
5 5 # Copyright 2010 Olivia Mackall <olivia@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 import glob
23 23 import keyword
24 24 import optparse
25 25 import os
26 26 import re
27 27 import sys
28 28
29 29 if sys.version_info[0] < 3:
30 30 opentext = open
31 31 else:
32 32
33 33 def opentext(f):
34 34 return open(f, encoding='latin1')
35 35
36 36
37 37 try:
38 38 xrange
39 39 except NameError:
40 40 xrange = range
41 41 try:
42 42 import re2
43 43 except ImportError:
44 44 re2 = None
45 45
46 46 import testparseutil
47 47
48 48
49 49 def compilere(pat, multiline=False):
50 50 if multiline:
51 51 pat = '(?m)' + pat
52 52 if re2:
53 53 try:
54 54 return re2.compile(pat)
55 55 except re2.error:
56 56 pass
57 57 return re.compile(pat)
58 58
59 59
60 60 # check "rules depending on implementation of repquote()" in each
61 61 # patterns (especially pypats), before changing around repquote()
62 62 _repquotefixedmap = {
63 63 ' ': ' ',
64 64 '\n': '\n',
65 65 '.': 'p',
66 66 ':': 'q',
67 67 '%': '%',
68 68 '\\': 'b',
69 69 '*': 'A',
70 70 '+': 'P',
71 71 '-': 'M',
72 72 }
73 73
74 74
75 75 def _repquoteencodechr(i):
76 76 if i > 255:
77 77 return 'u'
78 78 c = chr(i)
79 79 if c in _repquotefixedmap:
80 80 return _repquotefixedmap[c]
81 81 if c.isalpha():
82 82 return 'x'
83 83 if c.isdigit():
84 84 return 'n'
85 85 return 'o'
86 86
87 87
88 88 _repquotett = ''.join(_repquoteencodechr(i) for i in xrange(256))
89 89
90 90
91 91 def repquote(m):
92 92 t = m.group('text')
93 93 t = t.translate(_repquotett)
94 94 return m.group('quote') + t + m.group('quote')
95 95
96 96
97 97 def reppython(m):
98 98 comment = m.group('comment')
99 99 if comment:
100 100 l = len(comment.rstrip())
101 101 return "#" * l + comment[l:]
102 102 return repquote(m)
103 103
104 104
105 105 def repcomment(m):
106 106 return m.group(1) + "#" * len(m.group(2))
107 107
108 108
109 109 def repccomment(m):
110 110 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
111 111 return m.group(1) + t + "*/"
112 112
113 113
114 114 def repcallspaces(m):
115 115 t = re.sub(r"\n\s+", "\n", m.group(2))
116 116 return m.group(1) + t
117 117
118 118
119 119 def repinclude(m):
120 120 return m.group(1) + "<foo>"
121 121
122 122
123 123 def rephere(m):
124 124 t = re.sub(r"\S", "x", m.group(2))
125 125 return m.group(1) + t
126 126
127 127
128 128 testpats = [
129 129 [
130 130 (r'\b(push|pop)d\b', "don't use 'pushd' or 'popd', use 'cd'"),
131 131 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
132 132 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
133 133 (r'(?<!hg )grep.* -a', "don't use 'grep -a', use in-line python"),
134 134 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
135 135 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
136 136 (r'echo -n', "don't use 'echo -n', use printf"),
137 137 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
138 138 (r'head -c', "don't use 'head -c', use 'dd'"),
139 139 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
140 140 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
141 141 (r'\bls\b.*-\w*R', "don't use 'ls -R', use 'find'"),
142 142 (r'printf.*[^\\]\\([1-9]|0\d)', r"don't use 'printf \NNN', use Python"),
143 143 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
144 144 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
145 145 (
146 146 r'\[[^\]]+==',
147 147 '[ foo == bar ] is a bashism, use [ foo = bar ] instead',
148 148 ),
149 149 (r'(^|\|\s*)egrep', "use grep -E for extended grep syntax"),
150 (r'(^|\|\s*)fgrep', "use grep -F for fixed string grepping"),
150 151 (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"),
151 152 (r'(?<!!)/bin/', "don't use explicit paths for tools"),
152 153 (r'#!.*/bash', "don't use bash in shebang, use sh"),
153 154 (r'[^\n]\Z', "no trailing newline"),
154 155 (r'export .*=', "don't export and assign at once"),
155 156 (r'^source\b', "don't use 'source', use '.'"),
156 157 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
157 158 (r'\bls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
158 159 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
159 160 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
160 161 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
161 162 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
162 163 (r'^alias\b.*=', "don't use alias, use a function"),
163 164 (r'if\s*!', "don't use '!' to negate exit status"),
164 165 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
165 166 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
166 167 (
167 168 r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
168 169 "put a backslash-escaped newline after sed 'i' command",
169 170 ),
170 171 (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"),
171 172 (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"),
172 173 (r'[\s="`\']python\s(?!bindings)', "don't use 'python', use '$PYTHON'"),
173 174 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"),
174 175 (r'\butil\.Abort\b', "directly use error.Abort"),
175 176 (r'\|&', "don't use |&, use 2>&1"),
176 177 (r'\w = +\w', "only one space after = allowed"),
177 178 (
178 179 r'\bsed\b.*[^\\]\\n',
179 180 "don't use 'sed ... \\n', use a \\ and a newline",
180 181 ),
181 182 (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'"),
182 183 (r'cp.* -r ', "don't use 'cp -r', use 'cp -R'"),
183 184 (r'grep.* -[ABC]', "don't use grep's context flags"),
184 185 (
185 186 r'find.*-printf',
186 187 "don't use 'find -printf', it doesn't exist on BSD find(1)",
187 188 ),
188 189 (r'\$RANDOM ', "don't use bash-only $RANDOM to generate random values"),
189 190 ],
190 191 # warnings
191 192 [
192 193 (r'^function', "don't use 'function', use old style"),
193 194 (r'^diff.*-\w*N', "don't use 'diff -N'"),
194 195 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`", "no-pwd-check"),
195 196 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
196 197 (r'kill (`|\$\()', "don't use kill, use killdaemons.py"),
197 198 ],
198 199 ]
199 200
200 201 testfilters = [
201 202 (r"( *)(#([^!][^\n]*\S)?)", repcomment),
202 203 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
203 204 ]
204 205
205 206 uprefix = r"^ \$ "
206 207 utestpats = [
207 208 [
208 209 (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"),
209 210 (
210 211 uprefix + r'.*\|\s*sed[^|>\n]*\n',
211 212 "use regex test output patterns instead of sed",
212 213 ),
213 214 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
214 215 (
215 216 uprefix + r'.*\|\| echo.*(fail|error)',
216 217 "explicit exit code checks unnecessary",
217 218 ),
218 219 (uprefix + r'set -e', "don't use set -e"),
219 220 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
220 221 (
221 222 uprefix + r'.*:\.\S*/',
222 223 "x:.y in a path does not work on msys, rewrite "
223 224 "as x://.y, or see `hg log -k msys` for alternatives",
224 225 r'-\S+:\.|' '# no-msys', # -Rxxx
225 226 ), # in test-pull.t which is skipped on windows
226 227 (
227 228 r'^ [^$>].*27\.0\.0\.1',
228 229 'use $LOCALIP not an explicit loopback address',
229 230 ),
230 231 (
231 232 r'^ (?![>$] ).*\$LOCALIP.*[^)]$',
232 233 'mark $LOCALIP output lines with (glob) to help tests in BSD jails',
233 234 ),
234 235 (
235 236 r'^ (cat|find): .*: \$ENOENT\$',
236 237 'use test -f to test for file existence',
237 238 ),
238 239 (
239 240 r'^ diff -[^ -]*p',
240 241 "don't use (external) diff with -p for portability",
241 242 ),
242 243 (r' readlink ', 'use readlink.py instead of readlink'),
243 244 (
244 245 r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
245 246 "glob timezone field in diff output for portability",
246 247 ),
247 248 (
248 249 r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
249 250 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability",
250 251 ),
251 252 (
252 253 r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
253 254 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability",
254 255 ),
255 256 (
256 257 r'^ @@ -[0-9]+ [+][0-9]+ @@',
257 258 "use '@@ -N* +N* @@ (glob)' style chunk header for portability",
258 259 ),
259 260 (
260 261 uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
261 262 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
262 263 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)",
263 264 ),
264 265 ],
265 266 # warnings
266 267 [
267 268 (
268 269 r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$',
269 270 "glob match with no glob string (?, *, /, and $LOCALIP)",
270 271 ),
271 272 ],
272 273 ]
273 274
274 275 # transform plain test rules to unified test's
275 276 for i in [0, 1]:
276 277 for tp in testpats[i]:
277 278 p = tp[0]
278 279 m = tp[1]
279 280 if p.startswith('^'):
280 281 p = "^ [$>] (%s)" % p[1:]
281 282 else:
282 283 p = "^ [$>] .*(%s)" % p
283 284 utestpats[i].append((p, m) + tp[2:])
284 285
285 286 # don't transform the following rules:
286 287 # " > \t" and " \t" should be allowed in unified tests
287 288 testpats[0].append((r'^( *)\t', "don't use tabs to indent"))
288 289 utestpats[0].append((r'^( ?)\t', "don't use tabs to indent"))
289 290
290 291 utestfilters = [
291 292 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
292 293 (r"( +)(#([^!][^\n]*\S)?)", repcomment),
293 294 ]
294 295
295 296 # common patterns to check *.py
296 297 commonpypats = [
297 298 [
298 299 (r'\\$', 'Use () to wrap long lines in Python, not \\'),
299 300 (
300 301 r'^\s*def\s*\w+\s*\(.*,\s*\(',
301 302 "tuple parameter unpacking not available in Python 3+",
302 303 ),
303 304 (
304 305 r'lambda\s*\(.*,.*\)',
305 306 "tuple parameter unpacking not available in Python 3+",
306 307 ),
307 308 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
308 309 (r'(?<!\.)\breduce\s*\(.*', "reduce is not available in Python 3+"),
309 310 (
310 311 r'\bdict\(.*=',
311 312 'dict() is different in Py2 and 3 and is slower than {}',
312 313 'dict-from-generator',
313 314 ),
314 315 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
315 316 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
316 317 (r'^\s*\t', "don't use tabs"),
317 318 (r'\S;\s*\n', "semicolon"),
318 319 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
319 320 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
320 321 (r'(\w|\)),\w', "missing whitespace after ,"),
321 322 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
322 323 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
323 324 (
324 325 (
325 326 # a line ending with a colon, potentially with trailing comments
326 327 r':([ \t]*#[^\n]*)?\n'
327 328 # one that is not a pass and not only a comment
328 329 r'(?P<indent>[ \t]+)[^#][^\n]+\n'
329 330 # more lines at the same indent level
330 331 r'((?P=indent)[^\n]+\n)*'
331 332 # a pass at the same indent level, which is bogus
332 333 r'(?P=indent)pass[ \t\n#]'
333 334 ),
334 335 'omit superfluous pass',
335 336 ),
336 337 (r'[^\n]\Z', "no trailing newline"),
337 338 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
338 339 (
339 340 r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
340 341 "linebreak after :",
341 342 ),
342 343 (
343 344 r'\b(%s)\('
344 345 % '|'.join(k for k in keyword.kwlist if k not in ('print', 'exec')),
345 346 "Python keyword is not a function",
346 347 ),
347 348 # (r'class\s[A-Z][^\(]*\((?!Exception)',
348 349 # "don't capitalize non-exception classes"),
349 350 # (r'in range\(', "use xrange"),
350 351 # (r'^\s*print\s+', "avoid using print in core and extensions"),
351 352 (r'[\x80-\xff]', "non-ASCII character literal"),
352 353 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
353 354 (
354 355 r'([\(\[][ \t]\S)|(\S[ \t][\)\]])',
355 356 "gratuitous whitespace in () or []",
356 357 ),
357 358 # (r'\s\s=', "gratuitous whitespace before ="),
358 359 (
359 360 r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
360 361 "missing whitespace around operator",
361 362 ),
362 363 (
363 364 r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
364 365 "missing whitespace around operator",
365 366 ),
366 367 (
367 368 r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
368 369 "missing whitespace around operator",
369 370 ),
370 371 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]', "wrong whitespace around ="),
371 372 (
372 373 r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
373 374 "don't use old-style two-argument raise, use Exception(message)",
374 375 ),
375 376 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
376 377 (
377 378 r' [=!]=\s+(True|False|None)',
378 379 "comparison with singleton, use 'is' or 'is not' instead",
379 380 ),
380 381 (
381 382 r'^\s*(while|if) [01]:',
382 383 "use True/False for constant Boolean expression",
383 384 ),
384 385 (r'^\s*if False(:| +and)', 'Remove code instead of using `if False`'),
385 386 (
386 387 r'(?:(?<!def)\s+|\()hasattr\(',
387 388 'hasattr(foo, bar) is broken on py2, use util.safehasattr(foo, bar) '
388 389 'instead',
389 390 r'#.*hasattr-py3-only',
390 391 ),
391 392 (r'opener\([^)]*\).read\(', "use opener.read() instead"),
392 393 (r'opener\([^)]*\).write\(', "use opener.write() instead"),
393 394 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
394 395 (r'\.debug\(\_', "don't mark debug messages for translation"),
395 396 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
396 397 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
397 398 (
398 399 r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
399 400 'legacy exception syntax; use "as" instead of ","',
400 401 ),
401 402 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
402 403 (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
403 404 (
404 405 r'os\.path\.join\(.*, *(""|\'\')\)',
405 406 "use pathutil.normasprefix(path) instead of os.path.join(path, '')",
406 407 ),
407 408 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
408 409 # XXX only catch mutable arguments on the first line of the definition
409 410 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
410 411 (r'\butil\.Abort\b', "directly use error.Abort"),
411 412 (
412 413 r'^@(\w*\.)?cachefunc',
413 414 "module-level @cachefunc is risky, please avoid",
414 415 ),
415 416 (
416 417 r'^(from|import) mercurial\.(cext|pure|cffi)',
417 418 "use mercurial.policy.importmod instead",
418 419 ),
419 420 (r'\.next\(\)', "don't use .next(), use next(...)"),
420 421 (
421 422 r'([a-z]*).revision\(\1\.node\(',
422 423 "don't convert rev to node before passing to revision(nodeorrev)",
423 424 ),
424 425 (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
425 426 ],
426 427 # warnings
427 428 [],
428 429 ]
429 430
430 431 # patterns to check normal *.py files
431 432 pypats = [
432 433 [
433 434 # Ideally, these should be placed in "commonpypats" for
434 435 # consistency of coding rules in Mercurial source tree.
435 436 # But on the other hand, these are not so seriously required for
436 437 # python code fragments embedded in test scripts. Fixing test
437 438 # scripts for these patterns requires many changes, and has less
438 439 # profit than effort.
439 440 (r'raise Exception', "don't raise generic exceptions"),
440 441 (r'[\s\(](open|file)\([^)]*\)\.read\(', "use util.readfile() instead"),
441 442 (
442 443 r'[\s\(](open|file)\([^)]*\)\.write\(',
443 444 "use util.writefile() instead",
444 445 ),
445 446 (
446 447 r'^[\s\(]*(open(er)?|file)\([^)]*\)(?!\.close\(\))',
447 448 "always assign an opened file to a variable, and close it afterwards",
448 449 ),
449 450 (
450 451 r'[\s\(](open|file)\([^)]*\)\.(?!close\(\))',
451 452 "always assign an opened file to a variable, and close it afterwards",
452 453 ),
453 454 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
454 455 (r'^import atexit', "don't use atexit, use ui.atexit"),
455 456 # rules depending on implementation of repquote()
456 457 (
457 458 r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
458 459 'string join across lines with no space',
459 460 ),
460 461 (
461 462 r'''(?x)ui\.(status|progress|write|note|warn)\(
462 463 [ \t\n#]*
463 464 (?# any strings/comments might precede a string, which
464 465 # contains translatable message)
465 466 b?((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
466 467 (?# sequence consisting of below might precede translatable message
467 468 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
468 469 # - escaped character: "\\", "\n", "\0" ...
469 470 # - character other than '%', 'b' as '\', and 'x' as alphabet)
470 471 (['"]|\'\'\'|""")
471 472 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
472 473 (?# this regexp can't use [^...] style,
473 474 # because _preparepats forcibly adds "\n" into [^...],
474 475 # even though this regexp wants match it against "\n")''',
475 476 "missing _() in ui message (use () to hide false-positives)",
476 477 ),
477 478 ]
478 479 + commonpypats[0],
479 480 # warnings
480 481 [
481 482 # rules depending on implementation of repquote()
482 483 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
483 484 ]
484 485 + commonpypats[1],
485 486 ]
486 487
487 488 # patterns to check *.py for embedded ones in test script
488 489 embeddedpypats = [
489 490 [] + commonpypats[0],
490 491 # warnings
491 492 [] + commonpypats[1],
492 493 ]
493 494
494 495 # common filters to convert *.py
495 496 commonpyfilters = [
496 497 (
497 498 r"""(?msx)(?P<comment>\#.*?$)|
498 499 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
499 500 (?P<text>(([^\\]|\\.)*?))
500 501 (?P=quote))""",
501 502 reppython,
502 503 ),
503 504 ]
504 505
505 506 # pattern only for mercurial and extensions
506 507 core_py_pats = [
507 508 [
508 509 # Windows tend to get confused about capitalization of the drive letter
509 510 #
510 511 # see mercurial.windows.abspath for details
511 512 (
512 513 r'os\.path\.abspath',
513 514 "use util.abspath instead (windows)",
514 515 r'#.*re-exports',
515 516 ),
516 517 ],
517 518 # warnings
518 519 [],
519 520 ]
520 521
521 522 # filters to convert normal *.py files
522 523 pyfilters = [] + commonpyfilters
523 524
524 525 # non-filter patterns
525 526 pynfpats = [
526 527 [
527 528 (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
528 529 (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"),
529 530 (
530 531 r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]',
531 532 "use pycompat.isdarwin",
532 533 ),
533 534 ],
534 535 # warnings
535 536 [],
536 537 ]
537 538
538 539 # filters to convert *.py for embedded ones in test script
539 540 embeddedpyfilters = [] + commonpyfilters
540 541
541 542 # extension non-filter patterns
542 543 pyextnfpats = [
543 544 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
544 545 # warnings
545 546 [],
546 547 ]
547 548
548 549 txtfilters = []
549 550
550 551 txtpats = [
551 552 [
552 553 (r'\s$', 'trailing whitespace'),
553 554 ('.. note::[ \n][^\n]', 'add two newlines after note::'),
554 555 ],
555 556 [],
556 557 ]
557 558
558 559 cpats = [
559 560 [
560 561 (r'//', "don't use //-style comments"),
561 562 (r'\S\t', "don't use tabs except for indent"),
562 563 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
563 564 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
564 565 (r'return\(', "return is not a function"),
565 566 (r' ;', "no space before ;"),
566 567 (r'[^;] \)', "no space before )"),
567 568 (r'[)][{]', "space between ) and {"),
568 569 (r'\w+\* \w+', "use int *foo, not int* foo"),
569 570 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
570 571 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
571 572 (r'\w,\w', "missing whitespace after ,"),
572 573 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
573 574 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
574 575 (r'^#\s+\w', "use #foo, not # foo"),
575 576 (r'[^\n]\Z', "no trailing newline"),
576 577 (r'^\s*#import\b', "use only #include in standard C code"),
577 578 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
578 579 (r'strcat\(', "don't use strcat"),
579 580 # rules depending on implementation of repquote()
580 581 ],
581 582 # warnings
582 583 [
583 584 # rules depending on implementation of repquote()
584 585 ],
585 586 ]
586 587
587 588 cfilters = [
588 589 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
589 590 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
590 591 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
591 592 (r'(\()([^)]+\))', repcallspaces),
592 593 ]
593 594
594 595 inutilpats = [
595 596 [
596 597 (r'\bui\.', "don't use ui in util"),
597 598 ],
598 599 # warnings
599 600 [],
600 601 ]
601 602
602 603 inrevlogpats = [
603 604 [
604 605 (r'\brepo\.', "don't use repo in revlog"),
605 606 ],
606 607 # warnings
607 608 [],
608 609 ]
609 610
610 611 webtemplatefilters = []
611 612
612 613 webtemplatepats = [
613 614 [],
614 615 [
615 616 (
616 617 r'{desc(\|(?!websub|firstline)[^\|]*)+}',
617 618 'follow desc keyword with either firstline or websub',
618 619 ),
619 620 ],
620 621 ]
621 622
622 623 allfilesfilters = []
623 624
624 625 allfilespats = [
625 626 [
626 627 (
627 628 r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
628 629 'use mercurial-scm.org domain URL',
629 630 ),
630 631 (
631 632 r'mercurial@selenic\.com',
632 633 'use mercurial-scm.org domain for mercurial ML address',
633 634 ),
634 635 (
635 636 r'mercurial-devel@selenic\.com',
636 637 'use mercurial-scm.org domain for mercurial-devel ML address',
637 638 ),
638 639 ],
639 640 # warnings
640 641 [],
641 642 ]
642 643
643 644 py3pats = [
644 645 [
645 646 (
646 647 r'os\.environ',
647 648 "use encoding.environ instead (py3)",
648 649 r'#.*re-exports',
649 650 ),
650 651 (r'os\.name', "use pycompat.osname instead (py3)"),
651 652 (r'os\.getcwd', "use encoding.getcwd instead (py3)", r'#.*re-exports'),
652 653 (r'os\.sep', "use pycompat.ossep instead (py3)"),
653 654 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
654 655 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
655 656 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
656 657 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
657 658 (r'os\.getenv', "use encoding.environ.get instead"),
658 659 (r'os\.setenv', "modifying the environ dict is not preferred"),
659 660 (r'(?<!pycompat\.)xrange', "use pycompat.xrange instead (py3)"),
660 661 ],
661 662 # warnings
662 663 [],
663 664 ]
664 665
665 666 checks = [
666 667 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
667 668 ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats),
668 669 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
669 670 (
670 671 'python 3',
671 672 r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
672 673 '',
673 674 pyfilters,
674 675 py3pats,
675 676 ),
676 677 (
677 678 'core files',
678 679 r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
679 680 '',
680 681 pyfilters,
681 682 core_py_pats,
682 683 ),
683 684 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
684 685 ('c', r'.*\.[ch]$', '', cfilters, cpats),
685 686 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
686 687 (
687 688 'layering violation repo in revlog',
688 689 r'mercurial/revlog\.py',
689 690 '',
690 691 pyfilters,
691 692 inrevlogpats,
692 693 ),
693 694 (
694 695 'layering violation ui in util',
695 696 r'mercurial/util\.py',
696 697 '',
697 698 pyfilters,
698 699 inutilpats,
699 700 ),
700 701 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
701 702 (
702 703 'web template',
703 704 r'mercurial/templates/.*\.tmpl',
704 705 '',
705 706 webtemplatefilters,
706 707 webtemplatepats,
707 708 ),
708 709 ('all except for .po', r'.*(?<!\.po)$', '', allfilesfilters, allfilespats),
709 710 ]
710 711
711 712 # (desc,
712 713 # func to pick up embedded code fragments,
713 714 # list of patterns to convert target files
714 715 # list of patterns to detect errors/warnings)
715 716 embeddedchecks = [
716 717 (
717 718 'embedded python',
718 719 testparseutil.pyembedded,
719 720 embeddedpyfilters,
720 721 embeddedpypats,
721 722 )
722 723 ]
723 724
724 725
725 726 def _preparepats():
726 727 def preparefailandwarn(failandwarn):
727 728 for pats in failandwarn:
728 729 for i, pseq in enumerate(pats):
729 730 # fix-up regexes for multi-line searches
730 731 p = pseq[0]
731 732 # \s doesn't match \n (done in two steps)
732 733 # first, we replace \s that appears in a set already
733 734 p = re.sub(r'\[\\s', r'[ \\t', p)
734 735 # now we replace other \s instances.
735 736 p = re.sub(r'(?<!(\\|\[))\\s', r'[ \\t]', p)
736 737 # [^...] doesn't match newline
737 738 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
738 739
739 740 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
740 741
741 742 def preparefilters(filters):
742 743 for i, flt in enumerate(filters):
743 744 filters[i] = re.compile(flt[0]), flt[1]
744 745
745 746 for cs in (checks, embeddedchecks):
746 747 for c in cs:
747 748 failandwarn = c[-1]
748 749 preparefailandwarn(failandwarn)
749 750
750 751 filters = c[-2]
751 752 preparefilters(filters)
752 753
753 754
754 755 class norepeatlogger:
755 756 def __init__(self):
756 757 self._lastseen = None
757 758
758 759 def log(self, fname, lineno, line, msg, blame):
759 760 """print error related a to given line of a given file.
760 761
761 762 The faulty line will also be printed but only once in the case
762 763 of multiple errors.
763 764
764 765 :fname: filename
765 766 :lineno: line number
766 767 :line: actual content of the line
767 768 :msg: error message
768 769 """
769 770 msgid = fname, lineno, line
770 771 if msgid != self._lastseen:
771 772 if blame:
772 773 print("%s:%d (%s):" % (fname, lineno, blame))
773 774 else:
774 775 print("%s:%d:" % (fname, lineno))
775 776 print(" > %s" % line)
776 777 self._lastseen = msgid
777 778 print(" " + msg)
778 779
779 780
780 781 _defaultlogger = norepeatlogger()
781 782
782 783
783 784 def getblame(f):
784 785 lines = []
785 786 for l in os.popen('hg annotate -un %s' % f):
786 787 start, line = l.split(':', 1)
787 788 user, rev = start.split()
788 789 lines.append((line[1:-1], user, rev))
789 790 return lines
790 791
791 792
792 793 def checkfile(
793 794 f,
794 795 logfunc=_defaultlogger.log,
795 796 maxerr=None,
796 797 warnings=False,
797 798 blame=False,
798 799 debug=False,
799 800 lineno=True,
800 801 ):
801 802 """checks style and portability of a given file
802 803
803 804 :f: filepath
804 805 :logfunc: function used to report error
805 806 logfunc(filename, linenumber, linecontent, errormessage)
806 807 :maxerr: number of error to display before aborting.
807 808 Set to false (default) to report all errors
808 809
809 810 return True if no error is found, False otherwise.
810 811 """
811 812 result = True
812 813
813 814 try:
814 815 with opentext(f) as fp:
815 816 try:
816 817 pre = fp.read()
817 818 except UnicodeDecodeError as e:
818 819 print("%s while reading %s" % (e, f))
819 820 return result
820 821 except IOError as e:
821 822 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
822 823 return result
823 824
824 825 # context information shared while single checkfile() invocation
825 826 context = {'blamecache': None}
826 827
827 828 for name, match, magic, filters, pats in checks:
828 829 if debug:
829 830 print(name, f)
830 831 if not (re.match(match, f) or (magic and re.search(magic, pre))):
831 832 if debug:
832 833 print(
833 834 "Skipping %s for %s it doesn't match %s" % (name, match, f)
834 835 )
835 836 continue
836 837 if "no-" "check-code" in pre:
837 838 # If you're looking at this line, it's because a file has:
838 839 # no- check- code
839 840 # but the reason to output skipping is to make life for
840 841 # tests easier. So, instead of writing it with a normal
841 842 # spelling, we write it with the expected spelling from
842 843 # tests/test-check-code.t
843 844 print("Skipping %s it has no-che?k-code (glob)" % f)
844 845 return "Skip" # skip checking this file
845 846
846 847 fc = _checkfiledata(
847 848 name,
848 849 f,
849 850 pre,
850 851 filters,
851 852 pats,
852 853 context,
853 854 logfunc,
854 855 maxerr,
855 856 warnings,
856 857 blame,
857 858 debug,
858 859 lineno,
859 860 )
860 861 if fc:
861 862 result = False
862 863
863 864 if f.endswith('.t') and "no-" "check-code" not in pre:
864 865 if debug:
865 866 print("Checking embedded code in %s" % f)
866 867
867 868 prelines = pre.splitlines()
868 869 embeddederros = []
869 870 for name, embedded, filters, pats in embeddedchecks:
870 871 # "reset curmax at each repetition" treats maxerr as "max
871 872 # nubmer of errors in an actual file per entry of
872 873 # (embedded)checks"
873 874 curmaxerr = maxerr
874 875
875 876 for found in embedded(f, prelines, embeddederros):
876 877 filename, starts, ends, code = found
877 878 fc = _checkfiledata(
878 879 name,
879 880 f,
880 881 code,
881 882 filters,
882 883 pats,
883 884 context,
884 885 logfunc,
885 886 curmaxerr,
886 887 warnings,
887 888 blame,
888 889 debug,
889 890 lineno,
890 891 offset=starts - 1,
891 892 )
892 893 if fc:
893 894 result = False
894 895 if curmaxerr:
895 896 if fc >= curmaxerr:
896 897 break
897 898 curmaxerr -= fc
898 899
899 900 return result
900 901
901 902
902 903 def _checkfiledata(
903 904 name,
904 905 f,
905 906 filedata,
906 907 filters,
907 908 pats,
908 909 context,
909 910 logfunc,
910 911 maxerr,
911 912 warnings,
912 913 blame,
913 914 debug,
914 915 lineno,
915 916 offset=None,
916 917 ):
917 918 """Execute actual error check for file data
918 919
919 920 :name: of the checking category
920 921 :f: filepath
921 922 :filedata: content of a file
922 923 :filters: to be applied before checking
923 924 :pats: to detect errors
924 925 :context: a dict of information shared while single checkfile() invocation
925 926 Valid keys: 'blamecache'.
926 927 :logfunc: function used to report error
927 928 logfunc(filename, linenumber, linecontent, errormessage)
928 929 :maxerr: number of error to display before aborting, or False to
929 930 report all errors
930 931 :warnings: whether warning level checks should be applied
931 932 :blame: whether blame information should be displayed at error reporting
932 933 :debug: whether debug information should be displayed
933 934 :lineno: whether lineno should be displayed at error reporting
934 935 :offset: line number offset of 'filedata' in 'f' for checking
935 936 an embedded code fragment, or None (offset=0 is different
936 937 from offset=None)
937 938
938 939 returns number of detected errors.
939 940 """
940 941 blamecache = context['blamecache']
941 942 if offset is None:
942 943 lineoffset = 0
943 944 else:
944 945 lineoffset = offset
945 946
946 947 fc = 0
947 948 pre = post = filedata
948 949
949 950 if True: # TODO: get rid of this redundant 'if' block
950 951 for p, r in filters:
951 952 post = re.sub(p, r, post)
952 953 nerrs = len(pats[0]) # nerr elements are errors
953 954 if warnings:
954 955 pats = pats[0] + pats[1]
955 956 else:
956 957 pats = pats[0]
957 958 # print post # uncomment to show filtered version
958 959
959 960 if debug:
960 961 print("Checking %s for %s" % (name, f))
961 962
962 963 prelines = None
963 964 errors = []
964 965 for i, pat in enumerate(pats):
965 966 if len(pat) == 3:
966 967 p, msg, ignore = pat
967 968 else:
968 969 p, msg = pat
969 970 ignore = None
970 971 if i >= nerrs:
971 972 msg = "warning: " + msg
972 973
973 974 pos = 0
974 975 n = 0
975 976 for m in p.finditer(post):
976 977 if prelines is None:
977 978 prelines = pre.splitlines()
978 979 postlines = post.splitlines(True)
979 980
980 981 start = m.start()
981 982 while n < len(postlines):
982 983 step = len(postlines[n])
983 984 if pos + step > start:
984 985 break
985 986 pos += step
986 987 n += 1
987 988 l = prelines[n]
988 989
989 990 if ignore and re.search(ignore, l, re.MULTILINE):
990 991 if debug:
991 992 print(
992 993 "Skipping %s for %s:%s (ignore pattern)"
993 994 % (name, f, (n + lineoffset))
994 995 )
995 996 continue
996 997 bd = ""
997 998 if blame:
998 999 bd = 'working directory'
999 1000 if blamecache is None:
1000 1001 blamecache = getblame(f)
1001 1002 context['blamecache'] = blamecache
1002 1003 if (n + lineoffset) < len(blamecache):
1003 1004 bl, bu, br = blamecache[(n + lineoffset)]
1004 1005 if offset is None and bl == l:
1005 1006 bd = '%s@%s' % (bu, br)
1006 1007 elif offset is not None and bl.endswith(l):
1007 1008 # "offset is not None" means "checking
1008 1009 # embedded code fragment". In this case,
1009 1010 # "l" does not have information about the
1010 1011 # beginning of an *original* line in the
1011 1012 # file (e.g. ' > ').
1012 1013 # Therefore, use "str.endswith()", and
1013 1014 # show "maybe" for a little loose
1014 1015 # examination.
1015 1016 bd = '%s@%s, maybe' % (bu, br)
1016 1017
1017 1018 errors.append((f, lineno and (n + lineoffset + 1), l, msg, bd))
1018 1019
1019 1020 errors.sort()
1020 1021 for e in errors:
1021 1022 logfunc(*e)
1022 1023 fc += 1
1023 1024 if maxerr and fc >= maxerr:
1024 1025 print(" (too many errors, giving up)")
1025 1026 break
1026 1027
1027 1028 return fc
1028 1029
1029 1030
1030 1031 def main():
1031 1032 parser = optparse.OptionParser("%prog [options] [files | -]")
1032 1033 parser.add_option(
1033 1034 "-w",
1034 1035 "--warnings",
1035 1036 action="store_true",
1036 1037 help="include warning-level checks",
1037 1038 )
1038 1039 parser.add_option(
1039 1040 "-p", "--per-file", type="int", help="max warnings per file"
1040 1041 )
1041 1042 parser.add_option(
1042 1043 "-b",
1043 1044 "--blame",
1044 1045 action="store_true",
1045 1046 help="use annotate to generate blame info",
1046 1047 )
1047 1048 parser.add_option(
1048 1049 "", "--debug", action="store_true", help="show debug information"
1049 1050 )
1050 1051 parser.add_option(
1051 1052 "",
1052 1053 "--nolineno",
1053 1054 action="store_false",
1054 1055 dest='lineno',
1055 1056 help="don't show line numbers",
1056 1057 )
1057 1058
1058 1059 parser.set_defaults(
1059 1060 per_file=15, warnings=False, blame=False, debug=False, lineno=True
1060 1061 )
1061 1062 (options, args) = parser.parse_args()
1062 1063
1063 1064 if len(args) == 0:
1064 1065 check = glob.glob("*")
1065 1066 elif args == ['-']:
1066 1067 # read file list from stdin
1067 1068 check = sys.stdin.read().splitlines()
1068 1069 else:
1069 1070 check = args
1070 1071
1071 1072 _preparepats()
1072 1073
1073 1074 ret = 0
1074 1075 for f in check:
1075 1076 if not checkfile(
1076 1077 f,
1077 1078 maxerr=options.per_file,
1078 1079 warnings=options.warnings,
1079 1080 blame=options.blame,
1080 1081 debug=options.debug,
1081 1082 lineno=options.lineno,
1082 1083 ):
1083 1084 ret = 1
1084 1085 return ret
1085 1086
1086 1087
1087 1088 if __name__ == "__main__":
1088 1089 sys.exit(main())
@@ -1,292 +1,292 b''
1 1 initialize
2 2 $ make_changes() {
3 3 > d=`pwd`
4 4 > [ ! -z $1 ] && cd $1
5 5 > echo "test `basename \`pwd\``" >> test
6 6 > hg commit -Am"${2:-test}"
7 7 > r=$?
8 8 > cd $d
9 9 > return $r
10 10 > }
11 11 $ ls -1a
12 12 .
13 13 ..
14 14 $ hg init a
15 15 $ cd a
16 16 $ echo 'test' > test; hg commit -Am'test'
17 17 adding test
18 18
19 19 clone to b
20 20
21 21 $ mkdir ../b
22 22 $ cd ../b
23 23 $ hg clone ../a .
24 24 updating to branch default
25 25 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
26 26 $ echo "[extensions]" >> .hg/hgrc
27 27 $ echo "bookflow=" >> .hg/hgrc
28 28 $ hg branch X
29 29 abort: creating named branches is disabled and you should use bookmarks
30 30 (see 'hg help bookflow')
31 31 [255]
32 32 $ hg bookmark X
33 33 $ hg bookmarks
34 34 * X 0:* (glob)
35 35 $ hg bookmark X
36 36 abort: bookmark X already exists, to move use the --rev option
37 37 [255]
38 38 $ make_changes
39 39 $ hg push ../a -q
40 40
41 41 $ hg bookmarks
42 42 \* X 1:* (glob)
43 43
44 44 change a
45 45 $ cd ../a
46 46 $ hg up
47 47 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
48 48 $ echo 'test' >> test; hg commit -Am'test'
49 49
50 50
51 51 pull in b
52 52 $ cd ../b
53 53 $ hg pull -u
54 54 pulling from $TESTTMP/a
55 55 searching for changes
56 56 adding changesets
57 57 adding manifests
58 58 adding file changes
59 59 added 1 changesets with 1 changes to 1 files
60 60 new changesets * (glob)
61 61 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
62 62 (leaving bookmark X)
63 63 $ hg status
64 64 $ hg bookmarks
65 65 X 1:* (glob)
66 66
67 67 check protection of @ bookmark
68 68 $ hg bookmark @
69 69 $ hg bookmarks
70 70 \* @ 2:* (glob)
71 71 X 1:* (glob)
72 72 $ make_changes
73 73 abort: cannot commit, bookmark @ is protected
74 74 [255]
75 75
76 76 $ hg status
77 77 M test
78 78 $ hg bookmarks
79 79 \* @ 2:* (glob)
80 80 X 1:* (glob)
81 81
82 82 $ hg --config bookflow.protect= commit -Am"Updated test"
83 83
84 84 $ hg bookmarks
85 85 \* @ 3:* (glob)
86 86 X 1:* (glob)
87 87
88 88 check requirement for an active bookmark
89 89 $ hg bookmark -i
90 90 $ hg bookmarks
91 91 @ 3:* (glob)
92 92 X 1:* (glob)
93 93 $ make_changes
94 94 abort: cannot commit without an active bookmark
95 95 [255]
96 96 $ hg revert test
97 97 $ rm test.orig
98 98 $ hg status
99 99
100 100
101 101 make the bookmark move by updating it on a, and then pulling
102 102 # add a commit to a
103 103 $ cd ../a
104 104 $ hg bookmark X
105 105 $ hg bookmarks
106 106 \* X 2:* (glob)
107 107 $ make_changes
108 108 $ hg bookmarks
109 109 * X 3:81af7977fdb9
110 110
111 111 # go back to b, and check out X
112 112 $ cd ../b
113 113 $ hg up X
114 114 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
115 115 (activating bookmark X)
116 116 $ hg bookmarks
117 117 @ 3:* (glob)
118 118 \* X 1:* (glob)
119 119
120 120 # pull, this should move the bookmark forward, because it was changed remotely
121 121 $ hg pull -u | grep "updating to active bookmark X"
122 122 updating to active bookmark X
123 123
124 124 $ hg bookmarks
125 125 @ 3:* (glob)
126 126 * X 4:81af7977fdb9
127 127
128 128 the bookmark should not move if it diverged from remote
129 129 $ hg -R ../a status
130 130 $ hg -R ../b status
131 131 $ make_changes ../a
132 132 $ make_changes ../b
133 133 $ hg -R ../a status
134 134 $ hg -R ../b status
135 135 $ hg -R ../a bookmarks
136 136 * X 4:238292f60a57
137 137 $ hg -R ../b bookmarks
138 138 @ 3:* (glob)
139 139 * X 5:096f7e86892d
140 140 $ cd ../b
141 141 $ # make sure we cannot push after bookmarks diverged
142 142 $ hg push -B X | grep abort
143 143 abort: push creates new remote head * with bookmark 'X' (glob)
144 144 (pull and merge or see 'hg help push' for details about pushing new heads)
145 145 [1]
146 146 $ hg pull -u | grep divergent
147 147 divergent bookmark X stored as X@default
148 148 1 other divergent bookmarks for "X"
149 149 $ hg bookmarks
150 150 @ 3:* (glob)
151 151 * X 5:096f7e86892d
152 152 X@default 6:238292f60a57
153 153 $ hg id -in
154 154 096f7e86892d 5
155 155 $ make_changes
156 156 $ hg status
157 157 $ hg bookmarks
158 158 @ 3:* (glob)
159 159 * X 7:227f941aeb07
160 160 X@default 6:238292f60a57
161 161
162 162 now merge with the remote bookmark
163 163 $ hg merge X@default --tool :local -q
164 164 $ hg status
165 165 M test
166 166 $ hg commit -m"Merged with X@default"
167 167 $ hg bookmarks
168 168 @ 3:* (glob)
169 169 * X 8:26fed9bb3219
170 170 $ hg push -B X | grep bookmark
171 171 pushing to $TESTTMP/a (?)
172 172 updating bookmark X
173 173 $ cd ../a
174 174 $ hg up -q
175 175 $ hg bookmarks
176 176 * X 7:26fed9bb3219
177 177
178 178 test hg pull when there is more than one descendant
179 179 $ cd ../a
180 180 $ hg bookmark Z
181 181 $ hg bookmark Y
182 182 $ make_changes . YY
183 183 $ hg up Z -q
184 184 $ make_changes . ZZ
185 185 created new head
186 186 $ hg bookmarks
187 187 X 7:26fed9bb3219
188 188 Y 8:131e663dbd2a
189 189 * Z 9:b74a4149df25
190 190 $ hg log -r 'p1(Y)' -r 'p1(Z)' -T '{rev}\n' # prove that Y and Z share the same parent
191 191 7
192 192 $ hg log -r 'Y%Z' -T '{rev}\n' # revs in Y but not in Z
193 193 8
194 194 $ hg log -r 'Z%Y' -T '{rev}\n' # revs in Z but not in Y
195 195 9
196 196 $ cd ../b
197 197 $ hg pull -uq
198 198 $ hg id
199 199 b74a4149df25 tip Z
200 200 $ hg bookmarks | grep \* # no active bookmark
201 201 [1]
202 202
203 203
204 204 test shelving
205 205 $ cd ../a
206 206 $ echo anotherfile > anotherfile # this change should not conflict
207 207 $ hg add anotherfile
208 208 $ hg commit -m"Change in a"
209 209 $ cd ../b
210 210 $ hg up Z | grep Z
211 211 (activating bookmark Z)
212 212 $ hg book | grep \* # make sure active bookmark
213 213 \* Z 10:* (glob)
214 214 $ echo "test b" >> test
215 215 $ hg diff --stat
216 216 test | 1 +
217 217 1 files changed, 1 insertions(+), 0 deletions(-)
218 218 $ hg shelve
219 219 shelved as Z
220 220 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
221 221 $ hg pull -uq
222 222 $ hg unshelve
223 223 unshelving change 'Z'
224 224 rebasing shelved changes
225 225 $ hg diff --stat
226 226 test | 1 +
227 227 1 files changed, 1 insertions(+), 0 deletions(-)
228 228
229 229
230 230 make the bookmark move by updating it on a, and then pulling with a local change
231 231 # add a commit to a
232 232 $ cd ../a
233 $ hg up -C X |fgrep "activating bookmark X"
233 $ hg up -C X |grep -F "activating bookmark X"
234 234 (activating bookmark X)
235 235 # go back to b, and check out X
236 236 $ cd ../b
237 $ hg up -C X |fgrep "activating bookmark X"
237 $ hg up -C X |grep -F "activating bookmark X"
238 238 (activating bookmark X)
239 239 # update and push from a
240 240 $ make_changes ../a
241 241 created new head
242 242 $ echo "more" >> test
243 $ hg pull -u 2>&1 | fgrep -v TESTTMP| fgrep -v "searching for changes" | fgrep -v adding
243 $ hg pull -u 2>&1 | grep -F -v TESTTMP| grep -F -v "searching for changes" | grep -F -v adding
244 244 pulling from $TESTTMP/a
245 245 updating bookmark X
246 246 added 1 changesets with 0 changes to 0 files (+1 heads)
247 247 new changesets * (glob)
248 248 updating to active bookmark X
249 249 merging test
250 250 warning: conflicts while merging test! (edit, then use 'hg resolve --mark')
251 251 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
252 252 use 'hg resolve' to retry unresolved file merges
253 253 $ hg update -Cq
254 254 $ rm test.orig
255 255
256 256 make sure that commits aren't possible if working directory is not pointing to active bookmark
257 257 $ hg -R ../a status
258 258 $ hg -R ../b status
259 259 $ hg -R ../a id -i
260 260 36a6e592ec06
261 261 $ hg -R ../a book | grep X
262 262 \* X \d+:36a6e592ec06 (re)
263 263 $ hg -R ../b id -i
264 264 36a6e592ec06
265 265 $ hg -R ../b book | grep X
266 266 \* X \d+:36a6e592ec06 (re)
267 267 $ make_changes ../a
268 268 $ hg -R ../a book | grep X
269 269 \* X \d+:f73a71c992b8 (re)
270 270 $ cd ../b
271 271 $ hg pull 2>&1 | grep -v add | grep -v pulling | grep -v searching | grep -v changeset
272 272 updating bookmark X
273 273 (run 'hg update' to get a working copy)
274 274 working directory out of sync with active bookmark, run 'hg up X'
275 275 $ hg id -i # we're still on the old commit
276 276 36a6e592ec06
277 277 $ hg book | grep X # while the bookmark moved
278 278 \* X \d+:f73a71c992b8 (re)
279 279 $ make_changes
280 280 abort: cannot commit, working directory out of sync with active bookmark
281 281 (run 'hg up X')
282 282 [255]
283 283 $ hg up -Cq -r . # cleanup local changes
284 284 $ hg status
285 285 $ hg id -i # we're still on the old commit
286 286 36a6e592ec06
287 287 $ hg up X -q
288 288 $ hg id -i # now we're on X
289 289 f73a71c992b8
290 290 $ hg book | grep X
291 291 \* X \d+:f73a71c992b8 (re)
292 292
General Comments 0
You need to be logged in to leave comments. Login now