##// END OF EJS Templates
tests: fix the check-code rule for testing non-existent files...
Matt Harbison -
r35463:e28dedf4 @4 default
parent child Browse files
Show More
@@ -1,737 +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 184 (r'^ [^$>].*27\.0\.0\.1',
185 185 'use $LOCALIP not an explicit loopback address'),
186 186 (r'^ (?![>$] ).*\$LOCALIP.*[^)]$',
187 187 'mark $LOCALIP output lines with (glob) to help tests in BSD jails'),
188 (r'^ (cat|find): .*: No such file or directory',
188 (r'^ (cat|find): .*: \$ENOENT\$',
189 189 'use test -f to test for file existence'),
190 190 (r'^ diff -[^ -]*p',
191 191 "don't use (external) diff with -p for portability"),
192 192 (r' readlink ', 'use readlink.py instead of readlink'),
193 193 (r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
194 194 "glob timezone field in diff output for portability"),
195 195 (r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
196 196 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability"),
197 197 (r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
198 198 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability"),
199 199 (r'^ @@ -[0-9]+ [+][0-9]+ @@',
200 200 "use '@@ -N* +N* @@ (glob)' style chunk header for portability"),
201 201 (uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
202 202 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
203 203 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)"),
204 204 ],
205 205 # warnings
206 206 [
207 207 (r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$',
208 208 "glob match with no glob string (?, *, /, and $LOCALIP)"),
209 209 ]
210 210 ]
211 211
212 212 # transform plain test rules to unified test's
213 213 for i in [0, 1]:
214 214 for tp in testpats[i]:
215 215 p = tp[0]
216 216 m = tp[1]
217 217 if p.startswith(r'^'):
218 218 p = r"^ [$>] (%s)" % p[1:]
219 219 else:
220 220 p = r"^ [$>] .*(%s)" % p
221 221 utestpats[i].append((p, m) + tp[2:])
222 222
223 223 # don't transform the following rules:
224 224 # " > \t" and " \t" should be allowed in unified tests
225 225 testpats[0].append((r'^( *)\t', "don't use tabs to indent"))
226 226 utestpats[0].append((r'^( ?)\t', "don't use tabs to indent"))
227 227
228 228 utestfilters = [
229 229 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
230 230 (r"( +)(#([^!][^\n]*\S)?)", repcomment),
231 231 ]
232 232
233 233 pypats = [
234 234 [
235 235 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
236 236 "tuple parameter unpacking not available in Python 3+"),
237 237 (r'lambda\s*\(.*,.*\)',
238 238 "tuple parameter unpacking not available in Python 3+"),
239 239 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
240 240 (r'(?<!\.)\breduce\s*\(.*', "reduce is not available in Python 3+"),
241 241 (r'\bdict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
242 242 'dict-from-generator'),
243 243 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
244 244 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
245 245 (r'^\s*\t', "don't use tabs"),
246 246 (r'\S;\s*\n', "semicolon"),
247 247 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
248 248 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
249 249 (r'(\w|\)),\w', "missing whitespace after ,"),
250 250 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
251 251 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
252 252 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
253 253 ((
254 254 # a line ending with a colon, potentially with trailing comments
255 255 r':([ \t]*#[^\n]*)?\n'
256 256 # one that is not a pass and not only a comment
257 257 r'(?P<indent>[ \t]+)[^#][^\n]+\n'
258 258 # more lines at the same indent level
259 259 r'((?P=indent)[^\n]+\n)*'
260 260 # a pass at the same indent level, which is bogus
261 261 r'(?P=indent)pass[ \t\n#]'
262 262 ), 'omit superfluous pass'),
263 263 (r'.{81}', "line too long"),
264 264 (r'[^\n]\Z', "no trailing newline"),
265 265 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
266 266 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
267 267 # "don't use underbars in identifiers"),
268 268 (r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ',
269 269 "don't use camelcase in identifiers", r'#.*camelcase-required'),
270 270 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
271 271 "linebreak after :"),
272 272 (r'class\s[^( \n]+:', "old-style class, use class foo(object)",
273 273 r'#.*old-style'),
274 274 (r'class\s[^( \n]+\(\):',
275 275 "class foo() creates old style object, use class foo(object)",
276 276 r'#.*old-style'),
277 277 (r'\b(%s)\(' % '|'.join(k for k in keyword.kwlist
278 278 if k not in ('print', 'exec')),
279 279 "Python keyword is not a function"),
280 280 (r',]', "unneeded trailing ',' in list"),
281 281 # (r'class\s[A-Z][^\(]*\((?!Exception)',
282 282 # "don't capitalize non-exception classes"),
283 283 # (r'in range\(', "use xrange"),
284 284 # (r'^\s*print\s+', "avoid using print in core and extensions"),
285 285 (r'[\x80-\xff]', "non-ASCII character literal"),
286 286 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
287 287 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
288 288 "gratuitous whitespace after Python keyword"),
289 289 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
290 290 # (r'\s\s=', "gratuitous whitespace before ="),
291 291 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
292 292 "missing whitespace around operator"),
293 293 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
294 294 "missing whitespace around operator"),
295 295 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
296 296 "missing whitespace around operator"),
297 297 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
298 298 "wrong whitespace around ="),
299 299 (r'\([^()]*( =[^=]|[^<>!=]= )',
300 300 "no whitespace around = for named parameters"),
301 301 (r'raise Exception', "don't raise generic exceptions"),
302 302 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
303 303 "don't use old-style two-argument raise, use Exception(message)"),
304 304 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
305 305 (r' [=!]=\s+(True|False|None)',
306 306 "comparison with singleton, use 'is' or 'is not' instead"),
307 307 (r'^\s*(while|if) [01]:',
308 308 "use True/False for constant Boolean expression"),
309 309 (r'^\s*if False(:| +and)', 'Remove code instead of using `if False`'),
310 310 (r'(?:(?<!def)\s+|\()hasattr\(',
311 311 'hasattr(foo, bar) is broken on py2, use util.safehasattr(foo, bar) '
312 312 'instead', r'#.*hasattr-py3-only'),
313 313 (r'opener\([^)]*\).read\(',
314 314 "use opener.read() instead"),
315 315 (r'opener\([^)]*\).write\(',
316 316 "use opener.write() instead"),
317 317 (r'[\s\(](open|file)\([^)]*\)\.read\(',
318 318 "use util.readfile() instead"),
319 319 (r'[\s\(](open|file)\([^)]*\)\.write\(',
320 320 "use util.writefile() instead"),
321 321 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
322 322 "always assign an opened file to a variable, and close it afterwards"),
323 323 (r'[\s\(](open|file)\([^)]*\)\.',
324 324 "always assign an opened file to a variable, and close it afterwards"),
325 325 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
326 326 (r'\.debug\(\_', "don't mark debug messages for translation"),
327 327 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
328 328 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
329 329 (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
330 330 'legacy exception syntax; use "as" instead of ","'),
331 331 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
332 332 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
333 333 (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
334 334 (r'os\.path\.join\(.*, *(""|\'\')\)',
335 335 "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
336 336 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
337 337 # XXX only catch mutable arguments on the first line of the definition
338 338 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
339 339 (r'\butil\.Abort\b', "directly use error.Abort"),
340 340 (r'^@(\w*\.)?cachefunc', "module-level @cachefunc is risky, please avoid"),
341 341 (r'^import atexit', "don't use atexit, use ui.atexit"),
342 342 (r'^import Queue', "don't use Queue, use util.queue + util.empty"),
343 343 (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
344 344 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
345 345 (r'^import SocketServer', "don't use SockerServer, use util.socketserver"),
346 346 (r'^import urlparse', "don't use urlparse, use util.urlreq"),
347 347 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
348 348 (r'^import cPickle', "don't use cPickle, use util.pickle"),
349 349 (r'^import pickle', "don't use pickle, use util.pickle"),
350 350 (r'^import httplib', "don't use httplib, use util.httplib"),
351 351 (r'^import BaseHTTPServer', "use util.httpserver instead"),
352 352 (r'^(from|import) mercurial\.(cext|pure|cffi)',
353 353 "use mercurial.policy.importmod instead"),
354 354 (r'\.next\(\)', "don't use .next(), use next(...)"),
355 355 (r'([a-z]*).revision\(\1\.node\(',
356 356 "don't convert rev to node before passing to revision(nodeorrev)"),
357 357 (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
358 358
359 359 # rules depending on implementation of repquote()
360 360 (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
361 361 'string join across lines with no space'),
362 362 (r'''(?x)ui\.(status|progress|write|note|warn)\(
363 363 [ \t\n#]*
364 364 (?# any strings/comments might precede a string, which
365 365 # contains translatable message)
366 366 ((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
367 367 (?# sequence consisting of below might precede translatable message
368 368 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
369 369 # - escaped character: "\\", "\n", "\0" ...
370 370 # - character other than '%', 'b' as '\', and 'x' as alphabet)
371 371 (['"]|\'\'\'|""")
372 372 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
373 373 (?# this regexp can't use [^...] style,
374 374 # because _preparepats forcibly adds "\n" into [^...],
375 375 # even though this regexp wants match it against "\n")''',
376 376 "missing _() in ui message (use () to hide false-positives)"),
377 377 ],
378 378 # warnings
379 379 [
380 380 # rules depending on implementation of repquote()
381 381 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
382 382 ]
383 383 ]
384 384
385 385 pyfilters = [
386 386 (r"""(?msx)(?P<comment>\#.*?$)|
387 387 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
388 388 (?P<text>(([^\\]|\\.)*?))
389 389 (?P=quote))""", reppython),
390 390 ]
391 391
392 392 # non-filter patterns
393 393 pynfpats = [
394 394 [
395 395 (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
396 396 (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"),
397 397 (r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]',
398 398 "use pycompat.isdarwin"),
399 399 ],
400 400 # warnings
401 401 [],
402 402 ]
403 403
404 404 # extension non-filter patterns
405 405 pyextnfpats = [
406 406 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
407 407 # warnings
408 408 [],
409 409 ]
410 410
411 411 txtfilters = []
412 412
413 413 txtpats = [
414 414 [
415 415 ('\s$', 'trailing whitespace'),
416 416 ('.. note::[ \n][^\n]', 'add two newlines after note::')
417 417 ],
418 418 []
419 419 ]
420 420
421 421 cpats = [
422 422 [
423 423 (r'//', "don't use //-style comments"),
424 424 (r'\S\t', "don't use tabs except for indent"),
425 425 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
426 426 (r'.{81}', "line too long"),
427 427 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
428 428 (r'return\(', "return is not a function"),
429 429 (r' ;', "no space before ;"),
430 430 (r'[^;] \)', "no space before )"),
431 431 (r'[)][{]', "space between ) and {"),
432 432 (r'\w+\* \w+', "use int *foo, not int* foo"),
433 433 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
434 434 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
435 435 (r'\w,\w', "missing whitespace after ,"),
436 436 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
437 437 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
438 438 (r'^#\s+\w', "use #foo, not # foo"),
439 439 (r'[^\n]\Z', "no trailing newline"),
440 440 (r'^\s*#import\b', "use only #include in standard C code"),
441 441 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
442 442 (r'strcat\(', "don't use strcat"),
443 443
444 444 # rules depending on implementation of repquote()
445 445 ],
446 446 # warnings
447 447 [
448 448 # rules depending on implementation of repquote()
449 449 ]
450 450 ]
451 451
452 452 cfilters = [
453 453 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
454 454 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
455 455 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
456 456 (r'(\()([^)]+\))', repcallspaces),
457 457 ]
458 458
459 459 inutilpats = [
460 460 [
461 461 (r'\bui\.', "don't use ui in util"),
462 462 ],
463 463 # warnings
464 464 []
465 465 ]
466 466
467 467 inrevlogpats = [
468 468 [
469 469 (r'\brepo\.', "don't use repo in revlog"),
470 470 ],
471 471 # warnings
472 472 []
473 473 ]
474 474
475 475 webtemplatefilters = []
476 476
477 477 webtemplatepats = [
478 478 [],
479 479 [
480 480 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
481 481 'follow desc keyword with either firstline or websub'),
482 482 ]
483 483 ]
484 484
485 485 allfilesfilters = []
486 486
487 487 allfilespats = [
488 488 [
489 489 (r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
490 490 'use mercurial-scm.org domain URL'),
491 491 (r'mercurial@selenic\.com',
492 492 'use mercurial-scm.org domain for mercurial ML address'),
493 493 (r'mercurial-devel@selenic\.com',
494 494 'use mercurial-scm.org domain for mercurial-devel ML address'),
495 495 ],
496 496 # warnings
497 497 [],
498 498 ]
499 499
500 500 py3pats = [
501 501 [
502 502 (r'os\.environ', "use encoding.environ instead (py3)", r'#.*re-exports'),
503 503 (r'os\.name', "use pycompat.osname instead (py3)"),
504 504 (r'os\.getcwd', "use pycompat.getcwd instead (py3)"),
505 505 (r'os\.sep', "use pycompat.ossep instead (py3)"),
506 506 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
507 507 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
508 508 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
509 509 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
510 510 (r'os\.getenv', "use encoding.environ.get instead"),
511 511 (r'os\.setenv', "modifying the environ dict is not preferred"),
512 512 ],
513 513 # warnings
514 514 [],
515 515 ]
516 516
517 517 checks = [
518 518 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
519 519 ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats),
520 520 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
521 521 ('python 3', r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
522 522 '', pyfilters, py3pats),
523 523 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
524 524 ('c', r'.*\.[ch]$', '', cfilters, cpats),
525 525 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
526 526 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
527 527 pyfilters, inrevlogpats),
528 528 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
529 529 inutilpats),
530 530 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
531 531 ('web template', r'mercurial/templates/.*\.tmpl', '',
532 532 webtemplatefilters, webtemplatepats),
533 533 ('all except for .po', r'.*(?<!\.po)$', '',
534 534 allfilesfilters, allfilespats),
535 535 ]
536 536
537 537 def _preparepats():
538 538 for c in checks:
539 539 failandwarn = c[-1]
540 540 for pats in failandwarn:
541 541 for i, pseq in enumerate(pats):
542 542 # fix-up regexes for multi-line searches
543 543 p = pseq[0]
544 544 # \s doesn't match \n
545 545 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
546 546 # [^...] doesn't match newline
547 547 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
548 548
549 549 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
550 550 filters = c[3]
551 551 for i, flt in enumerate(filters):
552 552 filters[i] = re.compile(flt[0]), flt[1]
553 553
554 554 class norepeatlogger(object):
555 555 def __init__(self):
556 556 self._lastseen = None
557 557
558 558 def log(self, fname, lineno, line, msg, blame):
559 559 """print error related a to given line of a given file.
560 560
561 561 The faulty line will also be printed but only once in the case
562 562 of multiple errors.
563 563
564 564 :fname: filename
565 565 :lineno: line number
566 566 :line: actual content of the line
567 567 :msg: error message
568 568 """
569 569 msgid = fname, lineno, line
570 570 if msgid != self._lastseen:
571 571 if blame:
572 572 print("%s:%d (%s):" % (fname, lineno, blame))
573 573 else:
574 574 print("%s:%d:" % (fname, lineno))
575 575 print(" > %s" % line)
576 576 self._lastseen = msgid
577 577 print(" " + msg)
578 578
579 579 _defaultlogger = norepeatlogger()
580 580
581 581 def getblame(f):
582 582 lines = []
583 583 for l in os.popen('hg annotate -un %s' % f):
584 584 start, line = l.split(':', 1)
585 585 user, rev = start.split()
586 586 lines.append((line[1:-1], user, rev))
587 587 return lines
588 588
589 589 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
590 590 blame=False, debug=False, lineno=True):
591 591 """checks style and portability of a given file
592 592
593 593 :f: filepath
594 594 :logfunc: function used to report error
595 595 logfunc(filename, linenumber, linecontent, errormessage)
596 596 :maxerr: number of error to display before aborting.
597 597 Set to false (default) to report all errors
598 598
599 599 return True if no error is found, False otherwise.
600 600 """
601 601 blamecache = None
602 602 result = True
603 603
604 604 try:
605 605 with opentext(f) as fp:
606 606 try:
607 607 pre = post = fp.read()
608 608 except UnicodeDecodeError as e:
609 609 print("%s while reading %s" % (e, f))
610 610 return result
611 611 except IOError as e:
612 612 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
613 613 return result
614 614
615 615 for name, match, magic, filters, pats in checks:
616 616 post = pre # discard filtering result of previous check
617 617 if debug:
618 618 print(name, f)
619 619 fc = 0
620 620 if not (re.match(match, f) or (magic and re.search(magic, pre))):
621 621 if debug:
622 622 print("Skipping %s for %s it doesn't match %s" % (
623 623 name, match, f))
624 624 continue
625 625 if "no-" "check-code" in pre:
626 626 # If you're looking at this line, it's because a file has:
627 627 # no- check- code
628 628 # but the reason to output skipping is to make life for
629 629 # tests easier. So, instead of writing it with a normal
630 630 # spelling, we write it with the expected spelling from
631 631 # tests/test-check-code.t
632 632 print("Skipping %s it has no-che?k-code (glob)" % f)
633 633 return "Skip" # skip checking this file
634 634 for p, r in filters:
635 635 post = re.sub(p, r, post)
636 636 nerrs = len(pats[0]) # nerr elements are errors
637 637 if warnings:
638 638 pats = pats[0] + pats[1]
639 639 else:
640 640 pats = pats[0]
641 641 # print post # uncomment to show filtered version
642 642
643 643 if debug:
644 644 print("Checking %s for %s" % (name, f))
645 645
646 646 prelines = None
647 647 errors = []
648 648 for i, pat in enumerate(pats):
649 649 if len(pat) == 3:
650 650 p, msg, ignore = pat
651 651 else:
652 652 p, msg = pat
653 653 ignore = None
654 654 if i >= nerrs:
655 655 msg = "warning: " + msg
656 656
657 657 pos = 0
658 658 n = 0
659 659 for m in p.finditer(post):
660 660 if prelines is None:
661 661 prelines = pre.splitlines()
662 662 postlines = post.splitlines(True)
663 663
664 664 start = m.start()
665 665 while n < len(postlines):
666 666 step = len(postlines[n])
667 667 if pos + step > start:
668 668 break
669 669 pos += step
670 670 n += 1
671 671 l = prelines[n]
672 672
673 673 if ignore and re.search(ignore, l, re.MULTILINE):
674 674 if debug:
675 675 print("Skipping %s for %s:%s (ignore pattern)" % (
676 676 name, f, n))
677 677 continue
678 678 bd = ""
679 679 if blame:
680 680 bd = 'working directory'
681 681 if not blamecache:
682 682 blamecache = getblame(f)
683 683 if n < len(blamecache):
684 684 bl, bu, br = blamecache[n]
685 685 if bl == l:
686 686 bd = '%s@%s' % (bu, br)
687 687
688 688 errors.append((f, lineno and n + 1, l, msg, bd))
689 689 result = False
690 690
691 691 errors.sort()
692 692 for e in errors:
693 693 logfunc(*e)
694 694 fc += 1
695 695 if maxerr and fc >= maxerr:
696 696 print(" (too many errors, giving up)")
697 697 break
698 698
699 699 return result
700 700
701 701 def main():
702 702 parser = optparse.OptionParser("%prog [options] [files | -]")
703 703 parser.add_option("-w", "--warnings", action="store_true",
704 704 help="include warning-level checks")
705 705 parser.add_option("-p", "--per-file", type="int",
706 706 help="max warnings per file")
707 707 parser.add_option("-b", "--blame", action="store_true",
708 708 help="use annotate to generate blame info")
709 709 parser.add_option("", "--debug", action="store_true",
710 710 help="show debug information")
711 711 parser.add_option("", "--nolineno", action="store_false",
712 712 dest='lineno', help="don't show line numbers")
713 713
714 714 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
715 715 lineno=True)
716 716 (options, args) = parser.parse_args()
717 717
718 718 if len(args) == 0:
719 719 check = glob.glob("*")
720 720 elif args == ['-']:
721 721 # read file list from stdin
722 722 check = sys.stdin.read().splitlines()
723 723 else:
724 724 check = args
725 725
726 726 _preparepats()
727 727
728 728 ret = 0
729 729 for f in check:
730 730 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
731 731 blame=options.blame, debug=options.debug,
732 732 lineno=options.lineno):
733 733 ret = 1
734 734 return ret
735 735
736 736 if __name__ == "__main__":
737 737 sys.exit(main())
@@ -1,683 +1,682 b''
1 1 # Initial setup
2 2
3 3 $ cat >> $HGRCPATH << EOF
4 4 > [extensions]
5 5 > lfs=
6 6 > [lfs]
7 7 > threshold=1000B
8 8 > EOF
9 9
10 10 $ LONG=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
11 11
12 12 # Prepare server and enable extension
13 13 $ hg init server
14 14 $ hg clone -q server client
15 15 $ cd client
16 16
17 17 # Commit small file
18 18 $ echo s > smallfile
19 19 $ hg commit -Aqm "add small file"
20 20
21 21 # Commit large file
22 22 $ echo $LONG > largefile
23 23 $ grep lfs .hg/requires
24 24 [1]
25 25 $ hg commit --traceback -Aqm "add large file"
26 26 $ grep lfs .hg/requires
27 27 lfs
28 28
29 29 # Ensure metadata is stored
30 30 $ hg debugdata largefile 0
31 31 version https://git-lfs.github.com/spec/v1
32 32 oid sha256:f11e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
33 33 size 1501
34 34 x-is-binary 0
35 35
36 36 # Check the blobstore is populated
37 37 $ find .hg/store/lfs/objects | sort
38 38 .hg/store/lfs/objects
39 39 .hg/store/lfs/objects/f1
40 40 .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
41 41
42 42 # Check the blob stored contains the actual contents of the file
43 43 $ cat .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
44 44 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
45 45
46 46 # Push changes to the server
47 47
48 48 $ hg push
49 49 pushing to $TESTTMP/server
50 50 searching for changes
51 51 abort: lfs.url needs to be configured
52 52 [255]
53 53
54 54 $ cat >> $HGRCPATH << EOF
55 55 > [lfs]
56 56 > url=file:$TESTTMP/dummy-remote/
57 57 > EOF
58 58
59 59 $ hg push -v | egrep -v '^(uncompressed| )'
60 60 pushing to $TESTTMP/server
61 61 searching for changes
62 62 2 changesets found
63 63 adding changesets
64 64 adding manifests
65 65 adding file changes
66 66 added 2 changesets with 2 changes to 2 files
67 67
68 68 # Unknown URL scheme
69 69
70 70 $ hg push --config lfs.url=ftp://foobar
71 71 abort: lfs: unknown url scheme: ftp
72 72 [255]
73 73
74 74 $ cd ../
75 75
76 76 # Initialize new client (not cloning) and setup extension
77 77 $ hg init client2
78 78 $ cd client2
79 79 $ cat >> .hg/hgrc <<EOF
80 80 > [paths]
81 81 > default = $TESTTMP/server
82 82 > EOF
83 83
84 84 # Pull from server
85 85 $ hg pull default
86 86 pulling from $TESTTMP/server
87 87 requesting all changes
88 88 adding changesets
89 89 adding manifests
90 90 adding file changes
91 91 added 2 changesets with 2 changes to 2 files
92 92 new changesets b29ba743f89d:00c137947d30
93 93 (run 'hg update' to get a working copy)
94 94
95 95 # Check the blobstore is not yet populated
96 96 $ [ -d .hg/store/lfs/objects ]
97 97 [1]
98 98
99 99 # Update to the last revision containing the large file
100 100 $ hg update
101 101 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 102
103 103 # Check the blobstore has been populated on update
104 104 $ find .hg/store/lfs/objects | sort
105 105 .hg/store/lfs/objects
106 106 .hg/store/lfs/objects/f1
107 107 .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
108 108
109 109 # Check the contents of the file are fetched from blobstore when requested
110 110 $ hg cat -r . largefile
111 111 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
112 112
113 113 # Check the file has been copied in the working copy
114 114 $ cat largefile
115 115 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
116 116
117 117 $ cd ..
118 118
119 119 # Check rename, and switch between large and small files
120 120
121 121 $ hg init repo3
122 122 $ cd repo3
123 123 $ cat >> .hg/hgrc << EOF
124 124 > [lfs]
125 125 > threshold=10B
126 126 > EOF
127 127
128 128 $ echo LONGER-THAN-TEN-BYTES-WILL-TRIGGER-LFS > large
129 129 $ echo SHORTER > small
130 130 $ hg add . -q
131 131 $ hg commit -m 'commit with lfs content'
132 132
133 133 $ hg mv large l
134 134 $ hg mv small s
135 135 $ hg commit -m 'renames'
136 136
137 137 $ echo SHORT > l
138 138 $ echo BECOME-LARGER-FROM-SHORTER > s
139 139 $ hg commit -m 'large to small, small to large'
140 140
141 141 $ echo 1 >> l
142 142 $ echo 2 >> s
143 143 $ hg commit -m 'random modifications'
144 144
145 145 $ echo RESTORE-TO-BE-LARGE > l
146 146 $ echo SHORTER > s
147 147 $ hg commit -m 'switch large and small again'
148 148
149 149 # Test lfs_files template
150 150
151 151 $ hg log -r 'all()' -T '{rev} {join(lfs_files, ", ")}\n'
152 152 0 large
153 153 1 l
154 154 2 s
155 155 3 s
156 156 4 l
157 157
158 158 # Push and pull the above repo
159 159
160 160 $ hg --cwd .. init repo4
161 161 $ hg push ../repo4
162 162 pushing to ../repo4
163 163 searching for changes
164 164 adding changesets
165 165 adding manifests
166 166 adding file changes
167 167 added 5 changesets with 10 changes to 4 files
168 168
169 169 $ hg --cwd .. init repo5
170 170 $ hg --cwd ../repo5 pull ../repo3
171 171 pulling from ../repo3
172 172 requesting all changes
173 173 adding changesets
174 174 adding manifests
175 175 adding file changes
176 176 added 5 changesets with 10 changes to 4 files
177 177 new changesets fd47a419c4f7:5adf850972b9
178 178 (run 'hg update' to get a working copy)
179 179
180 180 $ cd ..
181 181
182 182 # Test clone
183 183
184 184 $ hg init repo6
185 185 $ cd repo6
186 186 $ cat >> .hg/hgrc << EOF
187 187 > [lfs]
188 188 > threshold=30B
189 189 > EOF
190 190
191 191 $ echo LARGE-BECAUSE-IT-IS-MORE-THAN-30-BYTES > large
192 192 $ echo SMALL > small
193 193 $ hg commit -Aqm 'create a lfs file' large small
194 194 $ hg debuglfsupload -r 'all()' -v
195 195
196 196 $ cd ..
197 197
198 198 $ hg clone repo6 repo7
199 199 updating to branch default
200 200 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
201 201 $ cd repo7
202 202 $ hg config extensions --debug | grep lfs
203 203 $TESTTMP/repo7/.hg/hgrc:*: extensions.lfs= (glob)
204 204 $ cat large
205 205 LARGE-BECAUSE-IT-IS-MORE-THAN-30-BYTES
206 206 $ cat small
207 207 SMALL
208 208
209 209 $ cd ..
210 210
211 211 $ hg --config extensions.share= share repo7 sharedrepo
212 212 updating working directory
213 213 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
214 214 $ hg -R sharedrepo config extensions --debug | grep lfs
215 215 $TESTTMP/sharedrepo/.hg/hgrc:*: extensions.lfs= (glob)
216 216
217 217 # Test rename and status
218 218
219 219 $ hg init repo8
220 220 $ cd repo8
221 221 $ cat >> .hg/hgrc << EOF
222 222 > [lfs]
223 223 > threshold=10B
224 224 > EOF
225 225
226 226 $ echo THIS-IS-LFS-BECAUSE-10-BYTES > a1
227 227 $ echo SMALL > a2
228 228 $ hg commit -m a -A a1 a2
229 229 $ hg status
230 230 $ hg mv a1 b1
231 231 $ hg mv a2 a1
232 232 $ hg mv b1 a2
233 233 $ hg commit -m b
234 234 $ hg status
235 235 >>> with open('a2', 'wb') as f:
236 236 ... f.write(b'\1\nSTART-WITH-HG-FILELOG-METADATA')
237 237 >>> with open('a1', 'wb') as f:
238 238 ... f.write(b'\1\nMETA\n')
239 239 $ hg commit -m meta
240 240 $ hg status
241 241 $ hg log -T '{rev}: {file_copies} | {file_dels} | {file_adds}\n'
242 242 2: | |
243 243 1: a1 (a2)a2 (a1) | |
244 244 0: | | a1 a2
245 245
246 246 $ for n in a1 a2; do
247 247 > for r in 0 1 2; do
248 248 > printf '\n%s @ %s\n' $n $r
249 249 > hg debugdata $n $r
250 250 > done
251 251 > done
252 252
253 253 a1 @ 0
254 254 version https://git-lfs.github.com/spec/v1
255 255 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
256 256 size 29
257 257 x-is-binary 0
258 258
259 259 a1 @ 1
260 260 \x01 (esc)
261 261 copy: a2
262 262 copyrev: 50470ad23cf937b1f4b9f80bfe54df38e65b50d9
263 263 \x01 (esc)
264 264 SMALL
265 265
266 266 a1 @ 2
267 267 \x01 (esc)
268 268 \x01 (esc)
269 269 \x01 (esc)
270 270 META
271 271
272 272 a2 @ 0
273 273 SMALL
274 274
275 275 a2 @ 1
276 276 version https://git-lfs.github.com/spec/v1
277 277 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
278 278 size 29
279 279 x-hg-copy a1
280 280 x-hg-copyrev be23af27908a582af43e5cda209a5a9b319de8d4
281 281 x-is-binary 0
282 282
283 283 a2 @ 2
284 284 version https://git-lfs.github.com/spec/v1
285 285 oid sha256:876dadc86a8542f9798048f2c47f51dbf8e4359aed883e8ec80c5db825f0d943
286 286 size 32
287 287 x-is-binary 0
288 288
289 289 # Verify commit hashes include rename metadata
290 290
291 291 $ hg log -T '{rev}:{node|short} {desc}\n'
292 292 2:0fae949de7fa meta
293 293 1:9cd6bdffdac0 b
294 294 0:7f96794915f7 a
295 295
296 296 $ cd ..
297 297
298 298 # Test bundle
299 299
300 300 $ hg init repo9
301 301 $ cd repo9
302 302 $ cat >> .hg/hgrc << EOF
303 303 > [lfs]
304 304 > threshold=10B
305 305 > [diff]
306 306 > git=1
307 307 > EOF
308 308
309 309 $ for i in 0 single two three 4; do
310 310 > echo 'THIS-IS-LFS-'$i > a
311 311 > hg commit -m a-$i -A a
312 312 > done
313 313
314 314 $ hg update 2 -q
315 315 $ echo 'THIS-IS-LFS-2-CHILD' > a
316 316 $ hg commit -m branching -q
317 317
318 318 $ hg bundle --base 1 bundle.hg -v
319 319 4 changesets found
320 320 uncompressed size of bundle content:
321 321 * (changelog) (glob)
322 322 * (manifests) (glob)
323 323 * a (glob)
324 324 $ hg --config extensions.strip= strip -r 2 --no-backup --force -q
325 325 $ hg -R bundle.hg log -p -T '{rev} {desc}\n' a
326 326 5 branching
327 327 diff --git a/a b/a
328 328 --- a/a
329 329 +++ b/a
330 330 @@ -1,1 +1,1 @@
331 331 -THIS-IS-LFS-two
332 332 +THIS-IS-LFS-2-CHILD
333 333
334 334 4 a-4
335 335 diff --git a/a b/a
336 336 --- a/a
337 337 +++ b/a
338 338 @@ -1,1 +1,1 @@
339 339 -THIS-IS-LFS-three
340 340 +THIS-IS-LFS-4
341 341
342 342 3 a-three
343 343 diff --git a/a b/a
344 344 --- a/a
345 345 +++ b/a
346 346 @@ -1,1 +1,1 @@
347 347 -THIS-IS-LFS-two
348 348 +THIS-IS-LFS-three
349 349
350 350 2 a-two
351 351 diff --git a/a b/a
352 352 --- a/a
353 353 +++ b/a
354 354 @@ -1,1 +1,1 @@
355 355 -THIS-IS-LFS-single
356 356 +THIS-IS-LFS-two
357 357
358 358 1 a-single
359 359 diff --git a/a b/a
360 360 --- a/a
361 361 +++ b/a
362 362 @@ -1,1 +1,1 @@
363 363 -THIS-IS-LFS-0
364 364 +THIS-IS-LFS-single
365 365
366 366 0 a-0
367 367 diff --git a/a b/a
368 368 new file mode 100644
369 369 --- /dev/null
370 370 +++ b/a
371 371 @@ -0,0 +1,1 @@
372 372 +THIS-IS-LFS-0
373 373
374 374 $ hg bundle -R bundle.hg --base 1 bundle-again.hg -q
375 375 $ hg -R bundle-again.hg log -p -T '{rev} {desc}\n' a
376 376 5 branching
377 377 diff --git a/a b/a
378 378 --- a/a
379 379 +++ b/a
380 380 @@ -1,1 +1,1 @@
381 381 -THIS-IS-LFS-two
382 382 +THIS-IS-LFS-2-CHILD
383 383
384 384 4 a-4
385 385 diff --git a/a b/a
386 386 --- a/a
387 387 +++ b/a
388 388 @@ -1,1 +1,1 @@
389 389 -THIS-IS-LFS-three
390 390 +THIS-IS-LFS-4
391 391
392 392 3 a-three
393 393 diff --git a/a b/a
394 394 --- a/a
395 395 +++ b/a
396 396 @@ -1,1 +1,1 @@
397 397 -THIS-IS-LFS-two
398 398 +THIS-IS-LFS-three
399 399
400 400 2 a-two
401 401 diff --git a/a b/a
402 402 --- a/a
403 403 +++ b/a
404 404 @@ -1,1 +1,1 @@
405 405 -THIS-IS-LFS-single
406 406 +THIS-IS-LFS-two
407 407
408 408 1 a-single
409 409 diff --git a/a b/a
410 410 --- a/a
411 411 +++ b/a
412 412 @@ -1,1 +1,1 @@
413 413 -THIS-IS-LFS-0
414 414 +THIS-IS-LFS-single
415 415
416 416 0 a-0
417 417 diff --git a/a b/a
418 418 new file mode 100644
419 419 --- /dev/null
420 420 +++ b/a
421 421 @@ -0,0 +1,1 @@
422 422 +THIS-IS-LFS-0
423 423
424 424 $ cd ..
425 425
426 426 # Test isbinary
427 427
428 428 $ hg init repo10
429 429 $ cd repo10
430 430 $ cat >> .hg/hgrc << EOF
431 431 > [extensions]
432 432 > lfs=
433 433 > [lfs]
434 434 > threshold=1
435 435 > EOF
436 436 $ $PYTHON <<'EOF'
437 437 > def write(path, content):
438 438 > with open(path, 'wb') as f:
439 439 > f.write(content)
440 440 > write('a', b'\0\0')
441 441 > write('b', b'\1\n')
442 442 > write('c', b'\1\n\0')
443 443 > write('d', b'xx')
444 444 > EOF
445 445 $ hg add a b c d
446 446 $ hg diff --stat
447 447 a | Bin
448 448 b | 1 +
449 449 c | Bin
450 450 d | 1 +
451 451 4 files changed, 2 insertions(+), 0 deletions(-)
452 452 $ hg commit -m binarytest
453 453 $ cat > $TESTTMP/dumpbinary.py << EOF
454 454 > def reposetup(ui, repo):
455 455 > for n in 'abcd':
456 456 > ui.write(('%s: binary=%s\n') % (n, repo['.'][n].isbinary()))
457 457 > EOF
458 458 $ hg --config extensions.dumpbinary=$TESTTMP/dumpbinary.py id --trace
459 459 a: binary=True
460 460 b: binary=False
461 461 c: binary=True
462 462 d: binary=False
463 463 b55353847f02 tip
464 464
465 465 $ cd ..
466 466
467 467 # Test fctx.cmp fastpath - diff without LFS blobs
468 468
469 469 $ hg init repo11
470 470 $ cd repo11
471 471 $ cat >> .hg/hgrc <<EOF
472 472 > [lfs]
473 473 > threshold=1
474 474 > EOF
475 475 $ cat > ../patch.diff <<EOF
476 476 > # HG changeset patch
477 477 > 2
478 478 >
479 479 > diff --git a/a b/a
480 480 > old mode 100644
481 481 > new mode 100755
482 482 > EOF
483 483
484 484 $ for i in 1 2 3; do
485 485 > cp ../repo10/a a
486 486 > if [ $i = 3 ]; then
487 487 > # make a content-only change
488 488 > hg import -q --bypass ../patch.diff
489 489 > hg update -q
490 490 > rm ../patch.diff
491 491 > else
492 492 > echo $i >> a
493 493 > hg commit -m $i -A a
494 494 > fi
495 495 > done
496 496 $ [ -d .hg/store/lfs/objects ]
497 497
498 498 $ cd ..
499 499
500 500 $ hg clone repo11 repo12 --noupdate
501 501 $ cd repo12
502 502 $ hg log --removed -p a -T '{desc}\n' --config diff.nobinary=1 --git
503 503 2
504 504 diff --git a/a b/a
505 505 old mode 100644
506 506 new mode 100755
507 507
508 508 2
509 509 diff --git a/a b/a
510 510 Binary file a has changed
511 511
512 512 1
513 513 diff --git a/a b/a
514 514 new file mode 100644
515 515 Binary file a has changed
516 516
517 517 $ [ -d .hg/store/lfs/objects ]
518 518 [1]
519 519
520 520 $ cd ..
521 521
522 522 # Verify the repos
523 523
524 524 $ cat > $TESTTMP/dumpflog.py << EOF
525 525 > # print raw revision sizes, flags, and hashes for certain files
526 526 > import hashlib
527 527 > from mercurial import revlog
528 528 > from mercurial.node import short
529 529 > def hash(rawtext):
530 530 > h = hashlib.sha512()
531 531 > h.update(rawtext)
532 532 > return h.hexdigest()[:4]
533 533 > def reposetup(ui, repo):
534 534 > # these 2 files are interesting
535 535 > for name in ['l', 's']:
536 536 > fl = repo.file(name)
537 537 > if len(fl) == 0:
538 538 > continue
539 539 > sizes = [revlog.revlog.rawsize(fl, i) for i in fl]
540 540 > texts = [fl.revision(i, raw=True) for i in fl]
541 541 > flags = [int(fl.flags(i)) for i in fl]
542 542 > hashes = [hash(t) for t in texts]
543 543 > print(' %s: rawsizes=%r flags=%r hashes=%r'
544 544 > % (name, sizes, flags, hashes))
545 545 > EOF
546 546
547 547 $ for i in client client2 server repo3 repo4 repo5 repo6 repo7 repo8 repo9 \
548 548 > repo10; do
549 549 > echo 'repo:' $i
550 550 > hg --cwd $i verify --config extensions.dumpflog=$TESTTMP/dumpflog.py -q
551 551 > done
552 552 repo: client
553 553 repo: client2
554 554 repo: server
555 555 repo: repo3
556 556 l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
557 557 s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
558 558 repo: repo4
559 559 l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
560 560 s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
561 561 repo: repo5
562 562 l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
563 563 s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
564 564 repo: repo6
565 565 repo: repo7
566 566 repo: repo8
567 567 repo: repo9
568 568 repo: repo10
569 569
570 570 repo12 doesn't have any cached lfs files and its source never pushed its
571 571 files. Therefore, the files don't exist in the remote store. Use the files in
572 572 the user cache.
573 573
574 $ find $TESTTMP/repo12/.hg/store/lfs/objects -type f
575 find: */repo12/.hg/store/lfs/objects*: $ENOENT$ (glob)
574 $ test -d $TESTTMP/repo12/.hg/store/lfs/objects
576 575 [1]
577 576
578 577 $ hg --config extensions.share= share repo12 repo13
579 578 updating working directory
580 579 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
581 580 $ hg -R repo13 -q verify
582 581
583 582 $ hg clone repo12 repo14
584 583 updating to branch default
585 584 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
586 585 $ hg -R repo14 -q verify
587 586
588 587 If the source repo doesn't have the blob (maybe it was pulled or cloned with
589 588 --noupdate), the blob is still accessible via the global cache to send to the
590 589 remote store.
591 590
592 591 $ rm -rf $TESTTMP/repo14/.hg/store/lfs
593 592 $ hg init repo15
594 593 $ hg -R repo14 push repo15
595 594 pushing to repo15
596 595 searching for changes
597 596 adding changesets
598 597 adding manifests
599 598 adding file changes
600 599 added 3 changesets with 2 changes to 1 files
601 600 $ hg -R repo14 -q verify
602 601
603 602 lfs -> normal -> lfs round trip conversions are possible. The threshold for the
604 603 lfs destination is specified here because it was originally listed in the local
605 604 .hgrc, and the global one is too high to trigger lfs usage. For lfs -> normal,
606 605 there's no 'lfs' destination repo requirement. For normal -> lfs, there is.
607 606
608 607 XXX: There's not a great way to ensure that the conversion to normal files
609 608 actually converts _everything_ to normal. The extension needs to be loaded for
610 609 the source, but there's no way to disable it for the destination. The best that
611 610 can be done is to raise the threshold so that lfs isn't used on the destination.
612 611 It doesn't like using '!' to unset the value on the command line.
613 612
614 613 $ hg --config extensions.convert= --config lfs.threshold=1000M \
615 614 > convert repo8 convert_normal
616 615 initializing destination convert_normal repository
617 616 scanning source...
618 617 sorting...
619 618 converting...
620 619 2 a
621 620 1 b
622 621 0 meta
623 622 $ grep 'lfs' convert_normal/.hg/requires
624 623 [1]
625 624 $ hg --cwd convert_normal debugdata a1 0
626 625 THIS-IS-LFS-BECAUSE-10-BYTES
627 626
628 627 $ hg --config extensions.convert= --config lfs.threshold=10B \
629 628 > convert convert_normal convert_lfs
630 629 initializing destination convert_lfs repository
631 630 scanning source...
632 631 sorting...
633 632 converting...
634 633 2 a
635 634 1 b
636 635 0 meta
637 636 $ hg --cwd convert_lfs debugdata a1 0
638 637 version https://git-lfs.github.com/spec/v1
639 638 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
640 639 size 29
641 640 x-is-binary 0
642 641 $ grep 'lfs' convert_lfs/.hg/requires
643 642 lfs
644 643
645 644 This convert is trickier, because it contains deleted files (via `hg mv`)
646 645
647 646 $ hg --config extensions.convert= --config lfs.threshold=1000M \
648 647 > convert repo3 convert_normal2
649 648 initializing destination convert_normal2 repository
650 649 scanning source...
651 650 sorting...
652 651 converting...
653 652 4 commit with lfs content
654 653 3 renames
655 654 2 large to small, small to large
656 655 1 random modifications
657 656 0 switch large and small again
658 657 $ grep 'lfs' convert_normal2/.hg/requires
659 658 [1]
660 659 $ hg --cwd convert_normal2 debugdata large 0
661 660 LONGER-THAN-TEN-BYTES-WILL-TRIGGER-LFS
662 661
663 662 $ hg --config extensions.convert= --config lfs.threshold=10B \
664 663 > convert convert_normal2 convert_lfs2
665 664 initializing destination convert_lfs2 repository
666 665 scanning source...
667 666 sorting...
668 667 converting...
669 668 4 commit with lfs content
670 669 3 renames
671 670 2 large to small, small to large
672 671 1 random modifications
673 672 0 switch large and small again
674 673 $ grep 'lfs' convert_lfs2/.hg/requires
675 674 lfs
676 675 $ hg --cwd convert_lfs2 debugdata large 0
677 676 version https://git-lfs.github.com/spec/v1
678 677 oid sha256:66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
679 678 size 39
680 679 x-is-binary 0
681 680
682 681 $ hg -R convert_lfs2 config --debug extensions | grep lfs
683 682 $TESTTMP/convert_lfs2/.hg/hgrc:*: extensions.lfs= (glob)
General Comments 0
You need to be logged in to leave comments. Login now