##// END OF EJS Templates
py2: remove simple from __future__ statements...
Gregory Szorc -
r50115:6000f5b2 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,108 +1,107 b''
1 # Randomized torture test generation for bdiff
1 # Randomized torture test generation for bdiff
2
2
3 from __future__ import absolute_import, print_function
4 import random
3 import random
5 import sys
4 import sys
6
5
7 from mercurial import (
6 from mercurial import (
8 mdiff,
7 mdiff,
9 pycompat,
8 pycompat,
10 )
9 )
11
10
12
11
13 def reducetest(a, b):
12 def reducetest(a, b):
14 tries = 0
13 tries = 0
15 reductions = 0
14 reductions = 0
16 print("reducing...")
15 print("reducing...")
17 while tries < 1000:
16 while tries < 1000:
18 a2 = (
17 a2 = (
19 "\n".join(l for l in a.splitlines() if random.randint(0, 100) > 0)
18 "\n".join(l for l in a.splitlines() if random.randint(0, 100) > 0)
20 + "\n"
19 + "\n"
21 )
20 )
22 b2 = (
21 b2 = (
23 "\n".join(l for l in b.splitlines() if random.randint(0, 100) > 0)
22 "\n".join(l for l in b.splitlines() if random.randint(0, 100) > 0)
24 + "\n"
23 + "\n"
25 )
24 )
26 if a2 == a and b2 == b:
25 if a2 == a and b2 == b:
27 continue
26 continue
28 if a2 == b2:
27 if a2 == b2:
29 continue
28 continue
30 tries += 1
29 tries += 1
31
30
32 try:
31 try:
33 test1(a, b)
32 test1(a, b)
34 except Exception:
33 except Exception:
35 reductions += 1
34 reductions += 1
36 tries = 0
35 tries = 0
37 a = a2
36 a = a2
38 b = b2
37 b = b2
39
38
40 print("reduced:", reductions, len(a) + len(b), repr(a), repr(b))
39 print("reduced:", reductions, len(a) + len(b), repr(a), repr(b))
41 try:
40 try:
42 test1(a, b)
41 test1(a, b)
43 except Exception as inst:
42 except Exception as inst:
44 print("failed:", inst)
43 print("failed:", inst)
45
44
46 sys.exit(0)
45 sys.exit(0)
47
46
48
47
49 def test1(a, b):
48 def test1(a, b):
50 d = mdiff.textdiff(a, b)
49 d = mdiff.textdiff(a, b)
51 if not d:
50 if not d:
52 raise ValueError("empty")
51 raise ValueError("empty")
53 c = mdiff.patches(a, [d])
52 c = mdiff.patches(a, [d])
54 if c != b:
53 if c != b:
55 raise ValueError("bad")
54 raise ValueError("bad")
56
55
57
56
58 def testwrap(a, b):
57 def testwrap(a, b):
59 try:
58 try:
60 test1(a, b)
59 test1(a, b)
61 return
60 return
62 except Exception as inst:
61 except Exception as inst:
63 print("exception:", inst)
62 print("exception:", inst)
64 reducetest(a, b)
63 reducetest(a, b)
65
64
66
65
67 def test(a, b):
66 def test(a, b):
68 testwrap(a, b)
67 testwrap(a, b)
69 testwrap(b, a)
68 testwrap(b, a)
70
69
71
70
72 def rndtest(size, noise):
71 def rndtest(size, noise):
73 a = []
72 a = []
74 src = " aaaaaaaabbbbccd"
73 src = " aaaaaaaabbbbccd"
75 for x in pycompat.xrange(size):
74 for x in pycompat.xrange(size):
76 a.append(src[random.randint(0, len(src) - 1)])
75 a.append(src[random.randint(0, len(src) - 1)])
77
76
78 while True:
77 while True:
79 b = [c for c in a if random.randint(0, 99) > noise]
78 b = [c for c in a if random.randint(0, 99) > noise]
80 b2 = []
79 b2 = []
81 for c in b:
80 for c in b:
82 b2.append(c)
81 b2.append(c)
83 while random.randint(0, 99) < noise:
82 while random.randint(0, 99) < noise:
84 b2.append(src[random.randint(0, len(src) - 1)])
83 b2.append(src[random.randint(0, len(src) - 1)])
85 if b2 != a:
84 if b2 != a:
86 break
85 break
87
86
88 a = "\n".join(a) + "\n"
87 a = "\n".join(a) + "\n"
89 b = "\n".join(b2) + "\n"
88 b = "\n".join(b2) + "\n"
90
89
91 test(a, b)
90 test(a, b)
92
91
93
92
94 maxvol = 10000
93 maxvol = 10000
95 startsize = 2
94 startsize = 2
96 while True:
95 while True:
97 size = startsize
96 size = startsize
98 count = 0
97 count = 0
99 while size < maxvol:
98 while size < maxvol:
100 print(size)
99 print(size)
101 volume = 0
100 volume = 0
102 while volume < maxvol:
101 while volume < maxvol:
103 rndtest(size, 2)
102 rndtest(size, 2)
104 volume += size
103 volume += size
105 count += 2
104 count += 2
106 size *= 2
105 size *= 2
107 maxvol *= 4
106 maxvol *= 4
108 startsize *= 4
107 startsize *= 4
@@ -1,126 +1,125 b''
1 # __init__.py - asv benchmark suite
1 # __init__.py - asv benchmark suite
2 #
2 #
3 # Copyright 2016 Logilab SA <contact@logilab.fr>
3 # Copyright 2016 Logilab SA <contact@logilab.fr>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 # "historical portability" policy of contrib/benchmarks:
8 # "historical portability" policy of contrib/benchmarks:
9 #
9 #
10 # We have to make this code work correctly with current mercurial stable branch
10 # We have to make this code work correctly with current mercurial stable branch
11 # and if possible with reasonable cost with early Mercurial versions.
11 # and if possible with reasonable cost with early Mercurial versions.
12
12
13 '''ASV (https://asv.readthedocs.io) benchmark suite
13 '''ASV (https://asv.readthedocs.io) benchmark suite
14
14
15 Benchmark are parameterized against reference repositories found in the
15 Benchmark are parameterized against reference repositories found in the
16 directory pointed by the REPOS_DIR environment variable.
16 directory pointed by the REPOS_DIR environment variable.
17
17
18 Invocation example:
18 Invocation example:
19
19
20 $ export REPOS_DIR=~/hgperf/repos
20 $ export REPOS_DIR=~/hgperf/repos
21 # run suite on given revision
21 # run suite on given revision
22 $ asv --config contrib/asv.conf.json run REV
22 $ asv --config contrib/asv.conf.json run REV
23 # run suite on new changesets found in stable and default branch
23 # run suite on new changesets found in stable and default branch
24 $ asv --config contrib/asv.conf.json run NEW
24 $ asv --config contrib/asv.conf.json run NEW
25 # display a comparative result table of benchmark results between two given
25 # display a comparative result table of benchmark results between two given
26 # revisions
26 # revisions
27 $ asv --config contrib/asv.conf.json compare REV1 REV2
27 $ asv --config contrib/asv.conf.json compare REV1 REV2
28 # compute regression detection and generate ASV static website
28 # compute regression detection and generate ASV static website
29 $ asv --config contrib/asv.conf.json publish
29 $ asv --config contrib/asv.conf.json publish
30 # serve the static website
30 # serve the static website
31 $ asv --config contrib/asv.conf.json preview
31 $ asv --config contrib/asv.conf.json preview
32 '''
32 '''
33
33
34 from __future__ import absolute_import
35
34
36 import functools
35 import functools
37 import os
36 import os
38 import re
37 import re
39
38
40 from mercurial import (
39 from mercurial import (
41 extensions,
40 extensions,
42 hg,
41 hg,
43 ui as uimod,
42 ui as uimod,
44 util,
43 util,
45 )
44 )
46
45
47 basedir = os.path.abspath(
46 basedir = os.path.abspath(
48 os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir)
47 os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir)
49 )
48 )
50 reposdir = os.environ['REPOS_DIR']
49 reposdir = os.environ['REPOS_DIR']
51 reposnames = [
50 reposnames = [
52 name
51 name
53 for name in os.listdir(reposdir)
52 for name in os.listdir(reposdir)
54 if os.path.isdir(os.path.join(reposdir, name, ".hg"))
53 if os.path.isdir(os.path.join(reposdir, name, ".hg"))
55 ]
54 ]
56 if not reposnames:
55 if not reposnames:
57 raise ValueError("No repositories found in $REPO_DIR")
56 raise ValueError("No repositories found in $REPO_DIR")
58 outputre = re.compile(
57 outputre = re.compile(
59 (
58 (
60 r'! wall (\d+.\d+) comb \d+.\d+ user \d+.\d+ sys '
59 r'! wall (\d+.\d+) comb \d+.\d+ user \d+.\d+ sys '
61 r'\d+.\d+ \(best of \d+\)'
60 r'\d+.\d+ \(best of \d+\)'
62 )
61 )
63 )
62 )
64
63
65
64
66 def runperfcommand(reponame, command, *args, **kwargs):
65 def runperfcommand(reponame, command, *args, **kwargs):
67 os.environ["HGRCPATH"] = os.environ.get("ASVHGRCPATH", "")
66 os.environ["HGRCPATH"] = os.environ.get("ASVHGRCPATH", "")
68 # for "historical portability"
67 # for "historical portability"
69 # ui.load() has been available since d83ca85
68 # ui.load() has been available since d83ca85
70 if util.safehasattr(uimod.ui, "load"):
69 if util.safehasattr(uimod.ui, "load"):
71 ui = uimod.ui.load()
70 ui = uimod.ui.load()
72 else:
71 else:
73 ui = uimod.ui()
72 ui = uimod.ui()
74 repo = hg.repository(ui, os.path.join(reposdir, reponame))
73 repo = hg.repository(ui, os.path.join(reposdir, reponame))
75 perfext = extensions.load(
74 perfext = extensions.load(
76 ui, 'perfext', os.path.join(basedir, 'contrib', 'perf.py')
75 ui, 'perfext', os.path.join(basedir, 'contrib', 'perf.py')
77 )
76 )
78 cmd = getattr(perfext, command)
77 cmd = getattr(perfext, command)
79 ui.pushbuffer()
78 ui.pushbuffer()
80 cmd(ui, repo, *args, **kwargs)
79 cmd(ui, repo, *args, **kwargs)
81 output = ui.popbuffer()
80 output = ui.popbuffer()
82 match = outputre.search(output)
81 match = outputre.search(output)
83 if not match:
82 if not match:
84 raise ValueError("Invalid output {}".format(output))
83 raise ValueError("Invalid output {}".format(output))
85 return float(match.group(1))
84 return float(match.group(1))
86
85
87
86
88 def perfbench(repos=reposnames, name=None, params=None):
87 def perfbench(repos=reposnames, name=None, params=None):
89 """decorator to declare ASV benchmark based on contrib/perf.py extension
88 """decorator to declare ASV benchmark based on contrib/perf.py extension
90
89
91 An ASV benchmark is a python function with the given attributes:
90 An ASV benchmark is a python function with the given attributes:
92
91
93 __name__: should start with track_, time_ or mem_ to be collected by ASV
92 __name__: should start with track_, time_ or mem_ to be collected by ASV
94 params and param_name: parameter matrix to display multiple graphs on the
93 params and param_name: parameter matrix to display multiple graphs on the
95 same page.
94 same page.
96 pretty_name: If defined it's displayed in web-ui instead of __name__
95 pretty_name: If defined it's displayed in web-ui instead of __name__
97 (useful for revsets)
96 (useful for revsets)
98 the module name is prepended to the benchmark name and displayed as
97 the module name is prepended to the benchmark name and displayed as
99 "category" in webui.
98 "category" in webui.
100
99
101 Benchmarks are automatically parameterized with repositories found in the
100 Benchmarks are automatically parameterized with repositories found in the
102 REPOS_DIR environment variable.
101 REPOS_DIR environment variable.
103
102
104 `params` is the param matrix in the form of a list of tuple
103 `params` is the param matrix in the form of a list of tuple
105 (param_name, [value0, value1])
104 (param_name, [value0, value1])
106
105
107 For example [(x, [a, b]), (y, [c, d])] declare benchmarks for
106 For example [(x, [a, b]), (y, [c, d])] declare benchmarks for
108 (a, c), (a, d), (b, c) and (b, d).
107 (a, c), (a, d), (b, c) and (b, d).
109 """
108 """
110 params = list(params or [])
109 params = list(params or [])
111 params.insert(0, ("repo", repos))
110 params.insert(0, ("repo", repos))
112
111
113 def decorator(func):
112 def decorator(func):
114 @functools.wraps(func)
113 @functools.wraps(func)
115 def wrapped(repo, *args):
114 def wrapped(repo, *args):
116 def perf(command, *a, **kw):
115 def perf(command, *a, **kw):
117 return runperfcommand(repo, command, *a, **kw)
116 return runperfcommand(repo, command, *a, **kw)
118
117
119 return func(perf, *args)
118 return func(perf, *args)
120
119
121 wrapped.params = [p[1] for p in params]
120 wrapped.params = [p[1] for p in params]
122 wrapped.param_names = [p[0] for p in params]
121 wrapped.param_names = [p[0] for p in params]
123 wrapped.pretty_name = name
122 wrapped.pretty_name = name
124 return wrapped
123 return wrapped
125
124
126 return decorator
125 return decorator
@@ -1,30 +1,29 b''
1 # perf.py - asv benchmarks using contrib/perf.py extension
1 # perf.py - asv benchmarks using contrib/perf.py extension
2 #
2 #
3 # Copyright 2016 Logilab SA <contact@logilab.fr>
3 # Copyright 2016 Logilab SA <contact@logilab.fr>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
9
8
10 from . import perfbench
9 from . import perfbench
11
10
12
11
13 @perfbench()
12 @perfbench()
14 def track_tags(perf):
13 def track_tags(perf):
15 return perf("perftags")
14 return perf("perftags")
16
15
17
16
18 @perfbench()
17 @perfbench()
19 def track_status(perf):
18 def track_status(perf):
20 return perf("perfstatus", unknown=False)
19 return perf("perfstatus", unknown=False)
21
20
22
21
23 @perfbench(params=[('rev', ['1000', '10000', 'tip'])])
22 @perfbench(params=[('rev', ['1000', '10000', 'tip'])])
24 def track_manifest(perf, rev):
23 def track_manifest(perf, rev):
25 return perf("perfmanifest", rev)
24 return perf("perfmanifest", rev)
26
25
27
26
28 @perfbench()
27 @perfbench()
29 def track_heads(perf):
28 def track_heads(perf):
30 return perf("perfheads")
29 return perf("perfheads")
@@ -1,57 +1,56 b''
1 # revset.py - asv revset benchmarks
1 # revset.py - asv revset benchmarks
2 #
2 #
3 # Copyright 2016 Logilab SA <contact@logilab.fr>
3 # Copyright 2016 Logilab SA <contact@logilab.fr>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''ASV revset benchmarks generated from contrib/base-revsets.txt
8 '''ASV revset benchmarks generated from contrib/base-revsets.txt
9
9
10 Each revset benchmark is parameterized with variants (first, last, sort, ...)
10 Each revset benchmark is parameterized with variants (first, last, sort, ...)
11 '''
11 '''
12
12
13 from __future__ import absolute_import
14
13
15 import os
14 import os
16 import string
15 import string
17 import sys
16 import sys
18
17
19 from . import basedir, perfbench
18 from . import basedir, perfbench
20
19
21
20
22 def createrevsetbenchmark(baseset, variants=None):
21 def createrevsetbenchmark(baseset, variants=None):
23 if variants is None:
22 if variants is None:
24 # Default variants
23 # Default variants
25 variants = ["plain", "first", "last", "sort", "sort+first", "sort+last"]
24 variants = ["plain", "first", "last", "sort", "sort+first", "sort+last"]
26 fname = "track_" + "_".join(
25 fname = "track_" + "_".join(
27 "".join(
26 "".join(
28 [c if c in string.digits + string.letters else " " for c in baseset]
27 [c if c in string.digits + string.letters else " " for c in baseset]
29 ).split()
28 ).split()
30 )
29 )
31
30
32 def wrap(fname, baseset):
31 def wrap(fname, baseset):
33 @perfbench(name=baseset, params=[("variant", variants)])
32 @perfbench(name=baseset, params=[("variant", variants)])
34 def f(perf, variant):
33 def f(perf, variant):
35 revset = baseset
34 revset = baseset
36 if variant != "plain":
35 if variant != "plain":
37 for var in variant.split("+"):
36 for var in variant.split("+"):
38 revset = "%s(%s)" % (var, revset)
37 revset = "%s(%s)" % (var, revset)
39 return perf("perfrevset", revset)
38 return perf("perfrevset", revset)
40
39
41 f.__name__ = fname
40 f.__name__ = fname
42 return f
41 return f
43
42
44 return wrap(fname, baseset)
43 return wrap(fname, baseset)
45
44
46
45
47 def initializerevsetbenchmarks():
46 def initializerevsetbenchmarks():
48 mod = sys.modules[__name__]
47 mod = sys.modules[__name__]
49 with open(os.path.join(basedir, 'contrib', 'base-revsets.txt'), 'rb') as fh:
48 with open(os.path.join(basedir, 'contrib', 'base-revsets.txt'), 'rb') as fh:
50 for line in fh:
49 for line in fh:
51 baseset = line.strip()
50 baseset = line.strip()
52 if baseset and not baseset.startswith('#'):
51 if baseset and not baseset.startswith('#'):
53 func = createrevsetbenchmark(baseset)
52 func = createrevsetbenchmark(baseset)
54 setattr(mod, func.__name__, func)
53 setattr(mod, func.__name__, func)
55
54
56
55
57 initializerevsetbenchmarks()
56 initializerevsetbenchmarks()
@@ -1,350 +1,349 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 #
2 #
3 # byteify-strings.py - transform string literals to be Python 3 safe
3 # byteify-strings.py - transform string literals to be Python 3 safe
4 #
4 #
5 # Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com>
5 # Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 from __future__ import absolute_import, print_function
11
10
12 import argparse
11 import argparse
13 import contextlib
12 import contextlib
14 import errno
13 import errno
15 import os
14 import os
16 import sys
15 import sys
17 import tempfile
16 import tempfile
18 import token
17 import token
19 import tokenize
18 import tokenize
20
19
21
20
22 def adjusttokenpos(t, ofs):
21 def adjusttokenpos(t, ofs):
23 """Adjust start/end column of the given token"""
22 """Adjust start/end column of the given token"""
24 return t._replace(
23 return t._replace(
25 start=(t.start[0], t.start[1] + ofs), end=(t.end[0], t.end[1] + ofs)
24 start=(t.start[0], t.start[1] + ofs), end=(t.end[0], t.end[1] + ofs)
26 )
25 )
27
26
28
27
29 def replacetokens(tokens, opts):
28 def replacetokens(tokens, opts):
30 """Transform a stream of tokens from raw to Python 3.
29 """Transform a stream of tokens from raw to Python 3.
31
30
32 Returns a generator of possibly rewritten tokens.
31 Returns a generator of possibly rewritten tokens.
33
32
34 The input token list may be mutated as part of processing. However,
33 The input token list may be mutated as part of processing. However,
35 its changes do not necessarily match the output token stream.
34 its changes do not necessarily match the output token stream.
36 """
35 """
37 sysstrtokens = set()
36 sysstrtokens = set()
38
37
39 # The following utility functions access the tokens list and i index of
38 # The following utility functions access the tokens list and i index of
40 # the for i, t enumerate(tokens) loop below
39 # the for i, t enumerate(tokens) loop below
41 def _isop(j, *o):
40 def _isop(j, *o):
42 """Assert that tokens[j] is an OP with one of the given values"""
41 """Assert that tokens[j] is an OP with one of the given values"""
43 try:
42 try:
44 return tokens[j].type == token.OP and tokens[j].string in o
43 return tokens[j].type == token.OP and tokens[j].string in o
45 except IndexError:
44 except IndexError:
46 return False
45 return False
47
46
48 def _findargnofcall(n):
47 def _findargnofcall(n):
49 """Find arg n of a call expression (start at 0)
48 """Find arg n of a call expression (start at 0)
50
49
51 Returns index of the first token of that argument, or None if
50 Returns index of the first token of that argument, or None if
52 there is not that many arguments.
51 there is not that many arguments.
53
52
54 Assumes that token[i + 1] is '('.
53 Assumes that token[i + 1] is '('.
55
54
56 """
55 """
57 nested = 0
56 nested = 0
58 for j in range(i + 2, len(tokens)):
57 for j in range(i + 2, len(tokens)):
59 if _isop(j, ')', ']', '}'):
58 if _isop(j, ')', ']', '}'):
60 # end of call, tuple, subscription or dict / set
59 # end of call, tuple, subscription or dict / set
61 nested -= 1
60 nested -= 1
62 if nested < 0:
61 if nested < 0:
63 return None
62 return None
64 elif n == 0:
63 elif n == 0:
65 # this is the starting position of arg
64 # this is the starting position of arg
66 return j
65 return j
67 elif _isop(j, '(', '[', '{'):
66 elif _isop(j, '(', '[', '{'):
68 nested += 1
67 nested += 1
69 elif _isop(j, ',') and nested == 0:
68 elif _isop(j, ',') and nested == 0:
70 n -= 1
69 n -= 1
71
70
72 return None
71 return None
73
72
74 def _ensuresysstr(j):
73 def _ensuresysstr(j):
75 """Make sure the token at j is a system string
74 """Make sure the token at j is a system string
76
75
77 Remember the given token so the string transformer won't add
76 Remember the given token so the string transformer won't add
78 the byte prefix.
77 the byte prefix.
79
78
80 Ignores tokens that are not strings. Assumes bounds checking has
79 Ignores tokens that are not strings. Assumes bounds checking has
81 already been done.
80 already been done.
82
81
83 """
82 """
84 k = j
83 k = j
85 currtoken = tokens[k]
84 currtoken = tokens[k]
86 while currtoken.type in (token.STRING, token.NEWLINE, tokenize.NL):
85 while currtoken.type in (token.STRING, token.NEWLINE, tokenize.NL):
87 k += 1
86 k += 1
88 if currtoken.type == token.STRING and currtoken.string.startswith(
87 if currtoken.type == token.STRING and currtoken.string.startswith(
89 ("'", '"')
88 ("'", '"')
90 ):
89 ):
91 sysstrtokens.add(currtoken)
90 sysstrtokens.add(currtoken)
92 try:
91 try:
93 currtoken = tokens[k]
92 currtoken = tokens[k]
94 except IndexError:
93 except IndexError:
95 break
94 break
96
95
97 def _isitemaccess(j):
96 def _isitemaccess(j):
98 """Assert the next tokens form an item access on `tokens[j]` and that
97 """Assert the next tokens form an item access on `tokens[j]` and that
99 `tokens[j]` is a name.
98 `tokens[j]` is a name.
100 """
99 """
101 try:
100 try:
102 return (
101 return (
103 tokens[j].type == token.NAME
102 tokens[j].type == token.NAME
104 and _isop(j + 1, '[')
103 and _isop(j + 1, '[')
105 and tokens[j + 2].type == token.STRING
104 and tokens[j + 2].type == token.STRING
106 and _isop(j + 3, ']')
105 and _isop(j + 3, ']')
107 )
106 )
108 except IndexError:
107 except IndexError:
109 return False
108 return False
110
109
111 def _ismethodcall(j, *methodnames):
110 def _ismethodcall(j, *methodnames):
112 """Assert the next tokens form a call to `methodname` with a string
111 """Assert the next tokens form a call to `methodname` with a string
113 as first argument on `tokens[j]` and that `tokens[j]` is a name.
112 as first argument on `tokens[j]` and that `tokens[j]` is a name.
114 """
113 """
115 try:
114 try:
116 return (
115 return (
117 tokens[j].type == token.NAME
116 tokens[j].type == token.NAME
118 and _isop(j + 1, '.')
117 and _isop(j + 1, '.')
119 and tokens[j + 2].type == token.NAME
118 and tokens[j + 2].type == token.NAME
120 and tokens[j + 2].string in methodnames
119 and tokens[j + 2].string in methodnames
121 and _isop(j + 3, '(')
120 and _isop(j + 3, '(')
122 and tokens[j + 4].type == token.STRING
121 and tokens[j + 4].type == token.STRING
123 )
122 )
124 except IndexError:
123 except IndexError:
125 return False
124 return False
126
125
127 coldelta = 0 # column increment for new opening parens
126 coldelta = 0 # column increment for new opening parens
128 coloffset = -1 # column offset for the current line (-1: TBD)
127 coloffset = -1 # column offset for the current line (-1: TBD)
129 parens = [(0, 0, 0, -1)] # stack of (line, end-column, column-offset, type)
128 parens = [(0, 0, 0, -1)] # stack of (line, end-column, column-offset, type)
130 ignorenextline = False # don't transform the next line
129 ignorenextline = False # don't transform the next line
131 insideignoreblock = False # don't transform until turned off
130 insideignoreblock = False # don't transform until turned off
132 for i, t in enumerate(tokens):
131 for i, t in enumerate(tokens):
133 # Compute the column offset for the current line, such that
132 # Compute the column offset for the current line, such that
134 # the current line will be aligned to the last opening paren
133 # the current line will be aligned to the last opening paren
135 # as before.
134 # as before.
136 if coloffset < 0:
135 if coloffset < 0:
137 lastparen = parens[-1]
136 lastparen = parens[-1]
138 if t.start[1] == lastparen[1]:
137 if t.start[1] == lastparen[1]:
139 coloffset = lastparen[2]
138 coloffset = lastparen[2]
140 elif t.start[1] + 1 == lastparen[1] and lastparen[3] not in (
139 elif t.start[1] + 1 == lastparen[1] and lastparen[3] not in (
141 token.NEWLINE,
140 token.NEWLINE,
142 tokenize.NL,
141 tokenize.NL,
143 ):
142 ):
144 # fix misaligned indent of s/util.Abort/error.Abort/
143 # fix misaligned indent of s/util.Abort/error.Abort/
145 coloffset = lastparen[2] + (lastparen[1] - t.start[1])
144 coloffset = lastparen[2] + (lastparen[1] - t.start[1])
146 else:
145 else:
147 coloffset = 0
146 coloffset = 0
148
147
149 # Reset per-line attributes at EOL.
148 # Reset per-line attributes at EOL.
150 if t.type in (token.NEWLINE, tokenize.NL):
149 if t.type in (token.NEWLINE, tokenize.NL):
151 yield adjusttokenpos(t, coloffset)
150 yield adjusttokenpos(t, coloffset)
152 coldelta = 0
151 coldelta = 0
153 coloffset = -1
152 coloffset = -1
154 if not insideignoreblock:
153 if not insideignoreblock:
155 ignorenextline = (
154 ignorenextline = (
156 tokens[i - 1].type == token.COMMENT
155 tokens[i - 1].type == token.COMMENT
157 and tokens[i - 1].string == "# no-py3-transform"
156 and tokens[i - 1].string == "# no-py3-transform"
158 )
157 )
159 continue
158 continue
160
159
161 if t.type == token.COMMENT:
160 if t.type == token.COMMENT:
162 if t.string == "# py3-transform: off":
161 if t.string == "# py3-transform: off":
163 insideignoreblock = True
162 insideignoreblock = True
164 if t.string == "# py3-transform: on":
163 if t.string == "# py3-transform: on":
165 insideignoreblock = False
164 insideignoreblock = False
166
165
167 if ignorenextline or insideignoreblock:
166 if ignorenextline or insideignoreblock:
168 yield adjusttokenpos(t, coloffset)
167 yield adjusttokenpos(t, coloffset)
169 continue
168 continue
170
169
171 # Remember the last paren position.
170 # Remember the last paren position.
172 if _isop(i, '(', '[', '{'):
171 if _isop(i, '(', '[', '{'):
173 parens.append(t.end + (coloffset + coldelta, tokens[i + 1].type))
172 parens.append(t.end + (coloffset + coldelta, tokens[i + 1].type))
174 elif _isop(i, ')', ']', '}'):
173 elif _isop(i, ')', ']', '}'):
175 parens.pop()
174 parens.pop()
176
175
177 # Convert most string literals to byte literals. String literals
176 # Convert most string literals to byte literals. String literals
178 # in Python 2 are bytes. String literals in Python 3 are unicode.
177 # in Python 2 are bytes. String literals in Python 3 are unicode.
179 # Most strings in Mercurial are bytes and unicode strings are rare.
178 # Most strings in Mercurial are bytes and unicode strings are rare.
180 # Rather than rewrite all string literals to use ``b''`` to indicate
179 # Rather than rewrite all string literals to use ``b''`` to indicate
181 # byte strings, we apply this token transformer to insert the ``b``
180 # byte strings, we apply this token transformer to insert the ``b``
182 # prefix nearly everywhere.
181 # prefix nearly everywhere.
183 if t.type == token.STRING and t not in sysstrtokens:
182 if t.type == token.STRING and t not in sysstrtokens:
184 s = t.string
183 s = t.string
185
184
186 # Preserve docstrings as string literals. This is inconsistent
185 # Preserve docstrings as string literals. This is inconsistent
187 # with regular unprefixed strings. However, the
186 # with regular unprefixed strings. However, the
188 # "from __future__" parsing (which allows a module docstring to
187 # "from __future__" parsing (which allows a module docstring to
189 # exist before it) doesn't properly handle the docstring if it
188 # exist before it) doesn't properly handle the docstring if it
190 # is b''' prefixed, leading to a SyntaxError. We leave all
189 # is b''' prefixed, leading to a SyntaxError. We leave all
191 # docstrings as unprefixed to avoid this. This means Mercurial
190 # docstrings as unprefixed to avoid this. This means Mercurial
192 # components touching docstrings need to handle unicode,
191 # components touching docstrings need to handle unicode,
193 # unfortunately.
192 # unfortunately.
194 if s[0:3] in ("'''", '"""'):
193 if s[0:3] in ("'''", '"""'):
195 # If it's assigned to something, it's not a docstring
194 # If it's assigned to something, it's not a docstring
196 if not _isop(i - 1, '='):
195 if not _isop(i - 1, '='):
197 yield adjusttokenpos(t, coloffset)
196 yield adjusttokenpos(t, coloffset)
198 continue
197 continue
199
198
200 # If the first character isn't a quote, it is likely a string
199 # If the first character isn't a quote, it is likely a string
201 # prefixing character (such as 'b', 'u', or 'r'. Ignore.
200 # prefixing character (such as 'b', 'u', or 'r'. Ignore.
202 if s[0] not in ("'", '"'):
201 if s[0] not in ("'", '"'):
203 yield adjusttokenpos(t, coloffset)
202 yield adjusttokenpos(t, coloffset)
204 continue
203 continue
205
204
206 # String literal. Prefix to make a b'' string.
205 # String literal. Prefix to make a b'' string.
207 yield adjusttokenpos(t._replace(string='b%s' % t.string), coloffset)
206 yield adjusttokenpos(t._replace(string='b%s' % t.string), coloffset)
208 coldelta += 1
207 coldelta += 1
209 continue
208 continue
210
209
211 # This looks like a function call.
210 # This looks like a function call.
212 if t.type == token.NAME and _isop(i + 1, '('):
211 if t.type == token.NAME and _isop(i + 1, '('):
213 fn = t.string
212 fn = t.string
214
213
215 # *attr() builtins don't accept byte strings to 2nd argument.
214 # *attr() builtins don't accept byte strings to 2nd argument.
216 if (
215 if (
217 fn
216 fn
218 in (
217 in (
219 'getattr',
218 'getattr',
220 'setattr',
219 'setattr',
221 'hasattr',
220 'hasattr',
222 'safehasattr',
221 'safehasattr',
223 'wrapfunction',
222 'wrapfunction',
224 'wrapclass',
223 'wrapclass',
225 'addattr',
224 'addattr',
226 )
225 )
227 and (opts['allow-attr-methods'] or not _isop(i - 1, '.'))
226 and (opts['allow-attr-methods'] or not _isop(i - 1, '.'))
228 ):
227 ):
229 arg1idx = _findargnofcall(1)
228 arg1idx = _findargnofcall(1)
230 if arg1idx is not None:
229 if arg1idx is not None:
231 _ensuresysstr(arg1idx)
230 _ensuresysstr(arg1idx)
232
231
233 # .encode() and .decode() on str/bytes/unicode don't accept
232 # .encode() and .decode() on str/bytes/unicode don't accept
234 # byte strings on Python 3.
233 # byte strings on Python 3.
235 elif fn in ('encode', 'decode') and _isop(i - 1, '.'):
234 elif fn in ('encode', 'decode') and _isop(i - 1, '.'):
236 for argn in range(2):
235 for argn in range(2):
237 argidx = _findargnofcall(argn)
236 argidx = _findargnofcall(argn)
238 if argidx is not None:
237 if argidx is not None:
239 _ensuresysstr(argidx)
238 _ensuresysstr(argidx)
240
239
241 # It changes iteritems/values to items/values as they are not
240 # It changes iteritems/values to items/values as they are not
242 # present in Python 3 world.
241 # present in Python 3 world.
243 elif opts['dictiter'] and fn in ('iteritems', 'itervalues'):
242 elif opts['dictiter'] and fn in ('iteritems', 'itervalues'):
244 yield adjusttokenpos(t._replace(string=fn[4:]), coloffset)
243 yield adjusttokenpos(t._replace(string=fn[4:]), coloffset)
245 continue
244 continue
246
245
247 if t.type == token.NAME and t.string in opts['treat-as-kwargs']:
246 if t.type == token.NAME and t.string in opts['treat-as-kwargs']:
248 if _isitemaccess(i):
247 if _isitemaccess(i):
249 _ensuresysstr(i + 2)
248 _ensuresysstr(i + 2)
250 if _ismethodcall(i, 'get', 'pop', 'setdefault', 'popitem'):
249 if _ismethodcall(i, 'get', 'pop', 'setdefault', 'popitem'):
251 _ensuresysstr(i + 4)
250 _ensuresysstr(i + 4)
252
251
253 # Looks like "if __name__ == '__main__'".
252 # Looks like "if __name__ == '__main__'".
254 if (
253 if (
255 t.type == token.NAME
254 t.type == token.NAME
256 and t.string == '__name__'
255 and t.string == '__name__'
257 and _isop(i + 1, '==')
256 and _isop(i + 1, '==')
258 ):
257 ):
259 _ensuresysstr(i + 2)
258 _ensuresysstr(i + 2)
260
259
261 # Emit unmodified token.
260 # Emit unmodified token.
262 yield adjusttokenpos(t, coloffset)
261 yield adjusttokenpos(t, coloffset)
263
262
264
263
265 def process(fin, fout, opts):
264 def process(fin, fout, opts):
266 tokens = tokenize.tokenize(fin.readline)
265 tokens = tokenize.tokenize(fin.readline)
267 tokens = replacetokens(list(tokens), opts)
266 tokens = replacetokens(list(tokens), opts)
268 fout.write(tokenize.untokenize(tokens))
267 fout.write(tokenize.untokenize(tokens))
269
268
270
269
271 def tryunlink(fname):
270 def tryunlink(fname):
272 try:
271 try:
273 os.unlink(fname)
272 os.unlink(fname)
274 except OSError as err:
273 except OSError as err:
275 if err.errno != errno.ENOENT:
274 if err.errno != errno.ENOENT:
276 raise
275 raise
277
276
278
277
279 @contextlib.contextmanager
278 @contextlib.contextmanager
280 def editinplace(fname):
279 def editinplace(fname):
281 n = os.path.basename(fname)
280 n = os.path.basename(fname)
282 d = os.path.dirname(fname)
281 d = os.path.dirname(fname)
283 fp = tempfile.NamedTemporaryFile(
282 fp = tempfile.NamedTemporaryFile(
284 prefix='.%s-' % n, suffix='~', dir=d, delete=False
283 prefix='.%s-' % n, suffix='~', dir=d, delete=False
285 )
284 )
286 try:
285 try:
287 yield fp
286 yield fp
288 fp.close()
287 fp.close()
289 if os.name == 'nt':
288 if os.name == 'nt':
290 tryunlink(fname)
289 tryunlink(fname)
291 os.rename(fp.name, fname)
290 os.rename(fp.name, fname)
292 finally:
291 finally:
293 fp.close()
292 fp.close()
294 tryunlink(fp.name)
293 tryunlink(fp.name)
295
294
296
295
297 def main():
296 def main():
298 ap = argparse.ArgumentParser()
297 ap = argparse.ArgumentParser()
299 ap.add_argument(
298 ap.add_argument(
300 '--version', action='version', version='Byteify strings 1.0'
299 '--version', action='version', version='Byteify strings 1.0'
301 )
300 )
302 ap.add_argument(
301 ap.add_argument(
303 '-i',
302 '-i',
304 '--inplace',
303 '--inplace',
305 action='store_true',
304 action='store_true',
306 default=False,
305 default=False,
307 help='edit files in place',
306 help='edit files in place',
308 )
307 )
309 ap.add_argument(
308 ap.add_argument(
310 '--dictiter',
309 '--dictiter',
311 action='store_true',
310 action='store_true',
312 default=False,
311 default=False,
313 help='rewrite iteritems() and itervalues()',
312 help='rewrite iteritems() and itervalues()',
314 ),
313 ),
315 ap.add_argument(
314 ap.add_argument(
316 '--allow-attr-methods',
315 '--allow-attr-methods',
317 action='store_true',
316 action='store_true',
318 default=False,
317 default=False,
319 help='also handle attr*() when they are methods',
318 help='also handle attr*() when they are methods',
320 ),
319 ),
321 ap.add_argument(
320 ap.add_argument(
322 '--treat-as-kwargs',
321 '--treat-as-kwargs',
323 nargs="+",
322 nargs="+",
324 default=[],
323 default=[],
325 help="ignore kwargs-like objects",
324 help="ignore kwargs-like objects",
326 ),
325 ),
327 ap.add_argument('files', metavar='FILE', nargs='+', help='source file')
326 ap.add_argument('files', metavar='FILE', nargs='+', help='source file')
328 args = ap.parse_args()
327 args = ap.parse_args()
329 opts = {
328 opts = {
330 'dictiter': args.dictiter,
329 'dictiter': args.dictiter,
331 'treat-as-kwargs': set(args.treat_as_kwargs),
330 'treat-as-kwargs': set(args.treat_as_kwargs),
332 'allow-attr-methods': args.allow_attr_methods,
331 'allow-attr-methods': args.allow_attr_methods,
333 }
332 }
334 for fname in args.files:
333 for fname in args.files:
335 fname = os.path.realpath(fname)
334 fname = os.path.realpath(fname)
336 if args.inplace:
335 if args.inplace:
337 with editinplace(fname) as fout:
336 with editinplace(fname) as fout:
338 with open(fname, 'rb') as fin:
337 with open(fname, 'rb') as fin:
339 process(fin, fout, opts)
338 process(fin, fout, opts)
340 else:
339 else:
341 with open(fname, 'rb') as fin:
340 with open(fname, 'rb') as fin:
342 fout = sys.stdout.buffer
341 fout = sys.stdout.buffer
343 process(fin, fout, opts)
342 process(fin, fout, opts)
344
343
345
344
346 if __name__ == '__main__':
345 if __name__ == '__main__':
347 if sys.version_info[0:2] < (3, 7):
346 if sys.version_info[0:2] < (3, 7):
348 print('This script must be run under Python 3.7+')
347 print('This script must be run under Python 3.7+')
349 sys.exit(3)
348 sys.exit(3)
350 main()
349 main()
@@ -1,41 +1,40 b''
1 from __future__ import absolute_import
2 import __builtin__
1 import __builtin__
3 import os
2 import os
4 from mercurial import util
3 from mercurial import util
5
4
6
5
7 def lowerwrap(scope, funcname):
6 def lowerwrap(scope, funcname):
8 f = getattr(scope, funcname)
7 f = getattr(scope, funcname)
9
8
10 def wrap(fname, *args, **kwargs):
9 def wrap(fname, *args, **kwargs):
11 d, base = os.path.split(fname)
10 d, base = os.path.split(fname)
12 try:
11 try:
13 files = os.listdir(d or '.')
12 files = os.listdir(d or '.')
14 except OSError:
13 except OSError:
15 files = []
14 files = []
16 if base in files:
15 if base in files:
17 return f(fname, *args, **kwargs)
16 return f(fname, *args, **kwargs)
18 for fn in files:
17 for fn in files:
19 if fn.lower() == base.lower():
18 if fn.lower() == base.lower():
20 return f(os.path.join(d, fn), *args, **kwargs)
19 return f(os.path.join(d, fn), *args, **kwargs)
21 return f(fname, *args, **kwargs)
20 return f(fname, *args, **kwargs)
22
21
23 scope.__dict__[funcname] = wrap
22 scope.__dict__[funcname] = wrap
24
23
25
24
26 def normcase(path):
25 def normcase(path):
27 return path.lower()
26 return path.lower()
28
27
29
28
30 os.path.normcase = normcase
29 os.path.normcase = normcase
31
30
32 for f in 'file open'.split():
31 for f in 'file open'.split():
33 lowerwrap(__builtin__, f)
32 lowerwrap(__builtin__, f)
34
33
35 for f in "chmod chown open lstat stat remove unlink".split():
34 for f in "chmod chown open lstat stat remove unlink".split():
36 lowerwrap(os, f)
35 lowerwrap(os, f)
37
36
38 for f in "exists lexists".split():
37 for f in "exists lexists".split():
39 lowerwrap(os.path, f)
38 lowerwrap(os.path, f)
40
39
41 lowerwrap(util, 'posixfile')
40 lowerwrap(util, 'posixfile')
@@ -1,121 +1,120 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 #
2 #
3 # Copyright 2018 Google LLC.
3 # Copyright 2018 Google LLC.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 """Tool read primitive events from a pipe to produce a catapult trace.
7 """Tool read primitive events from a pipe to produce a catapult trace.
8
8
9 Usage:
9 Usage:
10 Terminal 1: $ catapipe.py /tmp/mypipe /tmp/trace.json
10 Terminal 1: $ catapipe.py /tmp/mypipe /tmp/trace.json
11 Terminal 2: $ HGCATAPULTSERVERPIPE=/tmp/mypipe hg root
11 Terminal 2: $ HGCATAPULTSERVERPIPE=/tmp/mypipe hg root
12 <ctrl-c catapipe.py in Terminal 1>
12 <ctrl-c catapipe.py in Terminal 1>
13 $ catapult/tracing/bin/trace2html /tmp/trace.json # produce /tmp/trace.html
13 $ catapult/tracing/bin/trace2html /tmp/trace.json # produce /tmp/trace.html
14 <open trace.html in your browser of choice; the WASD keys are very useful>
14 <open trace.html in your browser of choice; the WASD keys are very useful>
15 (catapult is located at https://github.com/catapult-project/catapult)
15 (catapult is located at https://github.com/catapult-project/catapult)
16
16
17 For now the event stream supports
17 For now the event stream supports
18
18
19 START $SESSIONID ...
19 START $SESSIONID ...
20
20
21 and
21 and
22
22
23 END $SESSIONID ...
23 END $SESSIONID ...
24
24
25 events. Everything after the SESSIONID (which must not contain spaces)
25 events. Everything after the SESSIONID (which must not contain spaces)
26 is used as a label for the event. Events are timestamped as of when
26 is used as a label for the event. Events are timestamped as of when
27 they arrive in this process and are then used to produce catapult
27 they arrive in this process and are then used to produce catapult
28 traces that can be loaded in Chrome's about:tracing utility. It's
28 traces that can be loaded in Chrome's about:tracing utility. It's
29 important that the event stream *into* this process stay simple,
29 important that the event stream *into* this process stay simple,
30 because we have to emit it from the shell scripts produced by
30 because we have to emit it from the shell scripts produced by
31 run-tests.py.
31 run-tests.py.
32
32
33 Typically you'll want to place the path to the named pipe in the
33 Typically you'll want to place the path to the named pipe in the
34 HGCATAPULTSERVERPIPE environment variable, which both run-tests and hg
34 HGCATAPULTSERVERPIPE environment variable, which both run-tests and hg
35 understand. To trace *only* run-tests, use HGTESTCATAPULTSERVERPIPE instead.
35 understand. To trace *only* run-tests, use HGTESTCATAPULTSERVERPIPE instead.
36 """
36 """
37 from __future__ import absolute_import, print_function
38
37
39 import argparse
38 import argparse
40 import json
39 import json
41 import os
40 import os
42 import timeit
41 import timeit
43
42
44 _TYPEMAP = {
43 _TYPEMAP = {
45 'START': 'B',
44 'START': 'B',
46 'END': 'E',
45 'END': 'E',
47 'COUNTER': 'C',
46 'COUNTER': 'C',
48 }
47 }
49
48
50 _threadmap = {}
49 _threadmap = {}
51
50
52 # Timeit already contains the whole logic about which timer to use based on
51 # Timeit already contains the whole logic about which timer to use based on
53 # Python version and OS
52 # Python version and OS
54 timer = timeit.default_timer
53 timer = timeit.default_timer
55
54
56
55
57 def main():
56 def main():
58 parser = argparse.ArgumentParser()
57 parser = argparse.ArgumentParser()
59 parser.add_argument(
58 parser.add_argument(
60 'pipe',
59 'pipe',
61 type=str,
60 type=str,
62 nargs=1,
61 nargs=1,
63 help='Path of named pipe to create and listen on.',
62 help='Path of named pipe to create and listen on.',
64 )
63 )
65 parser.add_argument(
64 parser.add_argument(
66 'output',
65 'output',
67 default='trace.json',
66 default='trace.json',
68 type=str,
67 type=str,
69 nargs='?',
68 nargs='?',
70 help='Path of json file to create where the traces ' 'will be stored.',
69 help='Path of json file to create where the traces ' 'will be stored.',
71 )
70 )
72 parser.add_argument(
71 parser.add_argument(
73 '--debug',
72 '--debug',
74 default=False,
73 default=False,
75 action='store_true',
74 action='store_true',
76 help='Print useful debug messages',
75 help='Print useful debug messages',
77 )
76 )
78 args = parser.parse_args()
77 args = parser.parse_args()
79 fn = args.pipe[0]
78 fn = args.pipe[0]
80 os.mkfifo(fn)
79 os.mkfifo(fn)
81 try:
80 try:
82 with open(fn) as f, open(args.output, 'w') as out:
81 with open(fn) as f, open(args.output, 'w') as out:
83 out.write('[\n')
82 out.write('[\n')
84 start = timer()
83 start = timer()
85 while True:
84 while True:
86 ev = f.readline().strip()
85 ev = f.readline().strip()
87 if not ev:
86 if not ev:
88 continue
87 continue
89 now = timer()
88 now = timer()
90 if args.debug:
89 if args.debug:
91 print(ev)
90 print(ev)
92 verb, session, label = ev.split(' ', 2)
91 verb, session, label = ev.split(' ', 2)
93 if session not in _threadmap:
92 if session not in _threadmap:
94 _threadmap[session] = len(_threadmap)
93 _threadmap[session] = len(_threadmap)
95 if verb == 'COUNTER':
94 if verb == 'COUNTER':
96 amount, label = label.split(' ', 1)
95 amount, label = label.split(' ', 1)
97 payload_args = {'value': int(amount)}
96 payload_args = {'value': int(amount)}
98 else:
97 else:
99 payload_args = {}
98 payload_args = {}
100 pid = _threadmap[session]
99 pid = _threadmap[session]
101 ts_micros = (now - start) * 1000000
100 ts_micros = (now - start) * 1000000
102 out.write(
101 out.write(
103 json.dumps(
102 json.dumps(
104 {
103 {
105 "name": label,
104 "name": label,
106 "cat": "misc",
105 "cat": "misc",
107 "ph": _TYPEMAP[verb],
106 "ph": _TYPEMAP[verb],
108 "ts": ts_micros,
107 "ts": ts_micros,
109 "pid": pid,
108 "pid": pid,
110 "tid": 1,
109 "tid": 1,
111 "args": payload_args,
110 "args": payload_args,
112 }
111 }
113 )
112 )
114 )
113 )
115 out.write(',\n')
114 out.write(',\n')
116 finally:
115 finally:
117 os.unlink(fn)
116 os.unlink(fn)
118
117
119
118
120 if __name__ == '__main__':
119 if __name__ == '__main__':
121 main()
120 main()
@@ -1,1124 +1,1123 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 #
2 #
3 # check-code - a style and portability checker for Mercurial
3 # check-code - a style and portability checker for Mercurial
4 #
4 #
5 # Copyright 2010 Olivia Mackall <olivia@selenic.com>
5 # Copyright 2010 Olivia Mackall <olivia@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """style and portability checker for Mercurial
10 """style and portability checker for Mercurial
11
11
12 when a rule triggers wrong, do one of the following (prefer one from top):
12 when a rule triggers wrong, do one of the following (prefer one from top):
13 * do the work-around the rule suggests
13 * do the work-around the rule suggests
14 * doublecheck that it is a false match
14 * doublecheck that it is a false match
15 * improve the rule pattern
15 * improve the rule pattern
16 * add an ignore pattern to the rule (3rd arg) which matches your good line
16 * add an ignore pattern to the rule (3rd arg) which matches your good line
17 (you can append a short comment and match this, like: #re-raises)
17 (you can append a short comment and match this, like: #re-raises)
18 * change the pattern to a warning and list the exception in test-check-code-hg
18 * change the pattern to a warning and list the exception in test-check-code-hg
19 * ONLY use no--check-code for skipping entire files from external sources
19 * ONLY use no--check-code for skipping entire files from external sources
20 """
20 """
21
21
22 from __future__ import absolute_import, print_function
23 import glob
22 import glob
24 import keyword
23 import keyword
25 import optparse
24 import optparse
26 import os
25 import os
27 import re
26 import re
28 import sys
27 import sys
29
28
30 if sys.version_info[0] < 3:
29 if sys.version_info[0] < 3:
31 opentext = open
30 opentext = open
32 else:
31 else:
33
32
34 def opentext(f):
33 def opentext(f):
35 return open(f, encoding='latin1')
34 return open(f, encoding='latin1')
36
35
37
36
38 try:
37 try:
39 xrange
38 xrange
40 except NameError:
39 except NameError:
41 xrange = range
40 xrange = range
42 try:
41 try:
43 import re2
42 import re2
44 except ImportError:
43 except ImportError:
45 re2 = None
44 re2 = None
46
45
47 import testparseutil
46 import testparseutil
48
47
49
48
50 def compilere(pat, multiline=False):
49 def compilere(pat, multiline=False):
51 if multiline:
50 if multiline:
52 pat = '(?m)' + pat
51 pat = '(?m)' + pat
53 if re2:
52 if re2:
54 try:
53 try:
55 return re2.compile(pat)
54 return re2.compile(pat)
56 except re2.error:
55 except re2.error:
57 pass
56 pass
58 return re.compile(pat)
57 return re.compile(pat)
59
58
60
59
61 # check "rules depending on implementation of repquote()" in each
60 # check "rules depending on implementation of repquote()" in each
62 # patterns (especially pypats), before changing around repquote()
61 # patterns (especially pypats), before changing around repquote()
63 _repquotefixedmap = {
62 _repquotefixedmap = {
64 ' ': ' ',
63 ' ': ' ',
65 '\n': '\n',
64 '\n': '\n',
66 '.': 'p',
65 '.': 'p',
67 ':': 'q',
66 ':': 'q',
68 '%': '%',
67 '%': '%',
69 '\\': 'b',
68 '\\': 'b',
70 '*': 'A',
69 '*': 'A',
71 '+': 'P',
70 '+': 'P',
72 '-': 'M',
71 '-': 'M',
73 }
72 }
74
73
75
74
76 def _repquoteencodechr(i):
75 def _repquoteencodechr(i):
77 if i > 255:
76 if i > 255:
78 return 'u'
77 return 'u'
79 c = chr(i)
78 c = chr(i)
80 if c in _repquotefixedmap:
79 if c in _repquotefixedmap:
81 return _repquotefixedmap[c]
80 return _repquotefixedmap[c]
82 if c.isalpha():
81 if c.isalpha():
83 return 'x'
82 return 'x'
84 if c.isdigit():
83 if c.isdigit():
85 return 'n'
84 return 'n'
86 return 'o'
85 return 'o'
87
86
88
87
89 _repquotett = ''.join(_repquoteencodechr(i) for i in xrange(256))
88 _repquotett = ''.join(_repquoteencodechr(i) for i in xrange(256))
90
89
91
90
92 def repquote(m):
91 def repquote(m):
93 t = m.group('text')
92 t = m.group('text')
94 t = t.translate(_repquotett)
93 t = t.translate(_repquotett)
95 return m.group('quote') + t + m.group('quote')
94 return m.group('quote') + t + m.group('quote')
96
95
97
96
98 def reppython(m):
97 def reppython(m):
99 comment = m.group('comment')
98 comment = m.group('comment')
100 if comment:
99 if comment:
101 l = len(comment.rstrip())
100 l = len(comment.rstrip())
102 return "#" * l + comment[l:]
101 return "#" * l + comment[l:]
103 return repquote(m)
102 return repquote(m)
104
103
105
104
106 def repcomment(m):
105 def repcomment(m):
107 return m.group(1) + "#" * len(m.group(2))
106 return m.group(1) + "#" * len(m.group(2))
108
107
109
108
110 def repccomment(m):
109 def repccomment(m):
111 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
110 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
112 return m.group(1) + t + "*/"
111 return m.group(1) + t + "*/"
113
112
114
113
115 def repcallspaces(m):
114 def repcallspaces(m):
116 t = re.sub(r"\n\s+", "\n", m.group(2))
115 t = re.sub(r"\n\s+", "\n", m.group(2))
117 return m.group(1) + t
116 return m.group(1) + t
118
117
119
118
120 def repinclude(m):
119 def repinclude(m):
121 return m.group(1) + "<foo>"
120 return m.group(1) + "<foo>"
122
121
123
122
124 def rephere(m):
123 def rephere(m):
125 t = re.sub(r"\S", "x", m.group(2))
124 t = re.sub(r"\S", "x", m.group(2))
126 return m.group(1) + t
125 return m.group(1) + t
127
126
128
127
129 testpats = [
128 testpats = [
130 [
129 [
131 (r'\b(push|pop)d\b', "don't use 'pushd' or 'popd', use 'cd'"),
130 (r'\b(push|pop)d\b', "don't use 'pushd' or 'popd', use 'cd'"),
132 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
131 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
133 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
132 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
134 (r'(?<!hg )grep.* -a', "don't use 'grep -a', use in-line python"),
133 (r'(?<!hg )grep.* -a', "don't use 'grep -a', use in-line python"),
135 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
134 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
136 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
135 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
137 (r'echo -n', "don't use 'echo -n', use printf"),
136 (r'echo -n', "don't use 'echo -n', use printf"),
138 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
137 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
139 (r'head -c', "don't use 'head -c', use 'dd'"),
138 (r'head -c', "don't use 'head -c', use 'dd'"),
140 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
139 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
141 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
140 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
142 (r'\bls\b.*-\w*R', "don't use 'ls -R', use 'find'"),
141 (r'\bls\b.*-\w*R', "don't use 'ls -R', use 'find'"),
143 (r'printf.*[^\\]\\([1-9]|0\d)', r"don't use 'printf \NNN', use Python"),
142 (r'printf.*[^\\]\\([1-9]|0\d)', r"don't use 'printf \NNN', use Python"),
144 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
143 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
145 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
144 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
146 (
145 (
147 r'\[[^\]]+==',
146 r'\[[^\]]+==',
148 '[ foo == bar ] is a bashism, use [ foo = bar ] instead',
147 '[ foo == bar ] is a bashism, use [ foo = bar ] instead',
149 ),
148 ),
150 (
149 (
151 r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
150 r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
152 "use egrep for extended grep syntax",
151 "use egrep for extended grep syntax",
153 ),
152 ),
154 (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"),
153 (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"),
155 (r'(?<!!)/bin/', "don't use explicit paths for tools"),
154 (r'(?<!!)/bin/', "don't use explicit paths for tools"),
156 (r'#!.*/bash', "don't use bash in shebang, use sh"),
155 (r'#!.*/bash', "don't use bash in shebang, use sh"),
157 (r'[^\n]\Z', "no trailing newline"),
156 (r'[^\n]\Z', "no trailing newline"),
158 (r'export .*=', "don't export and assign at once"),
157 (r'export .*=', "don't export and assign at once"),
159 (r'^source\b', "don't use 'source', use '.'"),
158 (r'^source\b', "don't use 'source', use '.'"),
160 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
159 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
161 (r'\bls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
160 (r'\bls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
162 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
161 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
163 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
162 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
164 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
163 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
165 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
164 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
166 (r'^alias\b.*=', "don't use alias, use a function"),
165 (r'^alias\b.*=', "don't use alias, use a function"),
167 (r'if\s*!', "don't use '!' to negate exit status"),
166 (r'if\s*!', "don't use '!' to negate exit status"),
168 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
167 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
169 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
168 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
170 (
169 (
171 r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
170 r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
172 "put a backslash-escaped newline after sed 'i' command",
171 "put a backslash-escaped newline after sed 'i' command",
173 ),
172 ),
174 (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"),
173 (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"),
175 (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"),
174 (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"),
176 (r'[\s="`\']python\s(?!bindings)', "don't use 'python', use '$PYTHON'"),
175 (r'[\s="`\']python\s(?!bindings)', "don't use 'python', use '$PYTHON'"),
177 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"),
176 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"),
178 (r'\butil\.Abort\b', "directly use error.Abort"),
177 (r'\butil\.Abort\b', "directly use error.Abort"),
179 (r'\|&', "don't use |&, use 2>&1"),
178 (r'\|&', "don't use |&, use 2>&1"),
180 (r'\w = +\w', "only one space after = allowed"),
179 (r'\w = +\w', "only one space after = allowed"),
181 (
180 (
182 r'\bsed\b.*[^\\]\\n',
181 r'\bsed\b.*[^\\]\\n',
183 "don't use 'sed ... \\n', use a \\ and a newline",
182 "don't use 'sed ... \\n', use a \\ and a newline",
184 ),
183 ),
185 (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'"),
184 (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'"),
186 (r'cp.* -r ', "don't use 'cp -r', use 'cp -R'"),
185 (r'cp.* -r ', "don't use 'cp -r', use 'cp -R'"),
187 (r'grep.* -[ABC]', "don't use grep's context flags"),
186 (r'grep.* -[ABC]', "don't use grep's context flags"),
188 (
187 (
189 r'find.*-printf',
188 r'find.*-printf',
190 "don't use 'find -printf', it doesn't exist on BSD find(1)",
189 "don't use 'find -printf', it doesn't exist on BSD find(1)",
191 ),
190 ),
192 (r'\$RANDOM ', "don't use bash-only $RANDOM to generate random values"),
191 (r'\$RANDOM ', "don't use bash-only $RANDOM to generate random values"),
193 ],
192 ],
194 # warnings
193 # warnings
195 [
194 [
196 (r'^function', "don't use 'function', use old style"),
195 (r'^function', "don't use 'function', use old style"),
197 (r'^diff.*-\w*N', "don't use 'diff -N'"),
196 (r'^diff.*-\w*N', "don't use 'diff -N'"),
198 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`", "no-pwd-check"),
197 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`", "no-pwd-check"),
199 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
198 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
200 (r'kill (`|\$\()', "don't use kill, use killdaemons.py"),
199 (r'kill (`|\$\()', "don't use kill, use killdaemons.py"),
201 ],
200 ],
202 ]
201 ]
203
202
204 testfilters = [
203 testfilters = [
205 (r"( *)(#([^!][^\n]*\S)?)", repcomment),
204 (r"( *)(#([^!][^\n]*\S)?)", repcomment),
206 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
205 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
207 ]
206 ]
208
207
209 uprefix = r"^ \$ "
208 uprefix = r"^ \$ "
210 utestpats = [
209 utestpats = [
211 [
210 [
212 (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"),
211 (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"),
213 (
212 (
214 uprefix + r'.*\|\s*sed[^|>\n]*\n',
213 uprefix + r'.*\|\s*sed[^|>\n]*\n',
215 "use regex test output patterns instead of sed",
214 "use regex test output patterns instead of sed",
216 ),
215 ),
217 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
216 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
218 (
217 (
219 uprefix + r'.*\|\| echo.*(fail|error)',
218 uprefix + r'.*\|\| echo.*(fail|error)',
220 "explicit exit code checks unnecessary",
219 "explicit exit code checks unnecessary",
221 ),
220 ),
222 (uprefix + r'set -e', "don't use set -e"),
221 (uprefix + r'set -e', "don't use set -e"),
223 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
222 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
224 (
223 (
225 uprefix + r'.*:\.\S*/',
224 uprefix + r'.*:\.\S*/',
226 "x:.y in a path does not work on msys, rewrite "
225 "x:.y in a path does not work on msys, rewrite "
227 "as x://.y, or see `hg log -k msys` for alternatives",
226 "as x://.y, or see `hg log -k msys` for alternatives",
228 r'-\S+:\.|' '# no-msys', # -Rxxx
227 r'-\S+:\.|' '# no-msys', # -Rxxx
229 ), # in test-pull.t which is skipped on windows
228 ), # in test-pull.t which is skipped on windows
230 (
229 (
231 r'^ [^$>].*27\.0\.0\.1',
230 r'^ [^$>].*27\.0\.0\.1',
232 'use $LOCALIP not an explicit loopback address',
231 'use $LOCALIP not an explicit loopback address',
233 ),
232 ),
234 (
233 (
235 r'^ (?![>$] ).*\$LOCALIP.*[^)]$',
234 r'^ (?![>$] ).*\$LOCALIP.*[^)]$',
236 'mark $LOCALIP output lines with (glob) to help tests in BSD jails',
235 'mark $LOCALIP output lines with (glob) to help tests in BSD jails',
237 ),
236 ),
238 (
237 (
239 r'^ (cat|find): .*: \$ENOENT\$',
238 r'^ (cat|find): .*: \$ENOENT\$',
240 'use test -f to test for file existence',
239 'use test -f to test for file existence',
241 ),
240 ),
242 (
241 (
243 r'^ diff -[^ -]*p',
242 r'^ diff -[^ -]*p',
244 "don't use (external) diff with -p for portability",
243 "don't use (external) diff with -p for portability",
245 ),
244 ),
246 (r' readlink ', 'use readlink.py instead of readlink'),
245 (r' readlink ', 'use readlink.py instead of readlink'),
247 (
246 (
248 r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
247 r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
249 "glob timezone field in diff output for portability",
248 "glob timezone field in diff output for portability",
250 ),
249 ),
251 (
250 (
252 r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
251 r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
253 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability",
252 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability",
254 ),
253 ),
255 (
254 (
256 r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
255 r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
257 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability",
256 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability",
258 ),
257 ),
259 (
258 (
260 r'^ @@ -[0-9]+ [+][0-9]+ @@',
259 r'^ @@ -[0-9]+ [+][0-9]+ @@',
261 "use '@@ -N* +N* @@ (glob)' style chunk header for portability",
260 "use '@@ -N* +N* @@ (glob)' style chunk header for portability",
262 ),
261 ),
263 (
262 (
264 uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
263 uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
265 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
264 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
266 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)",
265 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)",
267 ),
266 ),
268 ],
267 ],
269 # warnings
268 # warnings
270 [
269 [
271 (
270 (
272 r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$',
271 r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$',
273 "glob match with no glob string (?, *, /, and $LOCALIP)",
272 "glob match with no glob string (?, *, /, and $LOCALIP)",
274 ),
273 ),
275 ],
274 ],
276 ]
275 ]
277
276
278 # transform plain test rules to unified test's
277 # transform plain test rules to unified test's
279 for i in [0, 1]:
278 for i in [0, 1]:
280 for tp in testpats[i]:
279 for tp in testpats[i]:
281 p = tp[0]
280 p = tp[0]
282 m = tp[1]
281 m = tp[1]
283 if p.startswith('^'):
282 if p.startswith('^'):
284 p = "^ [$>] (%s)" % p[1:]
283 p = "^ [$>] (%s)" % p[1:]
285 else:
284 else:
286 p = "^ [$>] .*(%s)" % p
285 p = "^ [$>] .*(%s)" % p
287 utestpats[i].append((p, m) + tp[2:])
286 utestpats[i].append((p, m) + tp[2:])
288
287
289 # don't transform the following rules:
288 # don't transform the following rules:
290 # " > \t" and " \t" should be allowed in unified tests
289 # " > \t" and " \t" should be allowed in unified tests
291 testpats[0].append((r'^( *)\t', "don't use tabs to indent"))
290 testpats[0].append((r'^( *)\t', "don't use tabs to indent"))
292 utestpats[0].append((r'^( ?)\t', "don't use tabs to indent"))
291 utestpats[0].append((r'^( ?)\t', "don't use tabs to indent"))
293
292
294 utestfilters = [
293 utestfilters = [
295 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
294 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
296 (r"( +)(#([^!][^\n]*\S)?)", repcomment),
295 (r"( +)(#([^!][^\n]*\S)?)", repcomment),
297 ]
296 ]
298
297
299 # common patterns to check *.py
298 # common patterns to check *.py
300 commonpypats = [
299 commonpypats = [
301 [
300 [
302 (r'\\$', 'Use () to wrap long lines in Python, not \\'),
301 (r'\\$', 'Use () to wrap long lines in Python, not \\'),
303 (
302 (
304 r'^\s*def\s*\w+\s*\(.*,\s*\(',
303 r'^\s*def\s*\w+\s*\(.*,\s*\(',
305 "tuple parameter unpacking not available in Python 3+",
304 "tuple parameter unpacking not available in Python 3+",
306 ),
305 ),
307 (
306 (
308 r'lambda\s*\(.*,.*\)',
307 r'lambda\s*\(.*,.*\)',
309 "tuple parameter unpacking not available in Python 3+",
308 "tuple parameter unpacking not available in Python 3+",
310 ),
309 ),
311 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
310 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
312 (r'(?<!\.)\breduce\s*\(.*', "reduce is not available in Python 3+"),
311 (r'(?<!\.)\breduce\s*\(.*', "reduce is not available in Python 3+"),
313 (
312 (
314 r'\bdict\(.*=',
313 r'\bdict\(.*=',
315 'dict() is different in Py2 and 3 and is slower than {}',
314 'dict() is different in Py2 and 3 and is slower than {}',
316 'dict-from-generator',
315 'dict-from-generator',
317 ),
316 ),
318 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
317 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
319 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
318 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
320 (r'^\s*\t', "don't use tabs"),
319 (r'^\s*\t', "don't use tabs"),
321 (r'\S;\s*\n', "semicolon"),
320 (r'\S;\s*\n', "semicolon"),
322 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
321 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
323 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
322 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
324 (r'(\w|\)),\w', "missing whitespace after ,"),
323 (r'(\w|\)),\w', "missing whitespace after ,"),
325 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
324 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
326 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
325 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
327 (
326 (
328 (
327 (
329 # a line ending with a colon, potentially with trailing comments
328 # a line ending with a colon, potentially with trailing comments
330 r':([ \t]*#[^\n]*)?\n'
329 r':([ \t]*#[^\n]*)?\n'
331 # one that is not a pass and not only a comment
330 # one that is not a pass and not only a comment
332 r'(?P<indent>[ \t]+)[^#][^\n]+\n'
331 r'(?P<indent>[ \t]+)[^#][^\n]+\n'
333 # more lines at the same indent level
332 # more lines at the same indent level
334 r'((?P=indent)[^\n]+\n)*'
333 r'((?P=indent)[^\n]+\n)*'
335 # a pass at the same indent level, which is bogus
334 # a pass at the same indent level, which is bogus
336 r'(?P=indent)pass[ \t\n#]'
335 r'(?P=indent)pass[ \t\n#]'
337 ),
336 ),
338 'omit superfluous pass',
337 'omit superfluous pass',
339 ),
338 ),
340 (r'[^\n]\Z', "no trailing newline"),
339 (r'[^\n]\Z', "no trailing newline"),
341 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
340 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
342 (
341 (
343 r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
342 r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
344 "linebreak after :",
343 "linebreak after :",
345 ),
344 ),
346 (
345 (
347 r'class\s[^( \n]+:',
346 r'class\s[^( \n]+:',
348 "old-style class, use class foo(object)",
347 "old-style class, use class foo(object)",
349 r'#.*old-style',
348 r'#.*old-style',
350 ),
349 ),
351 (
350 (
352 r'class\s[^( \n]+\(\):',
351 r'class\s[^( \n]+\(\):',
353 "class foo() creates old style object, use class foo(object)",
352 "class foo() creates old style object, use class foo(object)",
354 r'#.*old-style',
353 r'#.*old-style',
355 ),
354 ),
356 (
355 (
357 r'\b(%s)\('
356 r'\b(%s)\('
358 % '|'.join(k for k in keyword.kwlist if k not in ('print', 'exec')),
357 % '|'.join(k for k in keyword.kwlist if k not in ('print', 'exec')),
359 "Python keyword is not a function",
358 "Python keyword is not a function",
360 ),
359 ),
361 # (r'class\s[A-Z][^\(]*\((?!Exception)',
360 # (r'class\s[A-Z][^\(]*\((?!Exception)',
362 # "don't capitalize non-exception classes"),
361 # "don't capitalize non-exception classes"),
363 # (r'in range\(', "use xrange"),
362 # (r'in range\(', "use xrange"),
364 # (r'^\s*print\s+', "avoid using print in core and extensions"),
363 # (r'^\s*print\s+', "avoid using print in core and extensions"),
365 (r'[\x80-\xff]', "non-ASCII character literal"),
364 (r'[\x80-\xff]', "non-ASCII character literal"),
366 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
365 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
367 (
366 (
368 r'([\(\[][ \t]\S)|(\S[ \t][\)\]])',
367 r'([\(\[][ \t]\S)|(\S[ \t][\)\]])',
369 "gratuitous whitespace in () or []",
368 "gratuitous whitespace in () or []",
370 ),
369 ),
371 # (r'\s\s=', "gratuitous whitespace before ="),
370 # (r'\s\s=', "gratuitous whitespace before ="),
372 (
371 (
373 r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
372 r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
374 "missing whitespace around operator",
373 "missing whitespace around operator",
375 ),
374 ),
376 (
375 (
377 r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
376 r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
378 "missing whitespace around operator",
377 "missing whitespace around operator",
379 ),
378 ),
380 (
379 (
381 r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
380 r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
382 "missing whitespace around operator",
381 "missing whitespace around operator",
383 ),
382 ),
384 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]', "wrong whitespace around ="),
383 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]', "wrong whitespace around ="),
385 (
384 (
386 r'\([^()]*( =[^=]|[^<>!=]= )',
385 r'\([^()]*( =[^=]|[^<>!=]= )',
387 "no whitespace around = for named parameters",
386 "no whitespace around = for named parameters",
388 ),
387 ),
389 (
388 (
390 r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
389 r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
391 "don't use old-style two-argument raise, use Exception(message)",
390 "don't use old-style two-argument raise, use Exception(message)",
392 ),
391 ),
393 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
392 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
394 (
393 (
395 r' [=!]=\s+(True|False|None)',
394 r' [=!]=\s+(True|False|None)',
396 "comparison with singleton, use 'is' or 'is not' instead",
395 "comparison with singleton, use 'is' or 'is not' instead",
397 ),
396 ),
398 (
397 (
399 r'^\s*(while|if) [01]:',
398 r'^\s*(while|if) [01]:',
400 "use True/False for constant Boolean expression",
399 "use True/False for constant Boolean expression",
401 ),
400 ),
402 (r'^\s*if False(:| +and)', 'Remove code instead of using `if False`'),
401 (r'^\s*if False(:| +and)', 'Remove code instead of using `if False`'),
403 (
402 (
404 r'(?:(?<!def)\s+|\()hasattr\(',
403 r'(?:(?<!def)\s+|\()hasattr\(',
405 'hasattr(foo, bar) is broken on py2, use util.safehasattr(foo, bar) '
404 'hasattr(foo, bar) is broken on py2, use util.safehasattr(foo, bar) '
406 'instead',
405 'instead',
407 r'#.*hasattr-py3-only',
406 r'#.*hasattr-py3-only',
408 ),
407 ),
409 (r'opener\([^)]*\).read\(', "use opener.read() instead"),
408 (r'opener\([^)]*\).read\(', "use opener.read() instead"),
410 (r'opener\([^)]*\).write\(', "use opener.write() instead"),
409 (r'opener\([^)]*\).write\(', "use opener.write() instead"),
411 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
410 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
412 (r'\.debug\(\_', "don't mark debug messages for translation"),
411 (r'\.debug\(\_', "don't mark debug messages for translation"),
413 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
412 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
414 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
413 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
415 (
414 (
416 r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
415 r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
417 'legacy exception syntax; use "as" instead of ","',
416 'legacy exception syntax; use "as" instead of ","',
418 ),
417 ),
419 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
418 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
420 (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
419 (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
421 (
420 (
422 r'os\.path\.join\(.*, *(""|\'\')\)',
421 r'os\.path\.join\(.*, *(""|\'\')\)',
423 "use pathutil.normasprefix(path) instead of os.path.join(path, '')",
422 "use pathutil.normasprefix(path) instead of os.path.join(path, '')",
424 ),
423 ),
425 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
424 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
426 # XXX only catch mutable arguments on the first line of the definition
425 # XXX only catch mutable arguments on the first line of the definition
427 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
426 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
428 (r'\butil\.Abort\b', "directly use error.Abort"),
427 (r'\butil\.Abort\b', "directly use error.Abort"),
429 (
428 (
430 r'^@(\w*\.)?cachefunc',
429 r'^@(\w*\.)?cachefunc',
431 "module-level @cachefunc is risky, please avoid",
430 "module-level @cachefunc is risky, please avoid",
432 ),
431 ),
433 (
432 (
434 r'^import Queue',
433 r'^import Queue',
435 "don't use Queue, use pycompat.queue.Queue + "
434 "don't use Queue, use pycompat.queue.Queue + "
436 "pycompat.queue.Empty",
435 "pycompat.queue.Empty",
437 ),
436 ),
438 (
437 (
439 r'^import cStringIO',
438 r'^import cStringIO',
440 "don't use cStringIO.StringIO, use util.stringio",
439 "don't use cStringIO.StringIO, use util.stringio",
441 ),
440 ),
442 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
441 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
443 (
442 (
444 r'^import SocketServer',
443 r'^import SocketServer',
445 "don't use SockerServer, use util.socketserver",
444 "don't use SockerServer, use util.socketserver",
446 ),
445 ),
447 (r'^import urlparse', "don't use urlparse, use util.urlreq"),
446 (r'^import urlparse', "don't use urlparse, use util.urlreq"),
448 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
447 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
449 (r'^import httplib', "don't use httplib, use util.httplib"),
448 (r'^import httplib', "don't use httplib, use util.httplib"),
450 (r'^import BaseHTTPServer', "use util.httpserver instead"),
449 (r'^import BaseHTTPServer', "use util.httpserver instead"),
451 (
450 (
452 r'^(from|import) mercurial\.(cext|pure|cffi)',
451 r'^(from|import) mercurial\.(cext|pure|cffi)',
453 "use mercurial.policy.importmod instead",
452 "use mercurial.policy.importmod instead",
454 ),
453 ),
455 (r'\.next\(\)', "don't use .next(), use next(...)"),
454 (r'\.next\(\)', "don't use .next(), use next(...)"),
456 (
455 (
457 r'([a-z]*).revision\(\1\.node\(',
456 r'([a-z]*).revision\(\1\.node\(',
458 "don't convert rev to node before passing to revision(nodeorrev)",
457 "don't convert rev to node before passing to revision(nodeorrev)",
459 ),
458 ),
460 (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
459 (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
461 ],
460 ],
462 # warnings
461 # warnings
463 [],
462 [],
464 ]
463 ]
465
464
466 # patterns to check normal *.py files
465 # patterns to check normal *.py files
467 pypats = [
466 pypats = [
468 [
467 [
469 # Ideally, these should be placed in "commonpypats" for
468 # Ideally, these should be placed in "commonpypats" for
470 # consistency of coding rules in Mercurial source tree.
469 # consistency of coding rules in Mercurial source tree.
471 # But on the other hand, these are not so seriously required for
470 # But on the other hand, these are not so seriously required for
472 # python code fragments embedded in test scripts. Fixing test
471 # python code fragments embedded in test scripts. Fixing test
473 # scripts for these patterns requires many changes, and has less
472 # scripts for these patterns requires many changes, and has less
474 # profit than effort.
473 # profit than effort.
475 (r'raise Exception', "don't raise generic exceptions"),
474 (r'raise Exception', "don't raise generic exceptions"),
476 (r'[\s\(](open|file)\([^)]*\)\.read\(', "use util.readfile() instead"),
475 (r'[\s\(](open|file)\([^)]*\)\.read\(', "use util.readfile() instead"),
477 (
476 (
478 r'[\s\(](open|file)\([^)]*\)\.write\(',
477 r'[\s\(](open|file)\([^)]*\)\.write\(',
479 "use util.writefile() instead",
478 "use util.writefile() instead",
480 ),
479 ),
481 (
480 (
482 r'^[\s\(]*(open(er)?|file)\([^)]*\)(?!\.close\(\))',
481 r'^[\s\(]*(open(er)?|file)\([^)]*\)(?!\.close\(\))',
483 "always assign an opened file to a variable, and close it afterwards",
482 "always assign an opened file to a variable, and close it afterwards",
484 ),
483 ),
485 (
484 (
486 r'[\s\(](open|file)\([^)]*\)\.(?!close\(\))',
485 r'[\s\(](open|file)\([^)]*\)\.(?!close\(\))',
487 "always assign an opened file to a variable, and close it afterwards",
486 "always assign an opened file to a variable, and close it afterwards",
488 ),
487 ),
489 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
488 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
490 (r'^import atexit', "don't use atexit, use ui.atexit"),
489 (r'^import atexit', "don't use atexit, use ui.atexit"),
491 # rules depending on implementation of repquote()
490 # rules depending on implementation of repquote()
492 (
491 (
493 r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
492 r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
494 'string join across lines with no space',
493 'string join across lines with no space',
495 ),
494 ),
496 (
495 (
497 r'''(?x)ui\.(status|progress|write|note|warn)\(
496 r'''(?x)ui\.(status|progress|write|note|warn)\(
498 [ \t\n#]*
497 [ \t\n#]*
499 (?# any strings/comments might precede a string, which
498 (?# any strings/comments might precede a string, which
500 # contains translatable message)
499 # contains translatable message)
501 b?((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
500 b?((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
502 (?# sequence consisting of below might precede translatable message
501 (?# sequence consisting of below might precede translatable message
503 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
502 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
504 # - escaped character: "\\", "\n", "\0" ...
503 # - escaped character: "\\", "\n", "\0" ...
505 # - character other than '%', 'b' as '\', and 'x' as alphabet)
504 # - character other than '%', 'b' as '\', and 'x' as alphabet)
506 (['"]|\'\'\'|""")
505 (['"]|\'\'\'|""")
507 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
506 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
508 (?# this regexp can't use [^...] style,
507 (?# this regexp can't use [^...] style,
509 # because _preparepats forcibly adds "\n" into [^...],
508 # because _preparepats forcibly adds "\n" into [^...],
510 # even though this regexp wants match it against "\n")''',
509 # even though this regexp wants match it against "\n")''',
511 "missing _() in ui message (use () to hide false-positives)",
510 "missing _() in ui message (use () to hide false-positives)",
512 ),
511 ),
513 ]
512 ]
514 + commonpypats[0],
513 + commonpypats[0],
515 # warnings
514 # warnings
516 [
515 [
517 # rules depending on implementation of repquote()
516 # rules depending on implementation of repquote()
518 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
517 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
519 ]
518 ]
520 + commonpypats[1],
519 + commonpypats[1],
521 ]
520 ]
522
521
523 # patterns to check *.py for embedded ones in test script
522 # patterns to check *.py for embedded ones in test script
524 embeddedpypats = [
523 embeddedpypats = [
525 [] + commonpypats[0],
524 [] + commonpypats[0],
526 # warnings
525 # warnings
527 [] + commonpypats[1],
526 [] + commonpypats[1],
528 ]
527 ]
529
528
530 # common filters to convert *.py
529 # common filters to convert *.py
531 commonpyfilters = [
530 commonpyfilters = [
532 (
531 (
533 r"""(?msx)(?P<comment>\#.*?$)|
532 r"""(?msx)(?P<comment>\#.*?$)|
534 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
533 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
535 (?P<text>(([^\\]|\\.)*?))
534 (?P<text>(([^\\]|\\.)*?))
536 (?P=quote))""",
535 (?P=quote))""",
537 reppython,
536 reppython,
538 ),
537 ),
539 ]
538 ]
540
539
541 # pattern only for mercurial and extensions
540 # pattern only for mercurial and extensions
542 core_py_pats = [
541 core_py_pats = [
543 [
542 [
544 # Windows tend to get confused about capitalization of the drive letter
543 # Windows tend to get confused about capitalization of the drive letter
545 #
544 #
546 # see mercurial.windows.abspath for details
545 # see mercurial.windows.abspath for details
547 (
546 (
548 r'os\.path\.abspath',
547 r'os\.path\.abspath',
549 "use util.abspath instead (windows)",
548 "use util.abspath instead (windows)",
550 r'#.*re-exports',
549 r'#.*re-exports',
551 ),
550 ),
552 ],
551 ],
553 # warnings
552 # warnings
554 [],
553 [],
555 ]
554 ]
556
555
557 # filters to convert normal *.py files
556 # filters to convert normal *.py files
558 pyfilters = [] + commonpyfilters
557 pyfilters = [] + commonpyfilters
559
558
560 # non-filter patterns
559 # non-filter patterns
561 pynfpats = [
560 pynfpats = [
562 [
561 [
563 (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
562 (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
564 (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"),
563 (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"),
565 (
564 (
566 r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]',
565 r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]',
567 "use pycompat.isdarwin",
566 "use pycompat.isdarwin",
568 ),
567 ),
569 ],
568 ],
570 # warnings
569 # warnings
571 [],
570 [],
572 ]
571 ]
573
572
574 # filters to convert *.py for embedded ones in test script
573 # filters to convert *.py for embedded ones in test script
575 embeddedpyfilters = [] + commonpyfilters
574 embeddedpyfilters = [] + commonpyfilters
576
575
577 # extension non-filter patterns
576 # extension non-filter patterns
578 pyextnfpats = [
577 pyextnfpats = [
579 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
578 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
580 # warnings
579 # warnings
581 [],
580 [],
582 ]
581 ]
583
582
584 txtfilters = []
583 txtfilters = []
585
584
586 txtpats = [
585 txtpats = [
587 [
586 [
588 (r'\s$', 'trailing whitespace'),
587 (r'\s$', 'trailing whitespace'),
589 ('.. note::[ \n][^\n]', 'add two newlines after note::'),
588 ('.. note::[ \n][^\n]', 'add two newlines after note::'),
590 ],
589 ],
591 [],
590 [],
592 ]
591 ]
593
592
594 cpats = [
593 cpats = [
595 [
594 [
596 (r'//', "don't use //-style comments"),
595 (r'//', "don't use //-style comments"),
597 (r'\S\t', "don't use tabs except for indent"),
596 (r'\S\t', "don't use tabs except for indent"),
598 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
597 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
599 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
598 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
600 (r'return\(', "return is not a function"),
599 (r'return\(', "return is not a function"),
601 (r' ;', "no space before ;"),
600 (r' ;', "no space before ;"),
602 (r'[^;] \)', "no space before )"),
601 (r'[^;] \)', "no space before )"),
603 (r'[)][{]', "space between ) and {"),
602 (r'[)][{]', "space between ) and {"),
604 (r'\w+\* \w+', "use int *foo, not int* foo"),
603 (r'\w+\* \w+', "use int *foo, not int* foo"),
605 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
604 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
606 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
605 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
607 (r'\w,\w', "missing whitespace after ,"),
606 (r'\w,\w', "missing whitespace after ,"),
608 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
607 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
609 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
608 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
610 (r'^#\s+\w', "use #foo, not # foo"),
609 (r'^#\s+\w', "use #foo, not # foo"),
611 (r'[^\n]\Z', "no trailing newline"),
610 (r'[^\n]\Z', "no trailing newline"),
612 (r'^\s*#import\b', "use only #include in standard C code"),
611 (r'^\s*#import\b', "use only #include in standard C code"),
613 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
612 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
614 (r'strcat\(', "don't use strcat"),
613 (r'strcat\(', "don't use strcat"),
615 # rules depending on implementation of repquote()
614 # rules depending on implementation of repquote()
616 ],
615 ],
617 # warnings
616 # warnings
618 [
617 [
619 # rules depending on implementation of repquote()
618 # rules depending on implementation of repquote()
620 ],
619 ],
621 ]
620 ]
622
621
623 cfilters = [
622 cfilters = [
624 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
623 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
625 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
624 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
626 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
625 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
627 (r'(\()([^)]+\))', repcallspaces),
626 (r'(\()([^)]+\))', repcallspaces),
628 ]
627 ]
629
628
630 inutilpats = [
629 inutilpats = [
631 [
630 [
632 (r'\bui\.', "don't use ui in util"),
631 (r'\bui\.', "don't use ui in util"),
633 ],
632 ],
634 # warnings
633 # warnings
635 [],
634 [],
636 ]
635 ]
637
636
638 inrevlogpats = [
637 inrevlogpats = [
639 [
638 [
640 (r'\brepo\.', "don't use repo in revlog"),
639 (r'\brepo\.', "don't use repo in revlog"),
641 ],
640 ],
642 # warnings
641 # warnings
643 [],
642 [],
644 ]
643 ]
645
644
646 webtemplatefilters = []
645 webtemplatefilters = []
647
646
648 webtemplatepats = [
647 webtemplatepats = [
649 [],
648 [],
650 [
649 [
651 (
650 (
652 r'{desc(\|(?!websub|firstline)[^\|]*)+}',
651 r'{desc(\|(?!websub|firstline)[^\|]*)+}',
653 'follow desc keyword with either firstline or websub',
652 'follow desc keyword with either firstline or websub',
654 ),
653 ),
655 ],
654 ],
656 ]
655 ]
657
656
658 allfilesfilters = []
657 allfilesfilters = []
659
658
660 allfilespats = [
659 allfilespats = [
661 [
660 [
662 (
661 (
663 r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
662 r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
664 'use mercurial-scm.org domain URL',
663 'use mercurial-scm.org domain URL',
665 ),
664 ),
666 (
665 (
667 r'mercurial@selenic\.com',
666 r'mercurial@selenic\.com',
668 'use mercurial-scm.org domain for mercurial ML address',
667 'use mercurial-scm.org domain for mercurial ML address',
669 ),
668 ),
670 (
669 (
671 r'mercurial-devel@selenic\.com',
670 r'mercurial-devel@selenic\.com',
672 'use mercurial-scm.org domain for mercurial-devel ML address',
671 'use mercurial-scm.org domain for mercurial-devel ML address',
673 ),
672 ),
674 ],
673 ],
675 # warnings
674 # warnings
676 [],
675 [],
677 ]
676 ]
678
677
679 py3pats = [
678 py3pats = [
680 [
679 [
681 (
680 (
682 r'os\.environ',
681 r'os\.environ',
683 "use encoding.environ instead (py3)",
682 "use encoding.environ instead (py3)",
684 r'#.*re-exports',
683 r'#.*re-exports',
685 ),
684 ),
686 (r'os\.name', "use pycompat.osname instead (py3)"),
685 (r'os\.name', "use pycompat.osname instead (py3)"),
687 (r'os\.getcwd', "use encoding.getcwd instead (py3)", r'#.*re-exports'),
686 (r'os\.getcwd', "use encoding.getcwd instead (py3)", r'#.*re-exports'),
688 (r'os\.sep', "use pycompat.ossep instead (py3)"),
687 (r'os\.sep', "use pycompat.ossep instead (py3)"),
689 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
688 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
690 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
689 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
691 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
690 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
692 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
691 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
693 (r'os\.getenv', "use encoding.environ.get instead"),
692 (r'os\.getenv', "use encoding.environ.get instead"),
694 (r'os\.setenv', "modifying the environ dict is not preferred"),
693 (r'os\.setenv', "modifying the environ dict is not preferred"),
695 (r'(?<!pycompat\.)xrange', "use pycompat.xrange instead (py3)"),
694 (r'(?<!pycompat\.)xrange', "use pycompat.xrange instead (py3)"),
696 ],
695 ],
697 # warnings
696 # warnings
698 [],
697 [],
699 ]
698 ]
700
699
701 checks = [
700 checks = [
702 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
701 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
703 ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats),
702 ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats),
704 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
703 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
705 (
704 (
706 'python 3',
705 'python 3',
707 r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
706 r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
708 '',
707 '',
709 pyfilters,
708 pyfilters,
710 py3pats,
709 py3pats,
711 ),
710 ),
712 (
711 (
713 'core files',
712 'core files',
714 r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
713 r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
715 '',
714 '',
716 pyfilters,
715 pyfilters,
717 core_py_pats,
716 core_py_pats,
718 ),
717 ),
719 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
718 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
720 ('c', r'.*\.[ch]$', '', cfilters, cpats),
719 ('c', r'.*\.[ch]$', '', cfilters, cpats),
721 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
720 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
722 (
721 (
723 'layering violation repo in revlog',
722 'layering violation repo in revlog',
724 r'mercurial/revlog\.py',
723 r'mercurial/revlog\.py',
725 '',
724 '',
726 pyfilters,
725 pyfilters,
727 inrevlogpats,
726 inrevlogpats,
728 ),
727 ),
729 (
728 (
730 'layering violation ui in util',
729 'layering violation ui in util',
731 r'mercurial/util\.py',
730 r'mercurial/util\.py',
732 '',
731 '',
733 pyfilters,
732 pyfilters,
734 inutilpats,
733 inutilpats,
735 ),
734 ),
736 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
735 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
737 (
736 (
738 'web template',
737 'web template',
739 r'mercurial/templates/.*\.tmpl',
738 r'mercurial/templates/.*\.tmpl',
740 '',
739 '',
741 webtemplatefilters,
740 webtemplatefilters,
742 webtemplatepats,
741 webtemplatepats,
743 ),
742 ),
744 ('all except for .po', r'.*(?<!\.po)$', '', allfilesfilters, allfilespats),
743 ('all except for .po', r'.*(?<!\.po)$', '', allfilesfilters, allfilespats),
745 ]
744 ]
746
745
747 # (desc,
746 # (desc,
748 # func to pick up embedded code fragments,
747 # func to pick up embedded code fragments,
749 # list of patterns to convert target files
748 # list of patterns to convert target files
750 # list of patterns to detect errors/warnings)
749 # list of patterns to detect errors/warnings)
751 embeddedchecks = [
750 embeddedchecks = [
752 (
751 (
753 'embedded python',
752 'embedded python',
754 testparseutil.pyembedded,
753 testparseutil.pyembedded,
755 embeddedpyfilters,
754 embeddedpyfilters,
756 embeddedpypats,
755 embeddedpypats,
757 )
756 )
758 ]
757 ]
759
758
760
759
761 def _preparepats():
760 def _preparepats():
762 def preparefailandwarn(failandwarn):
761 def preparefailandwarn(failandwarn):
763 for pats in failandwarn:
762 for pats in failandwarn:
764 for i, pseq in enumerate(pats):
763 for i, pseq in enumerate(pats):
765 # fix-up regexes for multi-line searches
764 # fix-up regexes for multi-line searches
766 p = pseq[0]
765 p = pseq[0]
767 # \s doesn't match \n (done in two steps)
766 # \s doesn't match \n (done in two steps)
768 # first, we replace \s that appears in a set already
767 # first, we replace \s that appears in a set already
769 p = re.sub(r'\[\\s', r'[ \\t', p)
768 p = re.sub(r'\[\\s', r'[ \\t', p)
770 # now we replace other \s instances.
769 # now we replace other \s instances.
771 p = re.sub(r'(?<!(\\|\[))\\s', r'[ \\t]', p)
770 p = re.sub(r'(?<!(\\|\[))\\s', r'[ \\t]', p)
772 # [^...] doesn't match newline
771 # [^...] doesn't match newline
773 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
772 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
774
773
775 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
774 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
776
775
777 def preparefilters(filters):
776 def preparefilters(filters):
778 for i, flt in enumerate(filters):
777 for i, flt in enumerate(filters):
779 filters[i] = re.compile(flt[0]), flt[1]
778 filters[i] = re.compile(flt[0]), flt[1]
780
779
781 for cs in (checks, embeddedchecks):
780 for cs in (checks, embeddedchecks):
782 for c in cs:
781 for c in cs:
783 failandwarn = c[-1]
782 failandwarn = c[-1]
784 preparefailandwarn(failandwarn)
783 preparefailandwarn(failandwarn)
785
784
786 filters = c[-2]
785 filters = c[-2]
787 preparefilters(filters)
786 preparefilters(filters)
788
787
789
788
790 class norepeatlogger(object):
789 class norepeatlogger(object):
791 def __init__(self):
790 def __init__(self):
792 self._lastseen = None
791 self._lastseen = None
793
792
794 def log(self, fname, lineno, line, msg, blame):
793 def log(self, fname, lineno, line, msg, blame):
795 """print error related a to given line of a given file.
794 """print error related a to given line of a given file.
796
795
797 The faulty line will also be printed but only once in the case
796 The faulty line will also be printed but only once in the case
798 of multiple errors.
797 of multiple errors.
799
798
800 :fname: filename
799 :fname: filename
801 :lineno: line number
800 :lineno: line number
802 :line: actual content of the line
801 :line: actual content of the line
803 :msg: error message
802 :msg: error message
804 """
803 """
805 msgid = fname, lineno, line
804 msgid = fname, lineno, line
806 if msgid != self._lastseen:
805 if msgid != self._lastseen:
807 if blame:
806 if blame:
808 print("%s:%d (%s):" % (fname, lineno, blame))
807 print("%s:%d (%s):" % (fname, lineno, blame))
809 else:
808 else:
810 print("%s:%d:" % (fname, lineno))
809 print("%s:%d:" % (fname, lineno))
811 print(" > %s" % line)
810 print(" > %s" % line)
812 self._lastseen = msgid
811 self._lastseen = msgid
813 print(" " + msg)
812 print(" " + msg)
814
813
815
814
816 _defaultlogger = norepeatlogger()
815 _defaultlogger = norepeatlogger()
817
816
818
817
819 def getblame(f):
818 def getblame(f):
820 lines = []
819 lines = []
821 for l in os.popen('hg annotate -un %s' % f):
820 for l in os.popen('hg annotate -un %s' % f):
822 start, line = l.split(':', 1)
821 start, line = l.split(':', 1)
823 user, rev = start.split()
822 user, rev = start.split()
824 lines.append((line[1:-1], user, rev))
823 lines.append((line[1:-1], user, rev))
825 return lines
824 return lines
826
825
827
826
828 def checkfile(
827 def checkfile(
829 f,
828 f,
830 logfunc=_defaultlogger.log,
829 logfunc=_defaultlogger.log,
831 maxerr=None,
830 maxerr=None,
832 warnings=False,
831 warnings=False,
833 blame=False,
832 blame=False,
834 debug=False,
833 debug=False,
835 lineno=True,
834 lineno=True,
836 ):
835 ):
837 """checks style and portability of a given file
836 """checks style and portability of a given file
838
837
839 :f: filepath
838 :f: filepath
840 :logfunc: function used to report error
839 :logfunc: function used to report error
841 logfunc(filename, linenumber, linecontent, errormessage)
840 logfunc(filename, linenumber, linecontent, errormessage)
842 :maxerr: number of error to display before aborting.
841 :maxerr: number of error to display before aborting.
843 Set to false (default) to report all errors
842 Set to false (default) to report all errors
844
843
845 return True if no error is found, False otherwise.
844 return True if no error is found, False otherwise.
846 """
845 """
847 result = True
846 result = True
848
847
849 try:
848 try:
850 with opentext(f) as fp:
849 with opentext(f) as fp:
851 try:
850 try:
852 pre = fp.read()
851 pre = fp.read()
853 except UnicodeDecodeError as e:
852 except UnicodeDecodeError as e:
854 print("%s while reading %s" % (e, f))
853 print("%s while reading %s" % (e, f))
855 return result
854 return result
856 except IOError as e:
855 except IOError as e:
857 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
856 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
858 return result
857 return result
859
858
860 # context information shared while single checkfile() invocation
859 # context information shared while single checkfile() invocation
861 context = {'blamecache': None}
860 context = {'blamecache': None}
862
861
863 for name, match, magic, filters, pats in checks:
862 for name, match, magic, filters, pats in checks:
864 if debug:
863 if debug:
865 print(name, f)
864 print(name, f)
866 if not (re.match(match, f) or (magic and re.search(magic, pre))):
865 if not (re.match(match, f) or (magic and re.search(magic, pre))):
867 if debug:
866 if debug:
868 print(
867 print(
869 "Skipping %s for %s it doesn't match %s" % (name, match, f)
868 "Skipping %s for %s it doesn't match %s" % (name, match, f)
870 )
869 )
871 continue
870 continue
872 if "no-" "check-code" in pre:
871 if "no-" "check-code" in pre:
873 # If you're looking at this line, it's because a file has:
872 # If you're looking at this line, it's because a file has:
874 # no- check- code
873 # no- check- code
875 # but the reason to output skipping is to make life for
874 # but the reason to output skipping is to make life for
876 # tests easier. So, instead of writing it with a normal
875 # tests easier. So, instead of writing it with a normal
877 # spelling, we write it with the expected spelling from
876 # spelling, we write it with the expected spelling from
878 # tests/test-check-code.t
877 # tests/test-check-code.t
879 print("Skipping %s it has no-che?k-code (glob)" % f)
878 print("Skipping %s it has no-che?k-code (glob)" % f)
880 return "Skip" # skip checking this file
879 return "Skip" # skip checking this file
881
880
882 fc = _checkfiledata(
881 fc = _checkfiledata(
883 name,
882 name,
884 f,
883 f,
885 pre,
884 pre,
886 filters,
885 filters,
887 pats,
886 pats,
888 context,
887 context,
889 logfunc,
888 logfunc,
890 maxerr,
889 maxerr,
891 warnings,
890 warnings,
892 blame,
891 blame,
893 debug,
892 debug,
894 lineno,
893 lineno,
895 )
894 )
896 if fc:
895 if fc:
897 result = False
896 result = False
898
897
899 if f.endswith('.t') and "no-" "check-code" not in pre:
898 if f.endswith('.t') and "no-" "check-code" not in pre:
900 if debug:
899 if debug:
901 print("Checking embedded code in %s" % f)
900 print("Checking embedded code in %s" % f)
902
901
903 prelines = pre.splitlines()
902 prelines = pre.splitlines()
904 embeddederros = []
903 embeddederros = []
905 for name, embedded, filters, pats in embeddedchecks:
904 for name, embedded, filters, pats in embeddedchecks:
906 # "reset curmax at each repetition" treats maxerr as "max
905 # "reset curmax at each repetition" treats maxerr as "max
907 # nubmer of errors in an actual file per entry of
906 # nubmer of errors in an actual file per entry of
908 # (embedded)checks"
907 # (embedded)checks"
909 curmaxerr = maxerr
908 curmaxerr = maxerr
910
909
911 for found in embedded(f, prelines, embeddederros):
910 for found in embedded(f, prelines, embeddederros):
912 filename, starts, ends, code = found
911 filename, starts, ends, code = found
913 fc = _checkfiledata(
912 fc = _checkfiledata(
914 name,
913 name,
915 f,
914 f,
916 code,
915 code,
917 filters,
916 filters,
918 pats,
917 pats,
919 context,
918 context,
920 logfunc,
919 logfunc,
921 curmaxerr,
920 curmaxerr,
922 warnings,
921 warnings,
923 blame,
922 blame,
924 debug,
923 debug,
925 lineno,
924 lineno,
926 offset=starts - 1,
925 offset=starts - 1,
927 )
926 )
928 if fc:
927 if fc:
929 result = False
928 result = False
930 if curmaxerr:
929 if curmaxerr:
931 if fc >= curmaxerr:
930 if fc >= curmaxerr:
932 break
931 break
933 curmaxerr -= fc
932 curmaxerr -= fc
934
933
935 return result
934 return result
936
935
937
936
938 def _checkfiledata(
937 def _checkfiledata(
939 name,
938 name,
940 f,
939 f,
941 filedata,
940 filedata,
942 filters,
941 filters,
943 pats,
942 pats,
944 context,
943 context,
945 logfunc,
944 logfunc,
946 maxerr,
945 maxerr,
947 warnings,
946 warnings,
948 blame,
947 blame,
949 debug,
948 debug,
950 lineno,
949 lineno,
951 offset=None,
950 offset=None,
952 ):
951 ):
953 """Execute actual error check for file data
952 """Execute actual error check for file data
954
953
955 :name: of the checking category
954 :name: of the checking category
956 :f: filepath
955 :f: filepath
957 :filedata: content of a file
956 :filedata: content of a file
958 :filters: to be applied before checking
957 :filters: to be applied before checking
959 :pats: to detect errors
958 :pats: to detect errors
960 :context: a dict of information shared while single checkfile() invocation
959 :context: a dict of information shared while single checkfile() invocation
961 Valid keys: 'blamecache'.
960 Valid keys: 'blamecache'.
962 :logfunc: function used to report error
961 :logfunc: function used to report error
963 logfunc(filename, linenumber, linecontent, errormessage)
962 logfunc(filename, linenumber, linecontent, errormessage)
964 :maxerr: number of error to display before aborting, or False to
963 :maxerr: number of error to display before aborting, or False to
965 report all errors
964 report all errors
966 :warnings: whether warning level checks should be applied
965 :warnings: whether warning level checks should be applied
967 :blame: whether blame information should be displayed at error reporting
966 :blame: whether blame information should be displayed at error reporting
968 :debug: whether debug information should be displayed
967 :debug: whether debug information should be displayed
969 :lineno: whether lineno should be displayed at error reporting
968 :lineno: whether lineno should be displayed at error reporting
970 :offset: line number offset of 'filedata' in 'f' for checking
969 :offset: line number offset of 'filedata' in 'f' for checking
971 an embedded code fragment, or None (offset=0 is different
970 an embedded code fragment, or None (offset=0 is different
972 from offset=None)
971 from offset=None)
973
972
974 returns number of detected errors.
973 returns number of detected errors.
975 """
974 """
976 blamecache = context['blamecache']
975 blamecache = context['blamecache']
977 if offset is None:
976 if offset is None:
978 lineoffset = 0
977 lineoffset = 0
979 else:
978 else:
980 lineoffset = offset
979 lineoffset = offset
981
980
982 fc = 0
981 fc = 0
983 pre = post = filedata
982 pre = post = filedata
984
983
985 if True: # TODO: get rid of this redundant 'if' block
984 if True: # TODO: get rid of this redundant 'if' block
986 for p, r in filters:
985 for p, r in filters:
987 post = re.sub(p, r, post)
986 post = re.sub(p, r, post)
988 nerrs = len(pats[0]) # nerr elements are errors
987 nerrs = len(pats[0]) # nerr elements are errors
989 if warnings:
988 if warnings:
990 pats = pats[0] + pats[1]
989 pats = pats[0] + pats[1]
991 else:
990 else:
992 pats = pats[0]
991 pats = pats[0]
993 # print post # uncomment to show filtered version
992 # print post # uncomment to show filtered version
994
993
995 if debug:
994 if debug:
996 print("Checking %s for %s" % (name, f))
995 print("Checking %s for %s" % (name, f))
997
996
998 prelines = None
997 prelines = None
999 errors = []
998 errors = []
1000 for i, pat in enumerate(pats):
999 for i, pat in enumerate(pats):
1001 if len(pat) == 3:
1000 if len(pat) == 3:
1002 p, msg, ignore = pat
1001 p, msg, ignore = pat
1003 else:
1002 else:
1004 p, msg = pat
1003 p, msg = pat
1005 ignore = None
1004 ignore = None
1006 if i >= nerrs:
1005 if i >= nerrs:
1007 msg = "warning: " + msg
1006 msg = "warning: " + msg
1008
1007
1009 pos = 0
1008 pos = 0
1010 n = 0
1009 n = 0
1011 for m in p.finditer(post):
1010 for m in p.finditer(post):
1012 if prelines is None:
1011 if prelines is None:
1013 prelines = pre.splitlines()
1012 prelines = pre.splitlines()
1014 postlines = post.splitlines(True)
1013 postlines = post.splitlines(True)
1015
1014
1016 start = m.start()
1015 start = m.start()
1017 while n < len(postlines):
1016 while n < len(postlines):
1018 step = len(postlines[n])
1017 step = len(postlines[n])
1019 if pos + step > start:
1018 if pos + step > start:
1020 break
1019 break
1021 pos += step
1020 pos += step
1022 n += 1
1021 n += 1
1023 l = prelines[n]
1022 l = prelines[n]
1024
1023
1025 if ignore and re.search(ignore, l, re.MULTILINE):
1024 if ignore and re.search(ignore, l, re.MULTILINE):
1026 if debug:
1025 if debug:
1027 print(
1026 print(
1028 "Skipping %s for %s:%s (ignore pattern)"
1027 "Skipping %s for %s:%s (ignore pattern)"
1029 % (name, f, (n + lineoffset))
1028 % (name, f, (n + lineoffset))
1030 )
1029 )
1031 continue
1030 continue
1032 bd = ""
1031 bd = ""
1033 if blame:
1032 if blame:
1034 bd = 'working directory'
1033 bd = 'working directory'
1035 if blamecache is None:
1034 if blamecache is None:
1036 blamecache = getblame(f)
1035 blamecache = getblame(f)
1037 context['blamecache'] = blamecache
1036 context['blamecache'] = blamecache
1038 if (n + lineoffset) < len(blamecache):
1037 if (n + lineoffset) < len(blamecache):
1039 bl, bu, br = blamecache[(n + lineoffset)]
1038 bl, bu, br = blamecache[(n + lineoffset)]
1040 if offset is None and bl == l:
1039 if offset is None and bl == l:
1041 bd = '%s@%s' % (bu, br)
1040 bd = '%s@%s' % (bu, br)
1042 elif offset is not None and bl.endswith(l):
1041 elif offset is not None and bl.endswith(l):
1043 # "offset is not None" means "checking
1042 # "offset is not None" means "checking
1044 # embedded code fragment". In this case,
1043 # embedded code fragment". In this case,
1045 # "l" does not have information about the
1044 # "l" does not have information about the
1046 # beginning of an *original* line in the
1045 # beginning of an *original* line in the
1047 # file (e.g. ' > ').
1046 # file (e.g. ' > ').
1048 # Therefore, use "str.endswith()", and
1047 # Therefore, use "str.endswith()", and
1049 # show "maybe" for a little loose
1048 # show "maybe" for a little loose
1050 # examination.
1049 # examination.
1051 bd = '%s@%s, maybe' % (bu, br)
1050 bd = '%s@%s, maybe' % (bu, br)
1052
1051
1053 errors.append((f, lineno and (n + lineoffset + 1), l, msg, bd))
1052 errors.append((f, lineno and (n + lineoffset + 1), l, msg, bd))
1054
1053
1055 errors.sort()
1054 errors.sort()
1056 for e in errors:
1055 for e in errors:
1057 logfunc(*e)
1056 logfunc(*e)
1058 fc += 1
1057 fc += 1
1059 if maxerr and fc >= maxerr:
1058 if maxerr and fc >= maxerr:
1060 print(" (too many errors, giving up)")
1059 print(" (too many errors, giving up)")
1061 break
1060 break
1062
1061
1063 return fc
1062 return fc
1064
1063
1065
1064
1066 def main():
1065 def main():
1067 parser = optparse.OptionParser("%prog [options] [files | -]")
1066 parser = optparse.OptionParser("%prog [options] [files | -]")
1068 parser.add_option(
1067 parser.add_option(
1069 "-w",
1068 "-w",
1070 "--warnings",
1069 "--warnings",
1071 action="store_true",
1070 action="store_true",
1072 help="include warning-level checks",
1071 help="include warning-level checks",
1073 )
1072 )
1074 parser.add_option(
1073 parser.add_option(
1075 "-p", "--per-file", type="int", help="max warnings per file"
1074 "-p", "--per-file", type="int", help="max warnings per file"
1076 )
1075 )
1077 parser.add_option(
1076 parser.add_option(
1078 "-b",
1077 "-b",
1079 "--blame",
1078 "--blame",
1080 action="store_true",
1079 action="store_true",
1081 help="use annotate to generate blame info",
1080 help="use annotate to generate blame info",
1082 )
1081 )
1083 parser.add_option(
1082 parser.add_option(
1084 "", "--debug", action="store_true", help="show debug information"
1083 "", "--debug", action="store_true", help="show debug information"
1085 )
1084 )
1086 parser.add_option(
1085 parser.add_option(
1087 "",
1086 "",
1088 "--nolineno",
1087 "--nolineno",
1089 action="store_false",
1088 action="store_false",
1090 dest='lineno',
1089 dest='lineno',
1091 help="don't show line numbers",
1090 help="don't show line numbers",
1092 )
1091 )
1093
1092
1094 parser.set_defaults(
1093 parser.set_defaults(
1095 per_file=15, warnings=False, blame=False, debug=False, lineno=True
1094 per_file=15, warnings=False, blame=False, debug=False, lineno=True
1096 )
1095 )
1097 (options, args) = parser.parse_args()
1096 (options, args) = parser.parse_args()
1098
1097
1099 if len(args) == 0:
1098 if len(args) == 0:
1100 check = glob.glob("*")
1099 check = glob.glob("*")
1101 elif args == ['-']:
1100 elif args == ['-']:
1102 # read file list from stdin
1101 # read file list from stdin
1103 check = sys.stdin.read().splitlines()
1102 check = sys.stdin.read().splitlines()
1104 else:
1103 else:
1105 check = args
1104 check = args
1106
1105
1107 _preparepats()
1106 _preparepats()
1108
1107
1109 ret = 0
1108 ret = 0
1110 for f in check:
1109 for f in check:
1111 if not checkfile(
1110 if not checkfile(
1112 f,
1111 f,
1113 maxerr=options.per_file,
1112 maxerr=options.per_file,
1114 warnings=options.warnings,
1113 warnings=options.warnings,
1115 blame=options.blame,
1114 blame=options.blame,
1116 debug=options.debug,
1115 debug=options.debug,
1117 lineno=options.lineno,
1116 lineno=options.lineno,
1118 ):
1117 ):
1119 ret = 1
1118 ret = 1
1120 return ret
1119 return ret
1121
1120
1122
1121
1123 if __name__ == "__main__":
1122 if __name__ == "__main__":
1124 sys.exit(main())
1123 sys.exit(main())
@@ -1,115 +1,114 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 #
2 #
3 # Copyright 2014 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2014 Olivia Mackall <olivia@selenic.com>
4 #
4 #
5 # A tool/hook to run basic sanity checks on commits/patches for
5 # A tool/hook to run basic sanity checks on commits/patches for
6 # submission to Mercurial. Install by adding the following to your
6 # submission to Mercurial. Install by adding the following to your
7 # .hg/hgrc:
7 # .hg/hgrc:
8 #
8 #
9 # [hooks]
9 # [hooks]
10 # pretxncommit = contrib/check-commit
10 # pretxncommit = contrib/check-commit
11 #
11 #
12 # The hook can be temporarily bypassed with:
12 # The hook can be temporarily bypassed with:
13 #
13 #
14 # $ BYPASS= hg commit
14 # $ BYPASS= hg commit
15 #
15 #
16 # See also: https://mercurial-scm.org/wiki/ContributingChanges
16 # See also: https://mercurial-scm.org/wiki/ContributingChanges
17
17
18 from __future__ import absolute_import, print_function
19
18
20 import os
19 import os
21 import re
20 import re
22 import sys
21 import sys
23
22
24 commitheader = r"^(?:# [^\n]*\n)*"
23 commitheader = r"^(?:# [^\n]*\n)*"
25 afterheader = commitheader + r"(?!#)"
24 afterheader = commitheader + r"(?!#)"
26 beforepatch = afterheader + r"(?!\n(?!@@))"
25 beforepatch = afterheader + r"(?!\n(?!@@))"
27
26
28 errors = [
27 errors = [
29 (beforepatch + r".*[(]bc[)]", "(BC) needs to be uppercase"),
28 (beforepatch + r".*[(]bc[)]", "(BC) needs to be uppercase"),
30 (
29 (
31 beforepatch + r".*[(]issue \d\d\d",
30 beforepatch + r".*[(]issue \d\d\d",
32 "no space allowed between issue and number",
31 "no space allowed between issue and number",
33 ),
32 ),
34 (beforepatch + r".*[(]bug(\d|\s)", "use (issueDDDD) instead of bug"),
33 (beforepatch + r".*[(]bug(\d|\s)", "use (issueDDDD) instead of bug"),
35 (commitheader + r"# User [^@\n]+\n", "username is not an email address"),
34 (commitheader + r"# User [^@\n]+\n", "username is not an email address"),
36 (
35 (
37 commitheader + r"(?!merge with )[^#]\S+[^:] ",
36 commitheader + r"(?!merge with )[^#]\S+[^:] ",
38 "summary line doesn't start with 'topic: '",
37 "summary line doesn't start with 'topic: '",
39 ),
38 ),
40 (afterheader + r"[A-Z][a-z]\S+", "don't capitalize summary lines"),
39 (afterheader + r"[A-Z][a-z]\S+", "don't capitalize summary lines"),
41 (afterheader + r"^\S+: *[A-Z][a-z]\S+", "don't capitalize summary lines"),
40 (afterheader + r"^\S+: *[A-Z][a-z]\S+", "don't capitalize summary lines"),
42 (
41 (
43 afterheader + r"\S*[^A-Za-z0-9-_]\S*: ",
42 afterheader + r"\S*[^A-Za-z0-9-_]\S*: ",
44 "summary keyword should be most user-relevant one-word command or topic",
43 "summary keyword should be most user-relevant one-word command or topic",
45 ),
44 ),
46 (afterheader + r".*\.\s*\n", "don't add trailing period on summary line"),
45 (afterheader + r".*\.\s*\n", "don't add trailing period on summary line"),
47 (afterheader + r".{79,}", "summary line too long (limit is 78)"),
46 (afterheader + r".{79,}", "summary line too long (limit is 78)"),
48 ]
47 ]
49
48
50 word = re.compile(r'\S')
49 word = re.compile(r'\S')
51
50
52
51
53 def nonempty(first, second):
52 def nonempty(first, second):
54 if word.search(first):
53 if word.search(first):
55 return first
54 return first
56 return second
55 return second
57
56
58
57
59 def checkcommit(commit, node=None):
58 def checkcommit(commit, node=None):
60 exitcode = 0
59 exitcode = 0
61 printed = node is None
60 printed = node is None
62 hits = []
61 hits = []
63 signtag = (
62 signtag = (
64 afterheader + r'Added (tag [^ ]+|signature) for changeset [a-f0-9]{12}'
63 afterheader + r'Added (tag [^ ]+|signature) for changeset [a-f0-9]{12}'
65 )
64 )
66 if re.search(signtag, commit):
65 if re.search(signtag, commit):
67 return 0
66 return 0
68 for exp, msg in errors:
67 for exp, msg in errors:
69 for m in re.finditer(exp, commit):
68 for m in re.finditer(exp, commit):
70 end = m.end()
69 end = m.end()
71 trailing = re.search(r'(\\n)+$', exp)
70 trailing = re.search(r'(\\n)+$', exp)
72 if trailing:
71 if trailing:
73 end -= len(trailing.group()) / 2
72 end -= len(trailing.group()) / 2
74 hits.append((end, exp, msg))
73 hits.append((end, exp, msg))
75 if hits:
74 if hits:
76 hits.sort()
75 hits.sort()
77 pos = 0
76 pos = 0
78 last = ''
77 last = ''
79 for n, l in enumerate(commit.splitlines(True)):
78 for n, l in enumerate(commit.splitlines(True)):
80 pos += len(l)
79 pos += len(l)
81 while len(hits):
80 while len(hits):
82 end, exp, msg = hits[0]
81 end, exp, msg = hits[0]
83 if pos < end:
82 if pos < end:
84 break
83 break
85 if not printed:
84 if not printed:
86 printed = True
85 printed = True
87 print("node: %s" % node)
86 print("node: %s" % node)
88 print("%d: %s" % (n, msg))
87 print("%d: %s" % (n, msg))
89 print(" %s" % nonempty(l, last)[:-1])
88 print(" %s" % nonempty(l, last)[:-1])
90 if "BYPASS" not in os.environ:
89 if "BYPASS" not in os.environ:
91 exitcode = 1
90 exitcode = 1
92 del hits[0]
91 del hits[0]
93 last = nonempty(l, last)
92 last = nonempty(l, last)
94
93
95 return exitcode
94 return exitcode
96
95
97
96
98 def readcommit(node):
97 def readcommit(node):
99 return os.popen("hg export %s" % node).read()
98 return os.popen("hg export %s" % node).read()
100
99
101
100
102 if __name__ == "__main__":
101 if __name__ == "__main__":
103 exitcode = 0
102 exitcode = 0
104 node = os.environ.get("HG_NODE")
103 node = os.environ.get("HG_NODE")
105
104
106 if node:
105 if node:
107 commit = readcommit(node)
106 commit = readcommit(node)
108 exitcode = checkcommit(commit)
107 exitcode = checkcommit(commit)
109 elif sys.argv[1:]:
108 elif sys.argv[1:]:
110 for node in sys.argv[1:]:
109 for node in sys.argv[1:]:
111 exitcode |= checkcommit(readcommit(node), node)
110 exitcode |= checkcommit(readcommit(node), node)
112 else:
111 else:
113 commit = sys.stdin.read()
112 commit = sys.stdin.read()
114 exitcode = checkcommit(commit)
113 exitcode = checkcommit(commit)
115 sys.exit(exitcode)
114 sys.exit(exitcode)
@@ -1,191 +1,190 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 #
2 #
3 # check-config - a config flag documentation checker for Mercurial
3 # check-config - a config flag documentation checker for Mercurial
4 #
4 #
5 # Copyright 2015 Olivia Mackall <olivia@selenic.com>
5 # Copyright 2015 Olivia Mackall <olivia@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 from __future__ import absolute_import, print_function
11 import re
10 import re
12 import sys
11 import sys
13
12
14 foundopts = {}
13 foundopts = {}
15 documented = {}
14 documented = {}
16 allowinconsistent = set()
15 allowinconsistent = set()
17
16
18 configre = re.compile(
17 configre = re.compile(
19 br'''
18 br'''
20 # Function call
19 # Function call
21 ui\.config(?P<ctype>|int|bool|list)\(
20 ui\.config(?P<ctype>|int|bool|list)\(
22 # First argument.
21 # First argument.
23 ['"](?P<section>\S+)['"],\s*
22 ['"](?P<section>\S+)['"],\s*
24 # Second argument
23 # Second argument
25 ['"](?P<option>\S+)['"](,\s+
24 ['"](?P<option>\S+)['"](,\s+
26 (?:default=)?(?P<default>\S+?))?
25 (?:default=)?(?P<default>\S+?))?
27 \)''',
26 \)''',
28 re.VERBOSE | re.MULTILINE,
27 re.VERBOSE | re.MULTILINE,
29 )
28 )
30
29
31 configwithre = re.compile(
30 configwithre = re.compile(
32 br'''
31 br'''
33 ui\.config(?P<ctype>with)\(
32 ui\.config(?P<ctype>with)\(
34 # First argument is callback function. This doesn't parse robustly
33 # First argument is callback function. This doesn't parse robustly
35 # if it is e.g. a function call.
34 # if it is e.g. a function call.
36 [^,]+,\s*
35 [^,]+,\s*
37 ['"](?P<section>\S+)['"],\s*
36 ['"](?P<section>\S+)['"],\s*
38 ['"](?P<option>\S+)['"](,\s+
37 ['"](?P<option>\S+)['"](,\s+
39 (?:default=)?(?P<default>\S+?))?
38 (?:default=)?(?P<default>\S+?))?
40 \)''',
39 \)''',
41 re.VERBOSE | re.MULTILINE,
40 re.VERBOSE | re.MULTILINE,
42 )
41 )
43
42
44 configpartialre = br"""ui\.config"""
43 configpartialre = br"""ui\.config"""
45
44
46 ignorere = re.compile(
45 ignorere = re.compile(
47 br'''
46 br'''
48 \#\s(?P<reason>internal|experimental|deprecated|developer|inconsistent)\s
47 \#\s(?P<reason>internal|experimental|deprecated|developer|inconsistent)\s
49 config:\s(?P<config>\S+\.\S+)$
48 config:\s(?P<config>\S+\.\S+)$
50 ''',
49 ''',
51 re.VERBOSE | re.MULTILINE,
50 re.VERBOSE | re.MULTILINE,
52 )
51 )
53
52
54 if sys.version_info[0] > 2:
53 if sys.version_info[0] > 2:
55
54
56 def mkstr(b):
55 def mkstr(b):
57 if isinstance(b, str):
56 if isinstance(b, str):
58 return b
57 return b
59 return b.decode('utf8')
58 return b.decode('utf8')
60
59
61
60
62 else:
61 else:
63 mkstr = lambda x: x
62 mkstr = lambda x: x
64
63
65
64
66 def main(args):
65 def main(args):
67 for f in args:
66 for f in args:
68 sect = b''
67 sect = b''
69 prevname = b''
68 prevname = b''
70 confsect = b''
69 confsect = b''
71 carryover = b''
70 carryover = b''
72 linenum = 0
71 linenum = 0
73 for l in open(f, 'rb'):
72 for l in open(f, 'rb'):
74 linenum += 1
73 linenum += 1
75
74
76 # check topic-like bits
75 # check topic-like bits
77 m = re.match(br'\s*``(\S+)``', l)
76 m = re.match(br'\s*``(\S+)``', l)
78 if m:
77 if m:
79 prevname = m.group(1)
78 prevname = m.group(1)
80 if re.match(br'^\s*-+$', l):
79 if re.match(br'^\s*-+$', l):
81 sect = prevname
80 sect = prevname
82 prevname = b''
81 prevname = b''
83
82
84 if sect and prevname:
83 if sect and prevname:
85 name = sect + b'.' + prevname
84 name = sect + b'.' + prevname
86 documented[name] = 1
85 documented[name] = 1
87
86
88 # check docstring bits
87 # check docstring bits
89 m = re.match(br'^\s+\[(\S+)\]', l)
88 m = re.match(br'^\s+\[(\S+)\]', l)
90 if m:
89 if m:
91 confsect = m.group(1)
90 confsect = m.group(1)
92 continue
91 continue
93 m = re.match(br'^\s+(?:#\s*)?(\S+) = ', l)
92 m = re.match(br'^\s+(?:#\s*)?(\S+) = ', l)
94 if m:
93 if m:
95 name = confsect + b'.' + m.group(1)
94 name = confsect + b'.' + m.group(1)
96 documented[name] = 1
95 documented[name] = 1
97
96
98 # like the bugzilla extension
97 # like the bugzilla extension
99 m = re.match(br'^\s*(\S+\.\S+)$', l)
98 m = re.match(br'^\s*(\S+\.\S+)$', l)
100 if m:
99 if m:
101 documented[m.group(1)] = 1
100 documented[m.group(1)] = 1
102
101
103 # like convert
102 # like convert
104 m = re.match(br'^\s*:(\S+\.\S+):\s+', l)
103 m = re.match(br'^\s*:(\S+\.\S+):\s+', l)
105 if m:
104 if m:
106 documented[m.group(1)] = 1
105 documented[m.group(1)] = 1
107
106
108 # quoted in help or docstrings
107 # quoted in help or docstrings
109 m = re.match(br'.*?``(\S+\.\S+)``', l)
108 m = re.match(br'.*?``(\S+\.\S+)``', l)
110 if m:
109 if m:
111 documented[m.group(1)] = 1
110 documented[m.group(1)] = 1
112
111
113 # look for ignore markers
112 # look for ignore markers
114 m = ignorere.search(l)
113 m = ignorere.search(l)
115 if m:
114 if m:
116 if m.group('reason') == b'inconsistent':
115 if m.group('reason') == b'inconsistent':
117 allowinconsistent.add(m.group('config'))
116 allowinconsistent.add(m.group('config'))
118 else:
117 else:
119 documented[m.group('config')] = 1
118 documented[m.group('config')] = 1
120
119
121 # look for code-like bits
120 # look for code-like bits
122 line = carryover + l
121 line = carryover + l
123 m = configre.search(line) or configwithre.search(line)
122 m = configre.search(line) or configwithre.search(line)
124 if m:
123 if m:
125 ctype = m.group('ctype')
124 ctype = m.group('ctype')
126 if not ctype:
125 if not ctype:
127 ctype = 'str'
126 ctype = 'str'
128 name = m.group('section') + b"." + m.group('option')
127 name = m.group('section') + b"." + m.group('option')
129 default = m.group('default')
128 default = m.group('default')
130 if default in (
129 if default in (
131 None,
130 None,
132 b'False',
131 b'False',
133 b'None',
132 b'None',
134 b'0',
133 b'0',
135 b'[]',
134 b'[]',
136 b'""',
135 b'""',
137 b"''",
136 b"''",
138 ):
137 ):
139 default = b''
138 default = b''
140 if re.match(b'[a-z.]+$', default):
139 if re.match(b'[a-z.]+$', default):
141 default = b'<variable>'
140 default = b'<variable>'
142 if (
141 if (
143 name in foundopts
142 name in foundopts
144 and (ctype, default) != foundopts[name]
143 and (ctype, default) != foundopts[name]
145 and name not in allowinconsistent
144 and name not in allowinconsistent
146 ):
145 ):
147 print(mkstr(l.rstrip()))
146 print(mkstr(l.rstrip()))
148 fctype, fdefault = foundopts[name]
147 fctype, fdefault = foundopts[name]
149 print(
148 print(
150 "conflict on %s: %r != %r"
149 "conflict on %s: %r != %r"
151 % (
150 % (
152 mkstr(name),
151 mkstr(name),
153 (mkstr(ctype), mkstr(default)),
152 (mkstr(ctype), mkstr(default)),
154 (mkstr(fctype), mkstr(fdefault)),
153 (mkstr(fctype), mkstr(fdefault)),
155 )
154 )
156 )
155 )
157 print("at %s:%d:" % (mkstr(f), linenum))
156 print("at %s:%d:" % (mkstr(f), linenum))
158 foundopts[name] = (ctype, default)
157 foundopts[name] = (ctype, default)
159 carryover = b''
158 carryover = b''
160 else:
159 else:
161 m = re.search(configpartialre, line)
160 m = re.search(configpartialre, line)
162 if m:
161 if m:
163 carryover = line
162 carryover = line
164 else:
163 else:
165 carryover = b''
164 carryover = b''
166
165
167 for name in sorted(foundopts):
166 for name in sorted(foundopts):
168 if name not in documented:
167 if name not in documented:
169 if not (
168 if not (
170 name.startswith(b"devel.")
169 name.startswith(b"devel.")
171 or name.startswith(b"experimental.")
170 or name.startswith(b"experimental.")
172 or name.startswith(b"debug.")
171 or name.startswith(b"debug.")
173 ):
172 ):
174 ctype, default = foundopts[name]
173 ctype, default = foundopts[name]
175 if default:
174 if default:
176 if isinstance(default, bytes):
175 if isinstance(default, bytes):
177 default = mkstr(default)
176 default = mkstr(default)
178 default = ' [%s]' % default
177 default = ' [%s]' % default
179 elif isinstance(default, bytes):
178 elif isinstance(default, bytes):
180 default = mkstr(default)
179 default = mkstr(default)
181 print(
180 print(
182 "undocumented: %s (%s)%s"
181 "undocumented: %s (%s)%s"
183 % (mkstr(name), mkstr(ctype), default)
182 % (mkstr(name), mkstr(ctype), default)
184 )
183 )
185
184
186
185
187 if __name__ == "__main__":
186 if __name__ == "__main__":
188 if len(sys.argv) > 1:
187 if len(sys.argv) > 1:
189 sys.exit(main(sys.argv[1:]))
188 sys.exit(main(sys.argv[1:]))
190 else:
189 else:
191 sys.exit(main([l.rstrip() for l in sys.stdin]))
190 sys.exit(main([l.rstrip() for l in sys.stdin]))
@@ -1,93 +1,92 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 #
2 #
3 # check-py3-compat - check Python 3 compatibility of Mercurial files
3 # check-py3-compat - check Python 3 compatibility of Mercurial files
4 #
4 #
5 # Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com>
5 # Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 from __future__ import absolute_import, print_function
11
10
12 import ast
11 import ast
13 import importlib
12 import importlib
14 import os
13 import os
15 import sys
14 import sys
16 import traceback
15 import traceback
17 import warnings
16 import warnings
18
17
19
18
20 def check_compat_py3(f):
19 def check_compat_py3(f):
21 """Check Python 3 compatibility of a file with Python 3."""
20 """Check Python 3 compatibility of a file with Python 3."""
22 with open(f, 'rb') as fh:
21 with open(f, 'rb') as fh:
23 content = fh.read()
22 content = fh.read()
24
23
25 try:
24 try:
26 ast.parse(content, filename=f)
25 ast.parse(content, filename=f)
27 except SyntaxError as e:
26 except SyntaxError as e:
28 print('%s: invalid syntax: %s' % (f, e))
27 print('%s: invalid syntax: %s' % (f, e))
29 return
28 return
30
29
31 # Try to import the module.
30 # Try to import the module.
32 # For now we only support modules in packages because figuring out module
31 # For now we only support modules in packages because figuring out module
33 # paths for things not in a package can be confusing.
32 # paths for things not in a package can be confusing.
34 if f.startswith(
33 if f.startswith(
35 ('hgdemandimport/', 'hgext/', 'mercurial/')
34 ('hgdemandimport/', 'hgext/', 'mercurial/')
36 ) and not f.endswith('__init__.py'):
35 ) and not f.endswith('__init__.py'):
37 assert f.endswith('.py')
36 assert f.endswith('.py')
38 name = f.replace('/', '.')[:-3]
37 name = f.replace('/', '.')[:-3]
39 try:
38 try:
40 importlib.import_module(name)
39 importlib.import_module(name)
41 except Exception as e:
40 except Exception as e:
42 exc_type, exc_value, tb = sys.exc_info()
41 exc_type, exc_value, tb = sys.exc_info()
43 # We walk the stack and ignore frames from our custom importer,
42 # We walk the stack and ignore frames from our custom importer,
44 # import mechanisms, and stdlib modules. This kinda/sorta
43 # import mechanisms, and stdlib modules. This kinda/sorta
45 # emulates CPython behavior in import.c while also attempting
44 # emulates CPython behavior in import.c while also attempting
46 # to pin blame on a Mercurial file.
45 # to pin blame on a Mercurial file.
47 for frame in reversed(traceback.extract_tb(tb)):
46 for frame in reversed(traceback.extract_tb(tb)):
48 if frame.name == '_call_with_frames_removed':
47 if frame.name == '_call_with_frames_removed':
49 continue
48 continue
50 if 'importlib' in frame.filename:
49 if 'importlib' in frame.filename:
51 continue
50 continue
52 if 'mercurial/__init__.py' in frame.filename:
51 if 'mercurial/__init__.py' in frame.filename:
53 continue
52 continue
54 if frame.filename.startswith(sys.prefix):
53 if frame.filename.startswith(sys.prefix):
55 continue
54 continue
56 break
55 break
57
56
58 if frame.filename:
57 if frame.filename:
59 filename = os.path.basename(frame.filename)
58 filename = os.path.basename(frame.filename)
60 print(
59 print(
61 '%s: error importing: <%s> %s (error at %s:%d)'
60 '%s: error importing: <%s> %s (error at %s:%d)'
62 % (f, type(e).__name__, e, filename, frame.lineno)
61 % (f, type(e).__name__, e, filename, frame.lineno)
63 )
62 )
64 else:
63 else:
65 print(
64 print(
66 '%s: error importing module: <%s> %s (line %d)'
65 '%s: error importing module: <%s> %s (line %d)'
67 % (f, type(e).__name__, e, frame.lineno)
66 % (f, type(e).__name__, e, frame.lineno)
68 )
67 )
69
68
70
69
71 if __name__ == '__main__':
70 if __name__ == '__main__':
72 # check_compat_py3 will import every filename we specify as long as it
71 # check_compat_py3 will import every filename we specify as long as it
73 # starts with one of a few prefixes. It does this by converting
72 # starts with one of a few prefixes. It does this by converting
74 # specified filenames like 'mercurial/foo.py' to 'mercurial.foo' and
73 # specified filenames like 'mercurial/foo.py' to 'mercurial.foo' and
75 # importing that. When running standalone (not as part of a test), this
74 # importing that. When running standalone (not as part of a test), this
76 # means we actually import the installed versions, not the files we just
75 # means we actually import the installed versions, not the files we just
77 # specified. When running as test-check-py3-compat.t, we technically
76 # specified. When running as test-check-py3-compat.t, we technically
78 # would import the correct paths, but it's cleaner to have both cases
77 # would import the correct paths, but it's cleaner to have both cases
79 # use the same import logic.
78 # use the same import logic.
80 sys.path.insert(0, '.')
79 sys.path.insert(0, '.')
81
80
82 for f in sys.argv[1:]:
81 for f in sys.argv[1:]:
83 with warnings.catch_warnings(record=True) as warns:
82 with warnings.catch_warnings(record=True) as warns:
84 check_compat_py3(f)
83 check_compat_py3(f)
85
84
86 for w in warns:
85 for w in warns:
87 print(
86 print(
88 warnings.formatwarning(
87 warnings.formatwarning(
89 w.message, w.category, w.filename, w.lineno
88 w.message, w.category, w.filename, w.lineno
90 ).rstrip()
89 ).rstrip()
91 )
90 )
92
91
93 sys.exit(0)
92 sys.exit(0)
@@ -1,51 +1,50 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 #
2 #
3 # Dumps output generated by Mercurial's command server in a formatted style to a
3 # Dumps output generated by Mercurial's command server in a formatted style to a
4 # given file or stderr if '-' is specified. Output is also written in its raw
4 # given file or stderr if '-' is specified. Output is also written in its raw
5 # format to stdout.
5 # format to stdout.
6 #
6 #
7 # $ ./hg serve --cmds pipe | ./contrib/debugcmdserver.py -
7 # $ ./hg serve --cmds pipe | ./contrib/debugcmdserver.py -
8 # o, 52 -> 'capabilities: getencoding runcommand\nencoding: UTF-8'
8 # o, 52 -> 'capabilities: getencoding runcommand\nencoding: UTF-8'
9
9
10 from __future__ import absolute_import, print_function
11 import struct
10 import struct
12 import sys
11 import sys
13
12
14 if len(sys.argv) != 2:
13 if len(sys.argv) != 2:
15 print('usage: debugcmdserver.py FILE')
14 print('usage: debugcmdserver.py FILE')
16 sys.exit(1)
15 sys.exit(1)
17
16
18 outputfmt = '>cI'
17 outputfmt = '>cI'
19 outputfmtsize = struct.calcsize(outputfmt)
18 outputfmtsize = struct.calcsize(outputfmt)
20
19
21 if sys.argv[1] == '-':
20 if sys.argv[1] == '-':
22 log = sys.stderr
21 log = sys.stderr
23 else:
22 else:
24 log = open(sys.argv[1], 'a')
23 log = open(sys.argv[1], 'a')
25
24
26
25
27 def read(size):
26 def read(size):
28 data = sys.stdin.read(size)
27 data = sys.stdin.read(size)
29 if not data:
28 if not data:
30 raise EOFError
29 raise EOFError
31 sys.stdout.write(data)
30 sys.stdout.write(data)
32 sys.stdout.flush()
31 sys.stdout.flush()
33 return data
32 return data
34
33
35
34
36 try:
35 try:
37 while True:
36 while True:
38 header = read(outputfmtsize)
37 header = read(outputfmtsize)
39 channel, length = struct.unpack(outputfmt, header)
38 channel, length = struct.unpack(outputfmt, header)
40 log.write('%s, %-4d' % (channel, length))
39 log.write('%s, %-4d' % (channel, length))
41 if channel in 'IL':
40 if channel in 'IL':
42 log.write(' -> waiting for input\n')
41 log.write(' -> waiting for input\n')
43 else:
42 else:
44 data = read(length)
43 data = read(length)
45 log.write(' -> %r\n' % data)
44 log.write(' -> %r\n' % data)
46 log.flush()
45 log.flush()
47 except EOFError:
46 except EOFError:
48 pass
47 pass
49 finally:
48 finally:
50 if log != sys.stderr:
49 if log != sys.stderr:
51 log.close()
50 log.close()
@@ -1,65 +1,64 b''
1 # debugshell extension
1 # debugshell extension
2 """a python shell with repo, changelog & manifest objects"""
2 """a python shell with repo, changelog & manifest objects"""
3
3
4 from __future__ import absolute_import
5 import code
4 import code
6 import mercurial
5 import mercurial
7 import sys
6 import sys
8 from mercurial import (
7 from mercurial import (
9 demandimport,
8 demandimport,
10 pycompat,
9 pycompat,
11 registrar,
10 registrar,
12 )
11 )
13
12
14 cmdtable = {}
13 cmdtable = {}
15 command = registrar.command(cmdtable)
14 command = registrar.command(cmdtable)
16
15
17
16
18 def pdb(ui, repo, msg, **opts):
17 def pdb(ui, repo, msg, **opts):
19 objects = {
18 objects = {
20 'mercurial': mercurial,
19 'mercurial': mercurial,
21 'repo': repo,
20 'repo': repo,
22 'cl': repo.changelog,
21 'cl': repo.changelog,
23 'mf': repo.manifestlog,
22 'mf': repo.manifestlog,
24 }
23 }
25
24
26 code.interact(msg, local=objects)
25 code.interact(msg, local=objects)
27
26
28
27
29 def ipdb(ui, repo, msg, **opts):
28 def ipdb(ui, repo, msg, **opts):
30 import IPython
29 import IPython
31
30
32 cl = repo.changelog
31 cl = repo.changelog
33 mf = repo.manifestlog
32 mf = repo.manifestlog
34 cl, mf # use variables to appease pyflakes
33 cl, mf # use variables to appease pyflakes
35
34
36 IPython.embed()
35 IPython.embed()
37
36
38
37
39 @command(b'debugshell|dbsh', [])
38 @command(b'debugshell|dbsh', [])
40 def debugshell(ui, repo, **opts):
39 def debugshell(ui, repo, **opts):
41 bannermsg = "loaded repo : %s\n" "using source: %s" % (
40 bannermsg = "loaded repo : %s\n" "using source: %s" % (
42 pycompat.sysstr(repo.root),
41 pycompat.sysstr(repo.root),
43 mercurial.__path__[0],
42 mercurial.__path__[0],
44 )
43 )
45
44
46 pdbmap = {'pdb': 'code', 'ipdb': 'IPython'}
45 pdbmap = {'pdb': 'code', 'ipdb': 'IPython'}
47
46
48 debugger = ui.config(b"ui", b"debugger")
47 debugger = ui.config(b"ui", b"debugger")
49 if not debugger:
48 if not debugger:
50 debugger = 'pdb'
49 debugger = 'pdb'
51 else:
50 else:
52 debugger = pycompat.sysstr(debugger)
51 debugger = pycompat.sysstr(debugger)
53
52
54 # if IPython doesn't exist, fallback to code.interact
53 # if IPython doesn't exist, fallback to code.interact
55 try:
54 try:
56 with demandimport.deactivated():
55 with demandimport.deactivated():
57 __import__(pdbmap[debugger])
56 __import__(pdbmap[debugger])
58 except ImportError:
57 except ImportError:
59 ui.warnnoi18n(
58 ui.warnnoi18n(
60 b"%s debugger specified but %s module was not found\n"
59 b"%s debugger specified but %s module was not found\n"
61 % (debugger, pdbmap[debugger])
60 % (debugger, pdbmap[debugger])
62 )
61 )
63 debugger = b'pdb'
62 debugger = b'pdb'
64
63
65 getattr(sys.modules[__name__], debugger)(ui, repo, bannermsg, **opts)
64 getattr(sys.modules[__name__], debugger)(ui, repo, bannermsg, **opts)
@@ -1,60 +1,59 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 # Dump revlogs as raw data stream
2 # Dump revlogs as raw data stream
3 # $ find .hg/store/ -name "*.i" | xargs dumprevlog > repo.dump
3 # $ find .hg/store/ -name "*.i" | xargs dumprevlog > repo.dump
4
4
5 from __future__ import absolute_import, print_function
6
5
7 import sys
6 import sys
8 from mercurial.node import hex
7 from mercurial.node import hex
9 from mercurial import (
8 from mercurial import (
10 encoding,
9 encoding,
11 pycompat,
10 pycompat,
12 revlog,
11 revlog,
13 )
12 )
14 from mercurial.utils import procutil
13 from mercurial.utils import procutil
15
14
16 from mercurial.revlogutils import (
15 from mercurial.revlogutils import (
17 constants as revlog_constants,
16 constants as revlog_constants,
18 )
17 )
19
18
20 for fp in (sys.stdin, sys.stdout, sys.stderr):
19 for fp in (sys.stdin, sys.stdout, sys.stderr):
21 procutil.setbinary(fp)
20 procutil.setbinary(fp)
22
21
23
22
24 def binopen(path, mode=b'rb'):
23 def binopen(path, mode=b'rb'):
25 if b'b' not in mode:
24 if b'b' not in mode:
26 mode = mode + b'b'
25 mode = mode + b'b'
27 return open(path, pycompat.sysstr(mode))
26 return open(path, pycompat.sysstr(mode))
28
27
29
28
30 binopen.options = {}
29 binopen.options = {}
31
30
32
31
33 def printb(data, end=b'\n'):
32 def printb(data, end=b'\n'):
34 sys.stdout.flush()
33 sys.stdout.flush()
35 procutil.stdout.write(data + end)
34 procutil.stdout.write(data + end)
36
35
37
36
38 for f in sys.argv[1:]:
37 for f in sys.argv[1:]:
39 localf = encoding.strtolocal(f)
38 localf = encoding.strtolocal(f)
40 if not localf.endswith(b'.i'):
39 if not localf.endswith(b'.i'):
41 print("file:", f, file=sys.stderr)
40 print("file:", f, file=sys.stderr)
42 print(" invalid filename", file=sys.stderr)
41 print(" invalid filename", file=sys.stderr)
43
42
44 r = revlog.revlog(
43 r = revlog.revlog(
45 binopen,
44 binopen,
46 target=(revlog_constants.KIND_OTHER, b'dump-revlog'),
45 target=(revlog_constants.KIND_OTHER, b'dump-revlog'),
47 radix=localf[:-2],
46 radix=localf[:-2],
48 )
47 )
49 print("file:", f)
48 print("file:", f)
50 for i in r:
49 for i in r:
51 n = r.node(i)
50 n = r.node(i)
52 p = r.parents(n)
51 p = r.parents(n)
53 d = r.revision(n)
52 d = r.revision(n)
54 printb(b"node: %s" % hex(n))
53 printb(b"node: %s" % hex(n))
55 printb(b"linkrev: %d" % r.linkrev(i))
54 printb(b"linkrev: %d" % r.linkrev(i))
56 printb(b"parents: %s %s" % (hex(p[0]), hex(p[1])))
55 printb(b"parents: %s %s" % (hex(p[0]), hex(p[1])))
57 printb(b"length: %d" % len(d))
56 printb(b"length: %d" % len(d))
58 printb(b"-start-")
57 printb(b"-start-")
59 printb(d)
58 printb(d)
60 printb(b"-end-")
59 printb(b"-end-")
@@ -1,29 +1,27 b''
1 from __future__ import absolute_import, print_function
2
3 import argparse
1 import argparse
4 import zipfile
2 import zipfile
5
3
6 ap = argparse.ArgumentParser()
4 ap = argparse.ArgumentParser()
7 ap.add_argument("out", metavar="some.zip", type=str, nargs=1)
5 ap.add_argument("out", metavar="some.zip", type=str, nargs=1)
8 args = ap.parse_args()
6 args = ap.parse_args()
9
7
10 with zipfile.ZipFile(args.out[0], "w", zipfile.ZIP_STORED) as zf:
8 with zipfile.ZipFile(args.out[0], "w", zipfile.ZIP_STORED) as zf:
11 zf.writestr(
9 zf.writestr(
12 "greek-tree",
10 "greek-tree",
13 "\n".join(
11 "\n".join(
14 [
12 [
15 "iota",
13 "iota",
16 "A/mu",
14 "A/mu",
17 "A/B/lambda",
15 "A/B/lambda",
18 "A/B/E/alpha",
16 "A/B/E/alpha",
19 "A/B/E/beta",
17 "A/B/E/beta",
20 "A/D/gamma",
18 "A/D/gamma",
21 "A/D/G/pi",
19 "A/D/G/pi",
22 "A/D/G/rho",
20 "A/D/G/rho",
23 "A/D/G/tau",
21 "A/D/G/tau",
24 "A/D/H/chi",
22 "A/D/H/chi",
25 "A/D/H/omega",
23 "A/D/H/omega",
26 "A/D/H/psi",
24 "A/D/H/psi",
27 ]
25 ]
28 ),
26 ),
29 )
27 )
@@ -1,17 +1,15 b''
1 from __future__ import absolute_import, print_function
2
3 import argparse
1 import argparse
4 import os
2 import os
5 import zipfile
3 import zipfile
6
4
7 ap = argparse.ArgumentParser()
5 ap = argparse.ArgumentParser()
8 ap.add_argument("out", metavar="some.zip", type=str, nargs=1)
6 ap.add_argument("out", metavar="some.zip", type=str, nargs=1)
9 args = ap.parse_args()
7 args = ap.parse_args()
10
8
11 reporoot = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..'))
9 reporoot = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..'))
12 dirstate = os.path.join(reporoot, '.hg', 'dirstate')
10 dirstate = os.path.join(reporoot, '.hg', 'dirstate')
13
11
14 with zipfile.ZipFile(args.out[0], "w", zipfile.ZIP_STORED) as zf:
12 with zipfile.ZipFile(args.out[0], "w", zipfile.ZIP_STORED) as zf:
15 if os.path.exists(dirstate):
13 if os.path.exists(dirstate):
16 with open(dirstate, 'rb') as f:
14 with open(dirstate, 'rb') as f:
17 zf.writestr("dirstate", f.read())
15 zf.writestr("dirstate", f.read())
@@ -1,38 +1,36 b''
1 from __future__ import absolute_import, print_function
2
3 import argparse
1 import argparse
4 import zipfile
2 import zipfile
5
3
6 ap = argparse.ArgumentParser()
4 ap = argparse.ArgumentParser()
7 ap.add_argument("out", metavar="some.zip", type=str, nargs=1)
5 ap.add_argument("out", metavar="some.zip", type=str, nargs=1)
8 args = ap.parse_args()
6 args = ap.parse_args()
9
7
10 with zipfile.ZipFile(args.out[0], "w", zipfile.ZIP_STORED) as zf:
8 with zipfile.ZipFile(args.out[0], "w", zipfile.ZIP_STORED) as zf:
11 zf.writestr(
9 zf.writestr(
12 'smallish_obsstore',
10 'smallish_obsstore',
13 (
11 (
14 # header: fm1readmarkers should start at offset 1, and
12 # header: fm1readmarkers should start at offset 1, and
15 # read until byte 597.
13 # read until byte 597.
16 '1\x00597\x00'
14 '1\x00597\x00'
17 # body of obsstore file
15 # body of obsstore file
18 '\x01\x00\x00\x00vA\xd7\x02+C\x1a<)\x01,\x00\x00\x01\x03\x03\xe6'
16 '\x01\x00\x00\x00vA\xd7\x02+C\x1a<)\x01,\x00\x00\x01\x03\x03\xe6'
19 '\x92\xde)x\x16\xd1Xph\xc7\xa7[\xe5\xe2\x1a\xab\x1e6e\xaf\xc2\xae'
17 '\x92\xde)x\x16\xd1Xph\xc7\xa7[\xe5\xe2\x1a\xab\x1e6e\xaf\xc2\xae'
20 '\xe7\xbc\x83\xe1\x88\xa5\xda\xce>O\xbd\x04\xe9\x03\xc4o\xeb\x03'
18 '\xe7\xbc\x83\xe1\x88\xa5\xda\xce>O\xbd\x04\xe9\x03\xc4o\xeb\x03'
21 '\x01\t\x05\x04\x1fef18operationamenduserAugie Fackler <raf@duri'
19 '\x01\t\x05\x04\x1fef18operationamenduserAugie Fackler <raf@duri'
22 'n42.com>\x00\x00\x00vA\xd7\x02-\x8aD\xaf-\x01,\x00\x00\x01\x03\x03'
20 'n42.com>\x00\x00\x00vA\xd7\x02-\x8aD\xaf-\x01,\x00\x00\x01\x03\x03'
23 '\x17*\xca\x8f\x9e}i\xe0i\xbb\xdf\x9fb\x03\xd2XG?\xd3h\x98\x89\x1a'
21 '\x17*\xca\x8f\x9e}i\xe0i\xbb\xdf\x9fb\x03\xd2XG?\xd3h\x98\x89\x1a'
24 '=2\xeb\xc3\xc5<\xb3\x9e\xcc\x0e;#\xee\xc3\x10ux\x03\x01\t\x05\x04'
22 '=2\xeb\xc3\xc5<\xb3\x9e\xcc\x0e;#\xee\xc3\x10ux\x03\x01\t\x05\x04'
25 '\x1fef18operationamenduserAugie Fackler <raf@durin42.com>\x00\x00'
23 '\x1fef18operationamenduserAugie Fackler <raf@durin42.com>\x00\x00'
26 '\x00vA\xd7\x02Mn\xd9%\xea\x01,\x00\x00\x01\x03\x03\x98\x89\x1a='
24 '\x00vA\xd7\x02Mn\xd9%\xea\x01,\x00\x00\x01\x03\x03\x98\x89\x1a='
27 '2\xeb\xc3\xc5<\xb3\x9e\xcc\x0e;#\xee\xc3\x10ux\xe0*\xcaT\x86Z8J'
25 '2\xeb\xc3\xc5<\xb3\x9e\xcc\x0e;#\xee\xc3\x10ux\xe0*\xcaT\x86Z8J'
28 '\x85)\x97\xff7\xcc)\xc1\x7f\x19\x0c\x01\x03\x01\t\x05\x04\x1fef'
26 '\x85)\x97\xff7\xcc)\xc1\x7f\x19\x0c\x01\x03\x01\t\x05\x04\x1fef'
29 '18operationamenduserAugie Fackler <raf@durin42.com>\x00\x00\x00'
27 '18operationamenduserAugie Fackler <raf@durin42.com>\x00\x00\x00'
30 'yA\xd7\x02MtA\xbfj\x01,\x00\x00\x01\x03\x03\xe0*\xcaT\x86Z8J\x85'
28 'yA\xd7\x02MtA\xbfj\x01,\x00\x00\x01\x03\x03\xe0*\xcaT\x86Z8J\x85'
31 ')\x97\xff7\xcc)\xc1\x7f\x19\x0c\x01\x00\x94\x01\xa9\n\xf80\x92\xa3'
29 ')\x97\xff7\xcc)\xc1\x7f\x19\x0c\x01\x00\x94\x01\xa9\n\xf80\x92\xa3'
32 'j\xc5X\xb1\xc9:\xd51\xb8*\xa9\x03\x01\t\x08\x04\x1fef11operatio'
30 'j\xc5X\xb1\xc9:\xd51\xb8*\xa9\x03\x01\t\x08\x04\x1fef11operatio'
33 'nhistedituserAugie Fackler <raf@durin42.com>\x00\x00\x00yA\xd7\x02'
31 'nhistedituserAugie Fackler <raf@durin42.com>\x00\x00\x00yA\xd7\x02'
34 'MtA\xd4\xe1\x01,\x00\x00\x01\x03\x03"\xa5\xcb\x86\xb6\xf4\xbaO\xa0'
32 'MtA\xd4\xe1\x01,\x00\x00\x01\x03\x03"\xa5\xcb\x86\xb6\xf4\xbaO\xa0'
35 'sH\xe7?\xcb\x9b\xc2n\xcfI\x9e\x14\xf0D\xf0!\x18DN\xcd\x97\x016\xa5'
33 'sH\xe7?\xcb\x9b\xc2n\xcfI\x9e\x14\xf0D\xf0!\x18DN\xcd\x97\x016\xa5'
36 '\xef\xa06\xcb\x884\x8a\x03\x01\t\x08\x04\x1fef14operationhisted'
34 '\xef\xa06\xcb\x884\x8a\x03\x01\t\x08\x04\x1fef14operationhisted'
37 ),
35 ),
38 )
36 )
@@ -1,38 +1,36 b''
1 from __future__ import absolute_import, print_function
2
3 import argparse
1 import argparse
4 import zipfile
2 import zipfile
5
3
6 ap = argparse.ArgumentParser()
4 ap = argparse.ArgumentParser()
7 ap.add_argument("out", metavar="some.zip", type=str, nargs=1)
5 ap.add_argument("out", metavar="some.zip", type=str, nargs=1)
8 args = ap.parse_args()
6 args = ap.parse_args()
9
7
10 with zipfile.ZipFile(args.out[0], "w", zipfile.ZIP_STORED) as zf:
8 with zipfile.ZipFile(args.out[0], "w", zipfile.ZIP_STORED) as zf:
11 zf.writestr(
9 zf.writestr(
12 "manifest_zero",
10 "manifest_zero",
13 '''\0PKG-INFO\09b3ed8f2b81095a13064402e930565f083346e9a
11 '''\0PKG-INFO\09b3ed8f2b81095a13064402e930565f083346e9a
14 README\080b6e76643dcb44d4bc729e932fc464b3e36dbe3
12 README\080b6e76643dcb44d4bc729e932fc464b3e36dbe3
15 hg\0b6444347c629cc058d478023905cfb83b7f5bb9d
13 hg\0b6444347c629cc058d478023905cfb83b7f5bb9d
16 mercurial/__init__.py\0b80de5d138758541c5f05265ad144ab9fa86d1db
14 mercurial/__init__.py\0b80de5d138758541c5f05265ad144ab9fa86d1db
17 mercurial/byterange.py\017f5a9fbd99622f31a392c33ac1e903925dc80ed
15 mercurial/byterange.py\017f5a9fbd99622f31a392c33ac1e903925dc80ed
18 mercurial/fancyopts.py\0b6f52e23e356748c5039313d8b639cda16bf67ba
16 mercurial/fancyopts.py\0b6f52e23e356748c5039313d8b639cda16bf67ba
19 mercurial/hg.py\023cc12f225f1b42f32dc0d897a4f95a38ddc8f4a
17 mercurial/hg.py\023cc12f225f1b42f32dc0d897a4f95a38ddc8f4a
20 mercurial/mdiff.py\0a05f65c44bfbeec6a42336cd2ff0b30217899ca3
18 mercurial/mdiff.py\0a05f65c44bfbeec6a42336cd2ff0b30217899ca3
21 mercurial/revlog.py\0217bc3fde6d82c0210cf56aeae11d05a03f35b2b
19 mercurial/revlog.py\0217bc3fde6d82c0210cf56aeae11d05a03f35b2b
22 mercurial/transaction.py\09d180df101dc14ce3dd582fd998b36c98b3e39aa
20 mercurial/transaction.py\09d180df101dc14ce3dd582fd998b36c98b3e39aa
23 notes.txt\0703afcec5edb749cf5cec67831f554d6da13f2fb
21 notes.txt\0703afcec5edb749cf5cec67831f554d6da13f2fb
24 setup.py\0ccf3f6daf0f13101ca73631f7a1769e328b472c9
22 setup.py\0ccf3f6daf0f13101ca73631f7a1769e328b472c9
25 tkmerge\03c922edb43a9c143682f7bc7b00f98b3c756ebe7
23 tkmerge\03c922edb43a9c143682f7bc7b00f98b3c756ebe7
26 ''',
24 ''',
27 )
25 )
28 zf.writestr("badmanifest_shorthashes", "\0narf\0aa\nnarf2\0aaa\n")
26 zf.writestr("badmanifest_shorthashes", "\0narf\0aa\nnarf2\0aaa\n")
29 zf.writestr(
27 zf.writestr(
30 "badmanifest_nonull",
28 "badmanifest_nonull",
31 "\0narf\0cccccccccccccccccccccccccccccccccccccccc\n"
29 "\0narf\0cccccccccccccccccccccccccccccccccccccccc\n"
32 "narf2aaaaaaaaaaaaaaaaaaaa\n",
30 "narf2aaaaaaaaaaaaaaaaaaaa\n",
33 )
31 )
34
32
35 zf.writestr(
33 zf.writestr(
36 "manifest_long_nodes",
34 "manifest_long_nodes",
37 "\1a\0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\n",
35 "\1a\0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\n",
38 )
36 )
@@ -1,371 +1,369 b''
1 from __future__ import absolute_import, print_function
2
3 import argparse
1 import argparse
4 import os
2 import os
5 import struct
3 import struct
6 import sys
4 import sys
7 import zipfile
5 import zipfile
8
6
9 # Add ../.. to sys.path as an absolute path so we can import hg modules
7 # Add ../.. to sys.path as an absolute path so we can import hg modules
10 hgloc = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
8 hgloc = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
11 sys.path[0:0] = [hgloc]
9 sys.path[0:0] = [hgloc]
12
10
13 from mercurial import (
11 from mercurial import (
14 hg,
12 hg,
15 ui as uimod,
13 ui as uimod,
16 )
14 )
17
15
18 ap = argparse.ArgumentParser()
16 ap = argparse.ArgumentParser()
19 ap.add_argument("out", metavar="some.zip", type=str, nargs=1)
17 ap.add_argument("out", metavar="some.zip", type=str, nargs=1)
20 args = ap.parse_args()
18 args = ap.parse_args()
21
19
22
20
23 if sys.version_info[0] < 3:
21 if sys.version_info[0] < 3:
24
22
25 class py2reprhack(object):
23 class py2reprhack(object):
26 def __repr__(self):
24 def __repr__(self):
27 """Py2 calls __repr__ for `bytes(foo)`, forward to __bytes__"""
25 """Py2 calls __repr__ for `bytes(foo)`, forward to __bytes__"""
28 return self.__bytes__()
26 return self.__bytes__()
29
27
30
28
31 else:
29 else:
32
30
33 class py2reprhack(object):
31 class py2reprhack(object):
34 """Not needed on py3."""
32 """Not needed on py3."""
35
33
36
34
37 class deltafrag(py2reprhack):
35 class deltafrag(py2reprhack):
38 def __init__(self, start, end, data):
36 def __init__(self, start, end, data):
39 self.start = start
37 self.start = start
40 self.end = end
38 self.end = end
41 self.data = data
39 self.data = data
42
40
43 def __bytes__(self):
41 def __bytes__(self):
44 return (
42 return (
45 struct.pack(">lll", self.start, self.end, len(self.data))
43 struct.pack(">lll", self.start, self.end, len(self.data))
46 + self.data
44 + self.data
47 )
45 )
48
46
49
47
50 class delta(py2reprhack):
48 class delta(py2reprhack):
51 def __init__(self, frags):
49 def __init__(self, frags):
52 self.frags = frags
50 self.frags = frags
53
51
54 def __bytes__(self):
52 def __bytes__(self):
55 return b''.join(bytes(f) for f in self.frags)
53 return b''.join(bytes(f) for f in self.frags)
56
54
57
55
58 class corpus(py2reprhack):
56 class corpus(py2reprhack):
59 def __init__(self, base, deltas):
57 def __init__(self, base, deltas):
60 self.base = base
58 self.base = base
61 self.deltas = deltas
59 self.deltas = deltas
62
60
63 def __bytes__(self):
61 def __bytes__(self):
64 deltas = [bytes(d) for d in self.deltas]
62 deltas = [bytes(d) for d in self.deltas]
65 parts = (
63 parts = (
66 [
64 [
67 struct.pack(">B", len(deltas) + 1),
65 struct.pack(">B", len(deltas) + 1),
68 struct.pack(">H", len(self.base)),
66 struct.pack(">H", len(self.base)),
69 ]
67 ]
70 + [struct.pack(">H", len(d)) for d in deltas]
68 + [struct.pack(">H", len(d)) for d in deltas]
71 + [self.base]
69 + [self.base]
72 + deltas
70 + deltas
73 )
71 )
74 return b''.join(parts)
72 return b''.join(parts)
75
73
76
74
77 with zipfile.ZipFile(args.out[0], "w", zipfile.ZIP_STORED) as zf:
75 with zipfile.ZipFile(args.out[0], "w", zipfile.ZIP_STORED) as zf:
78 # Manually constructed entries
76 # Manually constructed entries
79 zf.writestr(
77 zf.writestr(
80 "one_delta_applies",
78 "one_delta_applies",
81 bytes(corpus(b'a', [delta([deltafrag(0, 1, b'b')])])),
79 bytes(corpus(b'a', [delta([deltafrag(0, 1, b'b')])])),
82 )
80 )
83 zf.writestr(
81 zf.writestr(
84 "one_delta_starts_late",
82 "one_delta_starts_late",
85 bytes(corpus(b'a', [delta([deltafrag(3, 1, b'b')])])),
83 bytes(corpus(b'a', [delta([deltafrag(3, 1, b'b')])])),
86 )
84 )
87 zf.writestr(
85 zf.writestr(
88 "one_delta_ends_late",
86 "one_delta_ends_late",
89 bytes(corpus(b'a', [delta([deltafrag(0, 20, b'b')])])),
87 bytes(corpus(b'a', [delta([deltafrag(0, 20, b'b')])])),
90 )
88 )
91
89
92 try:
90 try:
93 # Generated from repo data
91 # Generated from repo data
94 r = hg.repository(uimod.ui(), b'../..')
92 r = hg.repository(uimod.ui(), b'../..')
95 fl = r.file(b'mercurial/manifest.py')
93 fl = r.file(b'mercurial/manifest.py')
96 rl = getattr(fl, '_revlog', fl)
94 rl = getattr(fl, '_revlog', fl)
97 bins = rl._chunks(rl._deltachain(10)[0])
95 bins = rl._chunks(rl._deltachain(10)[0])
98 zf.writestr('manifest_py_rev_10', bytes(corpus(bins[0], bins[1:])))
96 zf.writestr('manifest_py_rev_10', bytes(corpus(bins[0], bins[1:])))
99 except: # skip this, so no re-raises
97 except: # skip this, so no re-raises
100 print('skipping seed file from repo data')
98 print('skipping seed file from repo data')
101 # Automatically discovered by running the fuzzer
99 # Automatically discovered by running the fuzzer
102 zf.writestr(
100 zf.writestr(
103 "mpatch_decode_old_overread", b"\x02\x00\x00\x00\x02\x00\x00\x00"
101 "mpatch_decode_old_overread", b"\x02\x00\x00\x00\x02\x00\x00\x00"
104 )
102 )
105 # https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=8876
103 # https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=8876
106 zf.writestr(
104 zf.writestr(
107 "mpatch_ossfuzz_getbe32_ubsan",
105 "mpatch_ossfuzz_getbe32_ubsan",
108 b"\x02\x00\x00\x00\x0c \xff\xff\xff\xff ",
106 b"\x02\x00\x00\x00\x0c \xff\xff\xff\xff ",
109 )
107 )
110 zf.writestr(
108 zf.writestr(
111 "mpatch_apply_over_memcpy",
109 "mpatch_apply_over_memcpy",
112 b'\x13\x01\x00\x05\xd0\x00\x00\x00\x00\x00\x00\x00\x00\n \x00\x00\x00'
110 b'\x13\x01\x00\x05\xd0\x00\x00\x00\x00\x00\x00\x00\x00\n \x00\x00\x00'
113 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
111 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
114 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00'
112 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00'
115 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
113 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
116 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
114 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
117 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
115 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
118 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
116 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
119 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
117 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
120 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
118 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
121 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
119 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
122 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
120 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
123 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
121 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
124 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
122 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
125 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
123 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
126 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
124 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
127 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
125 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
128 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
126 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
129 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
127 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
130 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
128 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
131 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
129 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
132 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
130 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
133 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
131 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
134 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
132 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
135 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
133 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
136 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
134 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
137 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
135 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
138 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
136 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
139 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
137 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
140 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
138 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
141 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
139 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
142 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8c\x00\x00\x00\x00'
140 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8c\x00\x00\x00\x00'
143 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
141 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
144 b'\x00\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00\x00\x00\x00\x00\x00'
142 b'\x00\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00\x00\x00\x00\x00\x00'
145 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
143 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
146 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
144 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
147 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
145 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
148 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
146 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
149 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
147 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
150 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
148 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
151 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
149 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
152 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
150 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
153 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
151 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
154 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
152 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
155 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
153 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
156 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
154 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
157 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
155 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
158 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
156 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
159 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
157 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
160 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
158 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
161 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
159 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
162 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
160 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
163 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
161 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
164 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
162 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
165 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
163 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
166 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
164 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
167 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
165 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
168 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
166 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
169 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
167 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
170 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
168 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
171 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
169 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
172 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
170 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
173 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
171 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
174 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
172 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
175 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
173 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
176 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
174 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
177 b'\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00A\x00\x00\x00\x00'
175 b'\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00A\x00\x00\x00\x00'
178 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
176 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
179 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
177 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
180 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
178 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
181 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
179 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
182 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
180 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
183 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
181 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
184 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
182 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
185 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
183 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
186 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
184 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
187 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
185 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
188 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
186 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
189 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
187 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
190 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
188 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
191 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
189 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
192 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
190 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
193 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
191 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
194 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
192 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
195 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
193 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
196 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
194 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
197 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
195 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
198 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
196 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
199 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
197 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
200 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
198 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
201 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
199 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
202 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x94\x18'
200 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x94\x18'
203 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
201 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
204 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
202 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
205 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
203 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
206 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
204 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
207 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
205 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
208 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
206 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
209 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
207 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
210 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
208 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
211 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
209 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
212 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
210 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
213 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
211 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
214 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
212 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
215 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
213 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
216 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
214 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
217 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
215 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
218 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
216 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
219 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
217 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
220 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
218 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
221 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
219 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
222 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
220 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
223 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
221 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
224 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
222 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
225 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
223 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
226 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
224 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
227 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
225 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
228 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
226 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
229 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
227 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
230 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
228 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
231 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
229 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
232 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
230 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
233 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
231 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
234 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
232 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
235 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
233 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
236 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
234 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
237 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
235 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
238 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
236 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
239 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
237 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
240 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
238 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
241 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
239 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
242 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
240 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
243 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
241 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
244 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
242 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
245 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
243 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
246 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
244 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
247 b'\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
245 b'\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
248 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
246 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
249 b'\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfa\x00\x00\x00\x00\x00\x00\x00'
247 b'\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfa\x00\x00\x00\x00\x00\x00\x00'
250 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
248 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
251 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
249 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
252 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
250 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
253 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
251 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
254 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
252 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
255 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
253 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
256 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
254 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
257 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
255 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
258 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
256 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
259 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
257 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
260 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
258 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
261 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
259 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
262 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
260 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
263 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
261 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
264 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
262 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
265 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
263 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
266 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
264 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
267 b'\x00\x00\x94\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
265 b'\x00\x00\x94\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
268 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
266 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
269 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
267 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
270 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
268 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
271 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
269 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
272 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
270 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
273 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
271 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
274 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
272 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
275 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
273 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
276 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
274 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
277 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
275 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
278 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
276 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
279 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
277 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
280 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
278 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
281 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
279 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
282 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
280 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
283 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
281 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
284 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
282 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
285 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
283 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
286 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
284 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
287 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
285 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
288 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
286 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
289 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
287 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
290 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
288 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
291 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
289 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
292 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
290 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
293 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
291 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
294 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
292 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
295 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
293 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
296 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
294 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
297 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
295 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
298 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
296 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
299 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
297 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
300 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
298 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
301 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
299 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
302 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
300 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
303 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
301 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
304 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
302 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
305 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
303 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
306 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
304 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
307 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
305 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
308 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
306 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
309 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
307 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
310 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
308 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
311 b'\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
309 b'\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
312 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
310 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
313 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfa\x00\x00\x00'
311 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfa\x00\x00\x00'
314 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
312 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
315 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
313 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
316 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
314 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
317 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
315 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
318 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
316 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
319 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
317 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
320 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
318 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
321 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
319 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
322 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
320 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
323 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
321 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
324 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
322 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
325 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
323 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
326 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
324 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
327 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
325 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
328 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
326 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
329 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
327 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
330 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
328 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
331 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
329 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
332 b'\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00'
330 b'\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00'
333 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
331 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
334 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
332 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
335 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
333 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
336 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
334 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
337 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
335 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
338 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
336 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
339 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
337 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
340 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
338 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
341 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
339 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
342 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
340 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
343 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
341 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
344 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
342 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
345 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
343 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
346 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
344 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
347 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x00\x00\x00\x00\x00\x00'
345 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x00\x00\x00\x00\x00\x00'
348 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
346 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
349 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
347 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
350 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
348 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
351 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
349 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
352 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00]\x00\x00'
350 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00]\x00\x00'
353 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
351 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
354 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
352 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
355 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
353 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
356 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
354 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
357 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
355 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
358 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
356 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
359 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
357 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
360 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
358 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
361 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
359 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
362 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
360 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
363 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
361 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
364 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
362 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
365 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
363 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
366 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
364 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
367 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
365 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
368 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00se\x00\x00'
366 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00se\x00\x00'
369 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
367 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
370 b'\x00\x00\x00\x00',
368 b'\x00\x00\x00\x00',
371 )
369 )
@@ -1,25 +1,23 b''
1 from __future__ import absolute_import
2
3 import argparse
1 import argparse
4 import os
2 import os
5 import zipfile
3 import zipfile
6
4
7 ap = argparse.ArgumentParser()
5 ap = argparse.ArgumentParser()
8 ap.add_argument("out", metavar="some.zip", type=str, nargs=1)
6 ap.add_argument("out", metavar="some.zip", type=str, nargs=1)
9 args = ap.parse_args()
7 args = ap.parse_args()
10
8
11 reporoot = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..'))
9 reporoot = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..'))
12 # typically a standalone index
10 # typically a standalone index
13 changelog = os.path.join(reporoot, '.hg', 'store', '00changelog.i')
11 changelog = os.path.join(reporoot, '.hg', 'store', '00changelog.i')
14 # an inline revlog with only a few revisions
12 # an inline revlog with only a few revisions
15 contributing = os.path.join(
13 contributing = os.path.join(
16 reporoot, '.hg', 'store', 'data', 'contrib', 'fuzz', 'mpatch.cc.i'
14 reporoot, '.hg', 'store', 'data', 'contrib', 'fuzz', 'mpatch.cc.i'
17 )
15 )
18
16
19 with zipfile.ZipFile(args.out[0], "w", zipfile.ZIP_STORED) as zf:
17 with zipfile.ZipFile(args.out[0], "w", zipfile.ZIP_STORED) as zf:
20 if os.path.exists(changelog):
18 if os.path.exists(changelog):
21 with open(changelog, 'rb') as f:
19 with open(changelog, 'rb') as f:
22 zf.writestr("00changelog.i", f.read())
20 zf.writestr("00changelog.i", f.read())
23 if os.path.exists(contributing):
21 if os.path.exists(contributing):
24 with open(contributing, 'rb') as f:
22 with open(contributing, 'rb') as f:
25 zf.writestr("contributing.i", f.read())
23 zf.writestr("contributing.i", f.read())
@@ -1,141 +1,140 b''
1 #!/usr/bin/env python2
1 #!/usr/bin/env python2
2 from __future__ import absolute_import, print_function
3
2
4 import argparse
3 import argparse
5 import os
4 import os
6 import subprocess
5 import subprocess
7 import sys
6 import sys
8
7
9 try:
8 try:
10 # Always load hg libraries from the hg we can find on $PATH.
9 # Always load hg libraries from the hg we can find on $PATH.
11 hglib = subprocess.check_output(['hg', 'debuginstall', '-T', '{hgmodules}'])
10 hglib = subprocess.check_output(['hg', 'debuginstall', '-T', '{hgmodules}'])
12 sys.path.insert(0, os.path.dirname(hglib))
11 sys.path.insert(0, os.path.dirname(hglib))
13 except subprocess.CalledProcessError:
12 except subprocess.CalledProcessError:
14 # We're probably running with a PyOxidized Mercurial, so just
13 # We're probably running with a PyOxidized Mercurial, so just
15 # proceed and hope it works out okay.
14 # proceed and hope it works out okay.
16 pass
15 pass
17
16
18 from mercurial import util
17 from mercurial import util
19
18
20 ap = argparse.ArgumentParser()
19 ap = argparse.ArgumentParser()
21 ap.add_argument(
20 ap.add_argument(
22 '--paranoid',
21 '--paranoid',
23 action='store_true',
22 action='store_true',
24 help=(
23 help=(
25 "Be paranoid about how version numbers compare and "
24 "Be paranoid about how version numbers compare and "
26 "produce something that's more likely to sort "
25 "produce something that's more likely to sort "
27 "reasonably."
26 "reasonably."
28 ),
27 ),
29 )
28 )
30 ap.add_argument('--selftest', action='store_true', help='Run self-tests.')
29 ap.add_argument('--selftest', action='store_true', help='Run self-tests.')
31 ap.add_argument('versionfile', help='Path to a valid mercurial __version__.py')
30 ap.add_argument('versionfile', help='Path to a valid mercurial __version__.py')
32
31
33
32
34 def paranoidver(ver):
33 def paranoidver(ver):
35 """Given an hg version produce something that distutils can sort.
34 """Given an hg version produce something that distutils can sort.
36
35
37 Some Mac package management systems use distutils code in order to
36 Some Mac package management systems use distutils code in order to
38 figure out upgrades, which makes life difficult. The test case is
37 figure out upgrades, which makes life difficult. The test case is
39 a reduced version of code in the Munki tool used by some large
38 a reduced version of code in the Munki tool used by some large
40 organizations to centrally manage OS X packages, which is what
39 organizations to centrally manage OS X packages, which is what
41 inspired this kludge.
40 inspired this kludge.
42
41
43 >>> paranoidver('3.4')
42 >>> paranoidver('3.4')
44 '3.4.0'
43 '3.4.0'
45 >>> paranoidver('3.4.2')
44 >>> paranoidver('3.4.2')
46 '3.4.2'
45 '3.4.2'
47 >>> paranoidver('3.0-rc+10')
46 >>> paranoidver('3.0-rc+10')
48 '2.9.9999-rc+10'
47 '2.9.9999-rc+10'
49 >>> paranoidver('4.2+483-5d44d7d4076e')
48 >>> paranoidver('4.2+483-5d44d7d4076e')
50 '4.2.0+483-5d44d7d4076e'
49 '4.2.0+483-5d44d7d4076e'
51 >>> paranoidver('4.2.1+598-48d1e1214d8c')
50 >>> paranoidver('4.2.1+598-48d1e1214d8c')
52 '4.2.1+598-48d1e1214d8c'
51 '4.2.1+598-48d1e1214d8c'
53 >>> paranoidver('4.3-rc')
52 >>> paranoidver('4.3-rc')
54 '4.2.9999-rc'
53 '4.2.9999-rc'
55 >>> paranoidver('4.3')
54 >>> paranoidver('4.3')
56 '4.3.0'
55 '4.3.0'
57 >>> from distutils import version
56 >>> from distutils import version
58 >>> class LossyPaddedVersion(version.LooseVersion):
57 >>> class LossyPaddedVersion(version.LooseVersion):
59 ... '''Subclass version.LooseVersion to compare things like
58 ... '''Subclass version.LooseVersion to compare things like
60 ... "10.6" and "10.6.0" as equal'''
59 ... "10.6" and "10.6.0" as equal'''
61 ... def __init__(self, s):
60 ... def __init__(self, s):
62 ... self.parse(s)
61 ... self.parse(s)
63 ...
62 ...
64 ... def _pad(self, version_list, max_length):
63 ... def _pad(self, version_list, max_length):
65 ... 'Pad a version list by adding extra 0 components to the end'
64 ... 'Pad a version list by adding extra 0 components to the end'
66 ... # copy the version_list so we don't modify it
65 ... # copy the version_list so we don't modify it
67 ... cmp_list = list(version_list)
66 ... cmp_list = list(version_list)
68 ... while len(cmp_list) < max_length:
67 ... while len(cmp_list) < max_length:
69 ... cmp_list.append(0)
68 ... cmp_list.append(0)
70 ... return cmp_list
69 ... return cmp_list
71 ...
70 ...
72 ... def __cmp__(self, other):
71 ... def __cmp__(self, other):
73 ... if isinstance(other, str):
72 ... if isinstance(other, str):
74 ... other = MunkiLooseVersion(other)
73 ... other = MunkiLooseVersion(other)
75 ... max_length = max(len(self.version), len(other.version))
74 ... max_length = max(len(self.version), len(other.version))
76 ... self_cmp_version = self._pad(self.version, max_length)
75 ... self_cmp_version = self._pad(self.version, max_length)
77 ... other_cmp_version = self._pad(other.version, max_length)
76 ... other_cmp_version = self._pad(other.version, max_length)
78 ... return cmp(self_cmp_version, other_cmp_version)
77 ... return cmp(self_cmp_version, other_cmp_version)
79 >>> def testver(older, newer):
78 >>> def testver(older, newer):
80 ... o = LossyPaddedVersion(paranoidver(older))
79 ... o = LossyPaddedVersion(paranoidver(older))
81 ... n = LossyPaddedVersion(paranoidver(newer))
80 ... n = LossyPaddedVersion(paranoidver(newer))
82 ... return o < n
81 ... return o < n
83 >>> testver('3.4', '3.5')
82 >>> testver('3.4', '3.5')
84 True
83 True
85 >>> testver('3.4.0', '3.5-rc')
84 >>> testver('3.4.0', '3.5-rc')
86 True
85 True
87 >>> testver('3.4-rc', '3.5')
86 >>> testver('3.4-rc', '3.5')
88 True
87 True
89 >>> testver('3.4-rc+10-deadbeef', '3.5')
88 >>> testver('3.4-rc+10-deadbeef', '3.5')
90 True
89 True
91 >>> testver('3.4.2', '3.5-rc')
90 >>> testver('3.4.2', '3.5-rc')
92 True
91 True
93 >>> testver('3.4.2', '3.5-rc+10-deadbeef')
92 >>> testver('3.4.2', '3.5-rc+10-deadbeef')
94 True
93 True
95 >>> testver('4.2+483-5d44d7d4076e', '4.2.1+598-48d1e1214d8c')
94 >>> testver('4.2+483-5d44d7d4076e', '4.2.1+598-48d1e1214d8c')
96 True
95 True
97 >>> testver('4.3-rc', '4.3')
96 >>> testver('4.3-rc', '4.3')
98 True
97 True
99 >>> testver('4.3', '4.3-rc')
98 >>> testver('4.3', '4.3-rc')
100 False
99 False
101 """
100 """
102 major, minor, micro, extra = util.versiontuple(ver, n=4)
101 major, minor, micro, extra = util.versiontuple(ver, n=4)
103 if micro is None:
102 if micro is None:
104 micro = 0
103 micro = 0
105 if extra:
104 if extra:
106 if extra.startswith('rc'):
105 if extra.startswith('rc'):
107 if minor == 0:
106 if minor == 0:
108 major -= 1
107 major -= 1
109 minor = 9
108 minor = 9
110 else:
109 else:
111 minor -= 1
110 minor -= 1
112 micro = 9999
111 micro = 9999
113 extra = '-' + extra
112 extra = '-' + extra
114 else:
113 else:
115 extra = '+' + extra
114 extra = '+' + extra
116 else:
115 else:
117 extra = ''
116 extra = ''
118 return '%d.%d.%d%s' % (major, minor, micro, extra)
117 return '%d.%d.%d%s' % (major, minor, micro, extra)
119
118
120
119
121 def main(argv):
120 def main(argv):
122 opts = ap.parse_args(argv[1:])
121 opts = ap.parse_args(argv[1:])
123 if opts.selftest:
122 if opts.selftest:
124 import doctest
123 import doctest
125
124
126 doctest.testmod()
125 doctest.testmod()
127 return
126 return
128 with open(opts.versionfile) as f:
127 with open(opts.versionfile) as f:
129 for l in f:
128 for l in f:
130 if l.startswith('version = b'):
129 if l.startswith('version = b'):
131 # version number is entire line minus the quotes
130 # version number is entire line minus the quotes
132 ver = l[len('version = b') + 1 : -2]
131 ver = l[len('version = b') + 1 : -2]
133 break
132 break
134 if opts.paranoid:
133 if opts.paranoid:
135 print(paranoidver(ver))
134 print(paranoidver(ver))
136 else:
135 else:
137 print(ver)
136 print(ver)
138
137
139
138
140 if __name__ == '__main__':
139 if __name__ == '__main__':
141 main(sys.argv)
140 main(sys.argv)
@@ -1,118 +1,117 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 #
2 #
3 # Copyright 2005-2007 by Intevation GmbH <intevation@intevation.de>
3 # Copyright 2005-2007 by Intevation GmbH <intevation@intevation.de>
4 #
4 #
5 # Author(s):
5 # Author(s):
6 # Thomas Arendsen Hein <thomas@intevation.de>
6 # Thomas Arendsen Hein <thomas@intevation.de>
7 #
7 #
8 # This software may be used and distributed according to the terms of the
8 # This software may be used and distributed according to the terms of the
9 # GNU General Public License version 2 or any later version.
9 # GNU General Public License version 2 or any later version.
10
10
11 """
11 """
12 hg-ssh - a wrapper for ssh access to a limited set of mercurial repos
12 hg-ssh - a wrapper for ssh access to a limited set of mercurial repos
13
13
14 To be used in ~/.ssh/authorized_keys with the "command" option, see sshd(8):
14 To be used in ~/.ssh/authorized_keys with the "command" option, see sshd(8):
15 command="hg-ssh path/to/repo1 /path/to/repo2 ~/repo3 ~user/repo4" ssh-dss ...
15 command="hg-ssh path/to/repo1 /path/to/repo2 ~/repo3 ~user/repo4" ssh-dss ...
16 (probably together with these other useful options:
16 (probably together with these other useful options:
17 no-port-forwarding,no-X11-forwarding,no-agent-forwarding)
17 no-port-forwarding,no-X11-forwarding,no-agent-forwarding)
18
18
19 This allows pull/push over ssh from/to the repositories given as arguments.
19 This allows pull/push over ssh from/to the repositories given as arguments.
20
20
21 If all your repositories are subdirectories of a common directory, you can
21 If all your repositories are subdirectories of a common directory, you can
22 allow shorter paths with:
22 allow shorter paths with:
23 command="cd path/to/my/repositories && hg-ssh repo1 subdir/repo2"
23 command="cd path/to/my/repositories && hg-ssh repo1 subdir/repo2"
24
24
25 You can use pattern matching of your normal shell, e.g.:
25 You can use pattern matching of your normal shell, e.g.:
26 command="cd repos && hg-ssh user/thomas/* projects/{mercurial,foo}"
26 command="cd repos && hg-ssh user/thomas/* projects/{mercurial,foo}"
27
27
28 You can also add a --read-only flag to allow read-only access to a key, e.g.:
28 You can also add a --read-only flag to allow read-only access to a key, e.g.:
29 command="hg-ssh --read-only repos/*"
29 command="hg-ssh --read-only repos/*"
30 """
30 """
31 from __future__ import absolute_import
32
31
33 import os
32 import os
34 import re
33 import re
35 import shlex
34 import shlex
36 import sys
35 import sys
37
36
38 # enable importing on demand to reduce startup time
37 # enable importing on demand to reduce startup time
39 import hgdemandimport
38 import hgdemandimport
40
39
41 hgdemandimport.enable()
40 hgdemandimport.enable()
42
41
43 from mercurial import (
42 from mercurial import (
44 dispatch,
43 dispatch,
45 pycompat,
44 pycompat,
46 ui as uimod,
45 ui as uimod,
47 )
46 )
48
47
49
48
50 def main():
49 def main():
51 # Prevent insertion/deletion of CRs
50 # Prevent insertion/deletion of CRs
52 dispatch.initstdio()
51 dispatch.initstdio()
53
52
54 cwd = os.getcwd()
53 cwd = os.getcwd()
55 if os.name == 'nt':
54 if os.name == 'nt':
56 # os.getcwd() is inconsistent on the capitalization of the drive
55 # os.getcwd() is inconsistent on the capitalization of the drive
57 # letter, so adjust it. see https://bugs.python.org/issue40368
56 # letter, so adjust it. see https://bugs.python.org/issue40368
58 if re.match('^[a-z]:', cwd):
57 if re.match('^[a-z]:', cwd):
59 cwd = cwd[0:1].upper() + cwd[1:]
58 cwd = cwd[0:1].upper() + cwd[1:]
60
59
61 readonly = False
60 readonly = False
62 args = sys.argv[1:]
61 args = sys.argv[1:]
63 while len(args):
62 while len(args):
64 if args[0] == '--read-only':
63 if args[0] == '--read-only':
65 readonly = True
64 readonly = True
66 args.pop(0)
65 args.pop(0)
67 else:
66 else:
68 break
67 break
69 allowed_paths = [
68 allowed_paths = [
70 os.path.normpath(os.path.join(cwd, os.path.expanduser(path)))
69 os.path.normpath(os.path.join(cwd, os.path.expanduser(path)))
71 for path in args
70 for path in args
72 ]
71 ]
73 orig_cmd = os.getenv('SSH_ORIGINAL_COMMAND', '?')
72 orig_cmd = os.getenv('SSH_ORIGINAL_COMMAND', '?')
74 try:
73 try:
75 cmdargv = shlex.split(orig_cmd)
74 cmdargv = shlex.split(orig_cmd)
76 except ValueError as e:
75 except ValueError as e:
77 sys.stderr.write('Illegal command "%s": %s\n' % (orig_cmd, e))
76 sys.stderr.write('Illegal command "%s": %s\n' % (orig_cmd, e))
78 sys.exit(255)
77 sys.exit(255)
79
78
80 if cmdargv[:2] == ['hg', '-R'] and cmdargv[3:] == ['serve', '--stdio']:
79 if cmdargv[:2] == ['hg', '-R'] and cmdargv[3:] == ['serve', '--stdio']:
81 path = cmdargv[2]
80 path = cmdargv[2]
82 repo = os.path.normpath(os.path.join(cwd, os.path.expanduser(path)))
81 repo = os.path.normpath(os.path.join(cwd, os.path.expanduser(path)))
83 if repo in allowed_paths:
82 if repo in allowed_paths:
84 cmd = [b'-R', pycompat.fsencode(repo), b'serve', b'--stdio']
83 cmd = [b'-R', pycompat.fsencode(repo), b'serve', b'--stdio']
85 req = dispatch.request(cmd)
84 req = dispatch.request(cmd)
86 if readonly:
85 if readonly:
87 if not req.ui:
86 if not req.ui:
88 req.ui = uimod.ui.load()
87 req.ui = uimod.ui.load()
89 req.ui.setconfig(
88 req.ui.setconfig(
90 b'hooks',
89 b'hooks',
91 b'pretxnopen.hg-ssh',
90 b'pretxnopen.hg-ssh',
92 b'python:__main__.rejectpush',
91 b'python:__main__.rejectpush',
93 b'hg-ssh',
92 b'hg-ssh',
94 )
93 )
95 req.ui.setconfig(
94 req.ui.setconfig(
96 b'hooks',
95 b'hooks',
97 b'prepushkey.hg-ssh',
96 b'prepushkey.hg-ssh',
98 b'python:__main__.rejectpush',
97 b'python:__main__.rejectpush',
99 b'hg-ssh',
98 b'hg-ssh',
100 )
99 )
101 dispatch.dispatch(req)
100 dispatch.dispatch(req)
102 else:
101 else:
103 sys.stderr.write('Illegal repository "%s"\n' % repo)
102 sys.stderr.write('Illegal repository "%s"\n' % repo)
104 sys.exit(255)
103 sys.exit(255)
105 else:
104 else:
106 sys.stderr.write('Illegal command "%s"\n' % orig_cmd)
105 sys.stderr.write('Illegal command "%s"\n' % orig_cmd)
107 sys.exit(255)
106 sys.exit(255)
108
107
109
108
110 def rejectpush(ui, **kwargs):
109 def rejectpush(ui, **kwargs):
111 ui.warn((b"Permission denied\n"))
110 ui.warn((b"Permission denied\n"))
112 # mercurial hooks use unix process conventions for hook return values
111 # mercurial hooks use unix process conventions for hook return values
113 # so a truthy return means failure
112 # so a truthy return means failure
114 return True
113 return True
115
114
116
115
117 if __name__ == '__main__':
116 if __name__ == '__main__':
118 main()
117 main()
@@ -1,163 +1,162 b''
1 # A minimal client for Mercurial's command server
1 # A minimal client for Mercurial's command server
2
2
3 from __future__ import absolute_import, print_function
4
3
5 import io
4 import io
6 import os
5 import os
7 import re
6 import re
8 import signal
7 import signal
9 import socket
8 import socket
10 import struct
9 import struct
11 import subprocess
10 import subprocess
12 import sys
11 import sys
13 import time
12 import time
14
13
15 if sys.version_info[0] >= 3:
14 if sys.version_info[0] >= 3:
16 stdout = sys.stdout.buffer
15 stdout = sys.stdout.buffer
17 stderr = sys.stderr.buffer
16 stderr = sys.stderr.buffer
18 stringio = io.BytesIO
17 stringio = io.BytesIO
19
18
20 def bprint(*args):
19 def bprint(*args):
21 # remove b'' as well for ease of test migration
20 # remove b'' as well for ease of test migration
22 pargs = [re.sub(br'''\bb(['"])''', br'\1', b'%s' % a) for a in args]
21 pargs = [re.sub(br'''\bb(['"])''', br'\1', b'%s' % a) for a in args]
23 stdout.write(b' '.join(pargs) + b'\n')
22 stdout.write(b' '.join(pargs) + b'\n')
24
23
25
24
26 else:
25 else:
27 import cStringIO
26 import cStringIO
28
27
29 stdout = sys.stdout
28 stdout = sys.stdout
30 stderr = sys.stderr
29 stderr = sys.stderr
31 stringio = cStringIO.StringIO
30 stringio = cStringIO.StringIO
32 bprint = print
31 bprint = print
33
32
34
33
35 def connectpipe(path=None, extraargs=()):
34 def connectpipe(path=None, extraargs=()):
36 cmdline = [b'hg', b'serve', b'--cmdserver', b'pipe']
35 cmdline = [b'hg', b'serve', b'--cmdserver', b'pipe']
37 if path:
36 if path:
38 cmdline += [b'-R', path]
37 cmdline += [b'-R', path]
39 cmdline.extend(extraargs)
38 cmdline.extend(extraargs)
40
39
41 def tonative(cmdline):
40 def tonative(cmdline):
42 if os.name != 'nt':
41 if os.name != 'nt':
43 return cmdline
42 return cmdline
44 return [arg.decode("utf-8") for arg in cmdline]
43 return [arg.decode("utf-8") for arg in cmdline]
45
44
46 server = subprocess.Popen(
45 server = subprocess.Popen(
47 tonative(cmdline), stdin=subprocess.PIPE, stdout=subprocess.PIPE
46 tonative(cmdline), stdin=subprocess.PIPE, stdout=subprocess.PIPE
48 )
47 )
49
48
50 return server
49 return server
51
50
52
51
53 class unixconnection(object):
52 class unixconnection(object):
54 def __init__(self, sockpath):
53 def __init__(self, sockpath):
55 self.sock = sock = socket.socket(socket.AF_UNIX)
54 self.sock = sock = socket.socket(socket.AF_UNIX)
56 sock.connect(sockpath)
55 sock.connect(sockpath)
57 self.stdin = sock.makefile('wb')
56 self.stdin = sock.makefile('wb')
58 self.stdout = sock.makefile('rb')
57 self.stdout = sock.makefile('rb')
59
58
60 def wait(self):
59 def wait(self):
61 self.stdin.close()
60 self.stdin.close()
62 self.stdout.close()
61 self.stdout.close()
63 self.sock.close()
62 self.sock.close()
64
63
65
64
66 class unixserver(object):
65 class unixserver(object):
67 def __init__(self, sockpath, logpath=None, repopath=None):
66 def __init__(self, sockpath, logpath=None, repopath=None):
68 self.sockpath = sockpath
67 self.sockpath = sockpath
69 cmdline = [b'hg', b'serve', b'--cmdserver', b'unix', b'-a', sockpath]
68 cmdline = [b'hg', b'serve', b'--cmdserver', b'unix', b'-a', sockpath]
70 if repopath:
69 if repopath:
71 cmdline += [b'-R', repopath]
70 cmdline += [b'-R', repopath]
72 if logpath:
71 if logpath:
73 stdout = open(logpath, 'a')
72 stdout = open(logpath, 'a')
74 stderr = subprocess.STDOUT
73 stderr = subprocess.STDOUT
75 else:
74 else:
76 stdout = stderr = None
75 stdout = stderr = None
77 self.server = subprocess.Popen(cmdline, stdout=stdout, stderr=stderr)
76 self.server = subprocess.Popen(cmdline, stdout=stdout, stderr=stderr)
78 # wait for listen()
77 # wait for listen()
79 while self.server.poll() is None:
78 while self.server.poll() is None:
80 if os.path.exists(sockpath):
79 if os.path.exists(sockpath):
81 break
80 break
82 time.sleep(0.1)
81 time.sleep(0.1)
83
82
84 def connect(self):
83 def connect(self):
85 return unixconnection(self.sockpath)
84 return unixconnection(self.sockpath)
86
85
87 def shutdown(self):
86 def shutdown(self):
88 os.kill(self.server.pid, signal.SIGTERM)
87 os.kill(self.server.pid, signal.SIGTERM)
89 self.server.wait()
88 self.server.wait()
90
89
91
90
92 def writeblock(server, data):
91 def writeblock(server, data):
93 server.stdin.write(struct.pack(b'>I', len(data)))
92 server.stdin.write(struct.pack(b'>I', len(data)))
94 server.stdin.write(data)
93 server.stdin.write(data)
95 server.stdin.flush()
94 server.stdin.flush()
96
95
97
96
98 def readchannel(server):
97 def readchannel(server):
99 data = server.stdout.read(5)
98 data = server.stdout.read(5)
100 if not data:
99 if not data:
101 raise EOFError
100 raise EOFError
102 channel, length = struct.unpack('>cI', data)
101 channel, length = struct.unpack('>cI', data)
103 if channel in b'IL':
102 if channel in b'IL':
104 return channel, length
103 return channel, length
105 else:
104 else:
106 return channel, server.stdout.read(length)
105 return channel, server.stdout.read(length)
107
106
108
107
109 def sep(text):
108 def sep(text):
110 return text.replace(b'\\', b'/')
109 return text.replace(b'\\', b'/')
111
110
112
111
113 def runcommand(
112 def runcommand(
114 server, args, output=stdout, error=stderr, input=None, outfilter=lambda x: x
113 server, args, output=stdout, error=stderr, input=None, outfilter=lambda x: x
115 ):
114 ):
116 bprint(b'*** runcommand', b' '.join(args))
115 bprint(b'*** runcommand', b' '.join(args))
117 stdout.flush()
116 stdout.flush()
118 server.stdin.write(b'runcommand\n')
117 server.stdin.write(b'runcommand\n')
119 writeblock(server, b'\0'.join(args))
118 writeblock(server, b'\0'.join(args))
120
119
121 if not input:
120 if not input:
122 input = stringio()
121 input = stringio()
123
122
124 while True:
123 while True:
125 ch, data = readchannel(server)
124 ch, data = readchannel(server)
126 if ch == b'o':
125 if ch == b'o':
127 output.write(outfilter(data))
126 output.write(outfilter(data))
128 output.flush()
127 output.flush()
129 elif ch == b'e':
128 elif ch == b'e':
130 error.write(data)
129 error.write(data)
131 error.flush()
130 error.flush()
132 elif ch == b'I':
131 elif ch == b'I':
133 writeblock(server, input.read(data))
132 writeblock(server, input.read(data))
134 elif ch == b'L':
133 elif ch == b'L':
135 writeblock(server, input.readline(data))
134 writeblock(server, input.readline(data))
136 elif ch == b'm':
135 elif ch == b'm':
137 bprint(b"message: %r" % data)
136 bprint(b"message: %r" % data)
138 elif ch == b'r':
137 elif ch == b'r':
139 (ret,) = struct.unpack('>i', data)
138 (ret,) = struct.unpack('>i', data)
140 if ret != 0:
139 if ret != 0:
141 bprint(b' [%d]' % ret)
140 bprint(b' [%d]' % ret)
142 return ret
141 return ret
143 else:
142 else:
144 bprint(b"unexpected channel %c: %r" % (ch, data))
143 bprint(b"unexpected channel %c: %r" % (ch, data))
145 if ch.isupper():
144 if ch.isupper():
146 return
145 return
147
146
148
147
149 def check(func, connect=connectpipe):
148 def check(func, connect=connectpipe):
150 stdout.flush()
149 stdout.flush()
151 server = connect()
150 server = connect()
152 try:
151 try:
153 return func(server)
152 return func(server)
154 finally:
153 finally:
155 server.stdin.close()
154 server.stdin.close()
156 server.wait()
155 server.wait()
157
156
158
157
159 def checkwith(connect=connectpipe, **kwargs):
158 def checkwith(connect=connectpipe, **kwargs):
160 def wrap(func):
159 def wrap(func):
161 return check(func, lambda: connect(**kwargs))
160 return check(func, lambda: connect(**kwargs))
162
161
163 return wrap
162 return wrap
@@ -1,771 +1,770 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2
2
3 from __future__ import absolute_import, print_function
4
3
5 import ast
4 import ast
6 import collections
5 import collections
7 import io
6 import io
8 import os
7 import os
9 import sys
8 import sys
10
9
11 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
10 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
12 # to work when run from a virtualenv. The modules were chosen empirically
11 # to work when run from a virtualenv. The modules were chosen empirically
13 # so that the return value matches the return value without virtualenv.
12 # so that the return value matches the return value without virtualenv.
14 if True: # disable lexical sorting checks
13 if True: # disable lexical sorting checks
15 try:
14 try:
16 import BaseHTTPServer as basehttpserver
15 import BaseHTTPServer as basehttpserver
17 except ImportError:
16 except ImportError:
18 basehttpserver = None
17 basehttpserver = None
19 import zlib
18 import zlib
20
19
21 import testparseutil
20 import testparseutil
22
21
23 # Whitelist of modules that symbols can be directly imported from.
22 # Whitelist of modules that symbols can be directly imported from.
24 allowsymbolimports = (
23 allowsymbolimports = (
25 '__future__',
24 '__future__',
26 'breezy',
25 'breezy',
27 'concurrent',
26 'concurrent',
28 'hgclient',
27 'hgclient',
29 'mercurial',
28 'mercurial',
30 'mercurial.hgweb.common',
29 'mercurial.hgweb.common',
31 'mercurial.hgweb.request',
30 'mercurial.hgweb.request',
32 'mercurial.i18n',
31 'mercurial.i18n',
33 'mercurial.interfaces',
32 'mercurial.interfaces',
34 'mercurial.node',
33 'mercurial.node',
35 'mercurial.pycompat',
34 'mercurial.pycompat',
36 # for revlog to re-export constant to extensions
35 # for revlog to re-export constant to extensions
37 'mercurial.revlogutils.constants',
36 'mercurial.revlogutils.constants',
38 'mercurial.revlogutils.flagutil',
37 'mercurial.revlogutils.flagutil',
39 # for cffi modules to re-export pure functions
38 # for cffi modules to re-export pure functions
40 'mercurial.pure.base85',
39 'mercurial.pure.base85',
41 'mercurial.pure.bdiff',
40 'mercurial.pure.bdiff',
42 'mercurial.pure.mpatch',
41 'mercurial.pure.mpatch',
43 'mercurial.pure.osutil',
42 'mercurial.pure.osutil',
44 'mercurial.pure.parsers',
43 'mercurial.pure.parsers',
45 # third-party imports should be directly imported
44 # third-party imports should be directly imported
46 'mercurial.thirdparty',
45 'mercurial.thirdparty',
47 'mercurial.thirdparty.attr',
46 'mercurial.thirdparty.attr',
48 'mercurial.thirdparty.zope',
47 'mercurial.thirdparty.zope',
49 'mercurial.thirdparty.zope.interface',
48 'mercurial.thirdparty.zope.interface',
50 )
49 )
51
50
52 # Whitelist of symbols that can be directly imported.
51 # Whitelist of symbols that can be directly imported.
53 directsymbols = ('demandimport',)
52 directsymbols = ('demandimport',)
54
53
55 # Modules that must be aliased because they are commonly confused with
54 # Modules that must be aliased because they are commonly confused with
56 # common variables and can create aliasing and readability issues.
55 # common variables and can create aliasing and readability issues.
57 requirealias = {
56 requirealias = {
58 'ui': 'uimod',
57 'ui': 'uimod',
59 }
58 }
60
59
61
60
62 def walklocal(root):
61 def walklocal(root):
63 """Recursively yield all descendant nodes but not in a different scope"""
62 """Recursively yield all descendant nodes but not in a different scope"""
64 todo = collections.deque(ast.iter_child_nodes(root))
63 todo = collections.deque(ast.iter_child_nodes(root))
65 yield root, False
64 yield root, False
66 while todo:
65 while todo:
67 node = todo.popleft()
66 node = todo.popleft()
68 newscope = isinstance(node, ast.FunctionDef)
67 newscope = isinstance(node, ast.FunctionDef)
69 if not newscope:
68 if not newscope:
70 todo.extend(ast.iter_child_nodes(node))
69 todo.extend(ast.iter_child_nodes(node))
71 yield node, newscope
70 yield node, newscope
72
71
73
72
74 def dotted_name_of_path(path):
73 def dotted_name_of_path(path):
75 """Given a relative path to a source file, return its dotted module name.
74 """Given a relative path to a source file, return its dotted module name.
76
75
77 >>> dotted_name_of_path('mercurial/error.py')
76 >>> dotted_name_of_path('mercurial/error.py')
78 'mercurial.error'
77 'mercurial.error'
79 >>> dotted_name_of_path('zlibmodule.so')
78 >>> dotted_name_of_path('zlibmodule.so')
80 'zlib'
79 'zlib'
81 """
80 """
82 parts = path.replace(os.sep, '/').split('/')
81 parts = path.replace(os.sep, '/').split('/')
83 parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so
82 parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so
84 if parts[-1].endswith('module'):
83 if parts[-1].endswith('module'):
85 parts[-1] = parts[-1][:-6]
84 parts[-1] = parts[-1][:-6]
86 return '.'.join(parts)
85 return '.'.join(parts)
87
86
88
87
89 def fromlocalfunc(modulename, localmods):
88 def fromlocalfunc(modulename, localmods):
90 """Get a function to examine which locally defined module the
89 """Get a function to examine which locally defined module the
91 target source imports via a specified name.
90 target source imports via a specified name.
92
91
93 `modulename` is an `dotted_name_of_path()`-ed source file path,
92 `modulename` is an `dotted_name_of_path()`-ed source file path,
94 which may have `.__init__` at the end of it, of the target source.
93 which may have `.__init__` at the end of it, of the target source.
95
94
96 `localmods` is a set of absolute `dotted_name_of_path()`-ed source file
95 `localmods` is a set of absolute `dotted_name_of_path()`-ed source file
97 paths of locally defined (= Mercurial specific) modules.
96 paths of locally defined (= Mercurial specific) modules.
98
97
99 This function assumes that module names not existing in
98 This function assumes that module names not existing in
100 `localmods` are from the Python standard library.
99 `localmods` are from the Python standard library.
101
100
102 This function returns the function, which takes `name` argument,
101 This function returns the function, which takes `name` argument,
103 and returns `(absname, dottedpath, hassubmod)` tuple if `name`
102 and returns `(absname, dottedpath, hassubmod)` tuple if `name`
104 matches against locally defined module. Otherwise, it returns
103 matches against locally defined module. Otherwise, it returns
105 False.
104 False.
106
105
107 It is assumed that `name` doesn't have `.__init__`.
106 It is assumed that `name` doesn't have `.__init__`.
108
107
109 `absname` is an absolute module name of specified `name`
108 `absname` is an absolute module name of specified `name`
110 (e.g. "hgext.convert"). This can be used to compose prefix for sub
109 (e.g. "hgext.convert"). This can be used to compose prefix for sub
111 modules or so.
110 modules or so.
112
111
113 `dottedpath` is a `dotted_name_of_path()`-ed source file path
112 `dottedpath` is a `dotted_name_of_path()`-ed source file path
114 (e.g. "hgext.convert.__init__") of `name`. This is used to look
113 (e.g. "hgext.convert.__init__") of `name`. This is used to look
115 module up in `localmods` again.
114 module up in `localmods` again.
116
115
117 `hassubmod` is whether it may have sub modules under it (for
116 `hassubmod` is whether it may have sub modules under it (for
118 convenient, even though this is also equivalent to "absname !=
117 convenient, even though this is also equivalent to "absname !=
119 dottednpath")
118 dottednpath")
120
119
121 >>> localmods = {'foo.__init__', 'foo.foo1',
120 >>> localmods = {'foo.__init__', 'foo.foo1',
122 ... 'foo.bar.__init__', 'foo.bar.bar1',
121 ... 'foo.bar.__init__', 'foo.bar.bar1',
123 ... 'baz.__init__', 'baz.baz1'}
122 ... 'baz.__init__', 'baz.baz1'}
124 >>> fromlocal = fromlocalfunc('foo.xxx', localmods)
123 >>> fromlocal = fromlocalfunc('foo.xxx', localmods)
125 >>> # relative
124 >>> # relative
126 >>> fromlocal('foo1')
125 >>> fromlocal('foo1')
127 ('foo.foo1', 'foo.foo1', False)
126 ('foo.foo1', 'foo.foo1', False)
128 >>> fromlocal('bar')
127 >>> fromlocal('bar')
129 ('foo.bar', 'foo.bar.__init__', True)
128 ('foo.bar', 'foo.bar.__init__', True)
130 >>> fromlocal('bar.bar1')
129 >>> fromlocal('bar.bar1')
131 ('foo.bar.bar1', 'foo.bar.bar1', False)
130 ('foo.bar.bar1', 'foo.bar.bar1', False)
132 >>> # absolute
131 >>> # absolute
133 >>> fromlocal('baz')
132 >>> fromlocal('baz')
134 ('baz', 'baz.__init__', True)
133 ('baz', 'baz.__init__', True)
135 >>> fromlocal('baz.baz1')
134 >>> fromlocal('baz.baz1')
136 ('baz.baz1', 'baz.baz1', False)
135 ('baz.baz1', 'baz.baz1', False)
137 >>> # unknown = maybe standard library
136 >>> # unknown = maybe standard library
138 >>> fromlocal('os')
137 >>> fromlocal('os')
139 False
138 False
140 >>> fromlocal(None, 1)
139 >>> fromlocal(None, 1)
141 ('foo', 'foo.__init__', True)
140 ('foo', 'foo.__init__', True)
142 >>> fromlocal('foo1', 1)
141 >>> fromlocal('foo1', 1)
143 ('foo.foo1', 'foo.foo1', False)
142 ('foo.foo1', 'foo.foo1', False)
144 >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods)
143 >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods)
145 >>> fromlocal2(None, 2)
144 >>> fromlocal2(None, 2)
146 ('foo', 'foo.__init__', True)
145 ('foo', 'foo.__init__', True)
147 >>> fromlocal2('bar2', 1)
146 >>> fromlocal2('bar2', 1)
148 False
147 False
149 >>> fromlocal2('bar', 2)
148 >>> fromlocal2('bar', 2)
150 ('foo.bar', 'foo.bar.__init__', True)
149 ('foo.bar', 'foo.bar.__init__', True)
151 """
150 """
152 if not isinstance(modulename, str):
151 if not isinstance(modulename, str):
153 modulename = modulename.decode('ascii')
152 modulename = modulename.decode('ascii')
154 prefix = '.'.join(modulename.split('.')[:-1])
153 prefix = '.'.join(modulename.split('.')[:-1])
155 if prefix:
154 if prefix:
156 prefix += '.'
155 prefix += '.'
157
156
158 def fromlocal(name, level=0):
157 def fromlocal(name, level=0):
159 # name is false value when relative imports are used.
158 # name is false value when relative imports are used.
160 if not name:
159 if not name:
161 # If relative imports are used, level must not be absolute.
160 # If relative imports are used, level must not be absolute.
162 assert level > 0
161 assert level > 0
163 candidates = ['.'.join(modulename.split('.')[:-level])]
162 candidates = ['.'.join(modulename.split('.')[:-level])]
164 else:
163 else:
165 if not level:
164 if not level:
166 # Check relative name first.
165 # Check relative name first.
167 candidates = [prefix + name, name]
166 candidates = [prefix + name, name]
168 else:
167 else:
169 candidates = [
168 candidates = [
170 '.'.join(modulename.split('.')[:-level]) + '.' + name
169 '.'.join(modulename.split('.')[:-level]) + '.' + name
171 ]
170 ]
172
171
173 for n in candidates:
172 for n in candidates:
174 if n in localmods:
173 if n in localmods:
175 return (n, n, False)
174 return (n, n, False)
176 dottedpath = n + '.__init__'
175 dottedpath = n + '.__init__'
177 if dottedpath in localmods:
176 if dottedpath in localmods:
178 return (n, dottedpath, True)
177 return (n, dottedpath, True)
179 return False
178 return False
180
179
181 return fromlocal
180 return fromlocal
182
181
183
182
184 def populateextmods(localmods):
183 def populateextmods(localmods):
185 """Populate C extension modules based on pure modules"""
184 """Populate C extension modules based on pure modules"""
186 newlocalmods = set(localmods)
185 newlocalmods = set(localmods)
187 for n in localmods:
186 for n in localmods:
188 if n.startswith('mercurial.pure.'):
187 if n.startswith('mercurial.pure.'):
189 m = n[len('mercurial.pure.') :]
188 m = n[len('mercurial.pure.') :]
190 newlocalmods.add('mercurial.cext.' + m)
189 newlocalmods.add('mercurial.cext.' + m)
191 newlocalmods.add('mercurial.cffi._' + m)
190 newlocalmods.add('mercurial.cffi._' + m)
192 return newlocalmods
191 return newlocalmods
193
192
194
193
195 def list_stdlib_modules():
194 def list_stdlib_modules():
196 """List the modules present in the stdlib.
195 """List the modules present in the stdlib.
197
196
198 >>> py3 = sys.version_info[0] >= 3
197 >>> py3 = sys.version_info[0] >= 3
199 >>> mods = set(list_stdlib_modules())
198 >>> mods = set(list_stdlib_modules())
200 >>> 'BaseHTTPServer' in mods or py3
199 >>> 'BaseHTTPServer' in mods or py3
201 True
200 True
202
201
203 os.path isn't really a module, so it's missing:
202 os.path isn't really a module, so it's missing:
204
203
205 >>> 'os.path' in mods
204 >>> 'os.path' in mods
206 False
205 False
207
206
208 sys requires special treatment, because it's baked into the
207 sys requires special treatment, because it's baked into the
209 interpreter, but it should still appear:
208 interpreter, but it should still appear:
210
209
211 >>> 'sys' in mods
210 >>> 'sys' in mods
212 True
211 True
213
212
214 >>> 'collections' in mods
213 >>> 'collections' in mods
215 True
214 True
216
215
217 >>> 'cStringIO' in mods or py3
216 >>> 'cStringIO' in mods or py3
218 True
217 True
219
218
220 >>> 'cffi' in mods
219 >>> 'cffi' in mods
221 True
220 True
222 """
221 """
223 for m in sys.builtin_module_names:
222 for m in sys.builtin_module_names:
224 yield m
223 yield m
225 # These modules only exist on windows, but we should always
224 # These modules only exist on windows, but we should always
226 # consider them stdlib.
225 # consider them stdlib.
227 for m in ['msvcrt', '_winreg']:
226 for m in ['msvcrt', '_winreg']:
228 yield m
227 yield m
229 yield '__builtin__'
228 yield '__builtin__'
230 yield 'builtins' # python3 only
229 yield 'builtins' # python3 only
231 yield 'importlib.abc' # python3 only
230 yield 'importlib.abc' # python3 only
232 yield 'importlib.machinery' # python3 only
231 yield 'importlib.machinery' # python3 only
233 yield 'importlib.util' # python3 only
232 yield 'importlib.util' # python3 only
234 for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only
233 for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only
235 yield m
234 yield m
236 for m in 'cPickle', 'datetime': # in Python (not C) on PyPy
235 for m in 'cPickle', 'datetime': # in Python (not C) on PyPy
237 yield m
236 yield m
238 for m in ['cffi']:
237 for m in ['cffi']:
239 yield m
238 yield m
240 stdlib_prefixes = {sys.prefix, sys.exec_prefix}
239 stdlib_prefixes = {sys.prefix, sys.exec_prefix}
241 # We need to supplement the list of prefixes for the search to work
240 # We need to supplement the list of prefixes for the search to work
242 # when run from within a virtualenv.
241 # when run from within a virtualenv.
243 for mod in (basehttpserver, zlib):
242 for mod in (basehttpserver, zlib):
244 if mod is None:
243 if mod is None:
245 continue
244 continue
246 try:
245 try:
247 # Not all module objects have a __file__ attribute.
246 # Not all module objects have a __file__ attribute.
248 filename = mod.__file__
247 filename = mod.__file__
249 except AttributeError:
248 except AttributeError:
250 continue
249 continue
251 dirname = os.path.dirname(filename)
250 dirname = os.path.dirname(filename)
252 for prefix in stdlib_prefixes:
251 for prefix in stdlib_prefixes:
253 if dirname.startswith(prefix):
252 if dirname.startswith(prefix):
254 # Then this directory is redundant.
253 # Then this directory is redundant.
255 break
254 break
256 else:
255 else:
257 stdlib_prefixes.add(dirname)
256 stdlib_prefixes.add(dirname)
258 sourceroot = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
257 sourceroot = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
259 for libpath in sys.path:
258 for libpath in sys.path:
260 # We want to walk everything in sys.path that starts with something in
259 # We want to walk everything in sys.path that starts with something in
261 # stdlib_prefixes, but not directories from the hg sources.
260 # stdlib_prefixes, but not directories from the hg sources.
262 if os.path.abspath(libpath).startswith(sourceroot) or not any(
261 if os.path.abspath(libpath).startswith(sourceroot) or not any(
263 libpath.startswith(p) for p in stdlib_prefixes
262 libpath.startswith(p) for p in stdlib_prefixes
264 ):
263 ):
265 continue
264 continue
266 for top, dirs, files in os.walk(libpath):
265 for top, dirs, files in os.walk(libpath):
267 if 'dist-packages' in top.split(os.path.sep):
266 if 'dist-packages' in top.split(os.path.sep):
268 continue
267 continue
269 for i, d in reversed(list(enumerate(dirs))):
268 for i, d in reversed(list(enumerate(dirs))):
270 if (
269 if (
271 not os.path.exists(os.path.join(top, d, '__init__.py'))
270 not os.path.exists(os.path.join(top, d, '__init__.py'))
272 or top == libpath
271 or top == libpath
273 and d in ('hgdemandimport', 'hgext', 'mercurial')
272 and d in ('hgdemandimport', 'hgext', 'mercurial')
274 ):
273 ):
275 del dirs[i]
274 del dirs[i]
276 for name in files:
275 for name in files:
277 if not name.endswith(('.py', '.so', '.pyc', '.pyo', '.pyd')):
276 if not name.endswith(('.py', '.so', '.pyc', '.pyo', '.pyd')):
278 continue
277 continue
279 if name.startswith('__init__.py'):
278 if name.startswith('__init__.py'):
280 full_path = top
279 full_path = top
281 else:
280 else:
282 full_path = os.path.join(top, name)
281 full_path = os.path.join(top, name)
283 rel_path = full_path[len(libpath) + 1 :]
282 rel_path = full_path[len(libpath) + 1 :]
284 mod = dotted_name_of_path(rel_path)
283 mod = dotted_name_of_path(rel_path)
285 yield mod
284 yield mod
286
285
287
286
288 stdlib_modules = set(list_stdlib_modules())
287 stdlib_modules = set(list_stdlib_modules())
289
288
290
289
291 def imported_modules(source, modulename, f, localmods, ignore_nested=False):
290 def imported_modules(source, modulename, f, localmods, ignore_nested=False):
292 """Given the source of a file as a string, yield the names
291 """Given the source of a file as a string, yield the names
293 imported by that file.
292 imported by that file.
294
293
295 Args:
294 Args:
296 source: The python source to examine as a string.
295 source: The python source to examine as a string.
297 modulename: of specified python source (may have `__init__`)
296 modulename: of specified python source (may have `__init__`)
298 localmods: set of locally defined module names (may have `__init__`)
297 localmods: set of locally defined module names (may have `__init__`)
299 ignore_nested: If true, import statements that do not start in
298 ignore_nested: If true, import statements that do not start in
300 column zero will be ignored.
299 column zero will be ignored.
301
300
302 Returns:
301 Returns:
303 A list of absolute module names imported by the given source.
302 A list of absolute module names imported by the given source.
304
303
305 >>> f = 'foo/xxx.py'
304 >>> f = 'foo/xxx.py'
306 >>> modulename = 'foo.xxx'
305 >>> modulename = 'foo.xxx'
307 >>> localmods = {'foo.__init__': True,
306 >>> localmods = {'foo.__init__': True,
308 ... 'foo.foo1': True, 'foo.foo2': True,
307 ... 'foo.foo1': True, 'foo.foo2': True,
309 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
308 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
310 ... 'baz.__init__': True, 'baz.baz1': True }
309 ... 'baz.__init__': True, 'baz.baz1': True }
311 >>> # standard library (= not locally defined ones)
310 >>> # standard library (= not locally defined ones)
312 >>> sorted(imported_modules(
311 >>> sorted(imported_modules(
313 ... 'from stdlib1 import foo, bar; import stdlib2',
312 ... 'from stdlib1 import foo, bar; import stdlib2',
314 ... modulename, f, localmods))
313 ... modulename, f, localmods))
315 []
314 []
316 >>> # relative importing
315 >>> # relative importing
317 >>> sorted(imported_modules(
316 >>> sorted(imported_modules(
318 ... 'import foo1; from bar import bar1',
317 ... 'import foo1; from bar import bar1',
319 ... modulename, f, localmods))
318 ... modulename, f, localmods))
320 ['foo.bar.bar1', 'foo.foo1']
319 ['foo.bar.bar1', 'foo.foo1']
321 >>> sorted(imported_modules(
320 >>> sorted(imported_modules(
322 ... 'from bar.bar1 import name1, name2, name3',
321 ... 'from bar.bar1 import name1, name2, name3',
323 ... modulename, f, localmods))
322 ... modulename, f, localmods))
324 ['foo.bar.bar1']
323 ['foo.bar.bar1']
325 >>> # absolute importing
324 >>> # absolute importing
326 >>> sorted(imported_modules(
325 >>> sorted(imported_modules(
327 ... 'from baz import baz1, name1',
326 ... 'from baz import baz1, name1',
328 ... modulename, f, localmods))
327 ... modulename, f, localmods))
329 ['baz.__init__', 'baz.baz1']
328 ['baz.__init__', 'baz.baz1']
330 >>> # mixed importing, even though it shouldn't be recommended
329 >>> # mixed importing, even though it shouldn't be recommended
331 >>> sorted(imported_modules(
330 >>> sorted(imported_modules(
332 ... 'import stdlib, foo1, baz',
331 ... 'import stdlib, foo1, baz',
333 ... modulename, f, localmods))
332 ... modulename, f, localmods))
334 ['baz.__init__', 'foo.foo1']
333 ['baz.__init__', 'foo.foo1']
335 >>> # ignore_nested
334 >>> # ignore_nested
336 >>> sorted(imported_modules(
335 >>> sorted(imported_modules(
337 ... '''import foo
336 ... '''import foo
338 ... def wat():
337 ... def wat():
339 ... import bar
338 ... import bar
340 ... ''', modulename, f, localmods))
339 ... ''', modulename, f, localmods))
341 ['foo.__init__', 'foo.bar.__init__']
340 ['foo.__init__', 'foo.bar.__init__']
342 >>> sorted(imported_modules(
341 >>> sorted(imported_modules(
343 ... '''import foo
342 ... '''import foo
344 ... def wat():
343 ... def wat():
345 ... import bar
344 ... import bar
346 ... ''', modulename, f, localmods, ignore_nested=True))
345 ... ''', modulename, f, localmods, ignore_nested=True))
347 ['foo.__init__']
346 ['foo.__init__']
348 """
347 """
349 fromlocal = fromlocalfunc(modulename, localmods)
348 fromlocal = fromlocalfunc(modulename, localmods)
350 for node in ast.walk(ast.parse(source, f)):
349 for node in ast.walk(ast.parse(source, f)):
351 if ignore_nested and getattr(node, 'col_offset', 0) > 0:
350 if ignore_nested and getattr(node, 'col_offset', 0) > 0:
352 continue
351 continue
353 if isinstance(node, ast.Import):
352 if isinstance(node, ast.Import):
354 for n in node.names:
353 for n in node.names:
355 found = fromlocal(n.name)
354 found = fromlocal(n.name)
356 if not found:
355 if not found:
357 # this should import standard library
356 # this should import standard library
358 continue
357 continue
359 yield found[1]
358 yield found[1]
360 elif isinstance(node, ast.ImportFrom):
359 elif isinstance(node, ast.ImportFrom):
361 found = fromlocal(node.module, node.level)
360 found = fromlocal(node.module, node.level)
362 if not found:
361 if not found:
363 # this should import standard library
362 # this should import standard library
364 continue
363 continue
365
364
366 absname, dottedpath, hassubmod = found
365 absname, dottedpath, hassubmod = found
367 if not hassubmod:
366 if not hassubmod:
368 # "dottedpath" is not a package; must be imported
367 # "dottedpath" is not a package; must be imported
369 yield dottedpath
368 yield dottedpath
370 # examination of "node.names" should be redundant
369 # examination of "node.names" should be redundant
371 # e.g.: from mercurial.node import nullid, nullrev
370 # e.g.: from mercurial.node import nullid, nullrev
372 continue
371 continue
373
372
374 modnotfound = False
373 modnotfound = False
375 prefix = absname + '.'
374 prefix = absname + '.'
376 for n in node.names:
375 for n in node.names:
377 found = fromlocal(prefix + n.name)
376 found = fromlocal(prefix + n.name)
378 if not found:
377 if not found:
379 # this should be a function or a property of "node.module"
378 # this should be a function or a property of "node.module"
380 modnotfound = True
379 modnotfound = True
381 continue
380 continue
382 yield found[1]
381 yield found[1]
383 if modnotfound and dottedpath != modulename:
382 if modnotfound and dottedpath != modulename:
384 # "dottedpath" is a package, but imported because of non-module
383 # "dottedpath" is a package, but imported because of non-module
385 # lookup
384 # lookup
386 # specifically allow "from . import foo" from __init__.py
385 # specifically allow "from . import foo" from __init__.py
387 yield dottedpath
386 yield dottedpath
388
387
389
388
390 def verify_import_convention(module, source, localmods):
389 def verify_import_convention(module, source, localmods):
391 """Verify imports match our established coding convention."""
390 """Verify imports match our established coding convention."""
392 root = ast.parse(source)
391 root = ast.parse(source)
393
392
394 return verify_modern_convention(module, root, localmods)
393 return verify_modern_convention(module, root, localmods)
395
394
396
395
397 def verify_modern_convention(module, root, localmods, root_col_offset=0):
396 def verify_modern_convention(module, root, localmods, root_col_offset=0):
398 """Verify a file conforms to the modern import convention rules.
397 """Verify a file conforms to the modern import convention rules.
399
398
400 The rules of the modern convention are:
399 The rules of the modern convention are:
401
400
402 * Ordering is stdlib followed by local imports. Each group is lexically
401 * Ordering is stdlib followed by local imports. Each group is lexically
403 sorted.
402 sorted.
404 * Importing multiple modules via "import X, Y" is not allowed: use
403 * Importing multiple modules via "import X, Y" is not allowed: use
405 separate import statements.
404 separate import statements.
406 * Importing multiple modules via "from X import ..." is allowed if using
405 * Importing multiple modules via "from X import ..." is allowed if using
407 parenthesis and one entry per line.
406 parenthesis and one entry per line.
408 * Only 1 relative import statement per import level ("from .", "from ..")
407 * Only 1 relative import statement per import level ("from .", "from ..")
409 is allowed.
408 is allowed.
410 * Relative imports from higher levels must occur before lower levels. e.g.
409 * Relative imports from higher levels must occur before lower levels. e.g.
411 "from .." must be before "from .".
410 "from .." must be before "from .".
412 * Imports from peer packages should use relative import (e.g. do not
411 * Imports from peer packages should use relative import (e.g. do not
413 "import mercurial.foo" from a "mercurial.*" module).
412 "import mercurial.foo" from a "mercurial.*" module).
414 * Symbols can only be imported from specific modules (see
413 * Symbols can only be imported from specific modules (see
415 `allowsymbolimports`). For other modules, first import the module then
414 `allowsymbolimports`). For other modules, first import the module then
416 assign the symbol to a module-level variable. In addition, these imports
415 assign the symbol to a module-level variable. In addition, these imports
417 must be performed before other local imports. This rule only
416 must be performed before other local imports. This rule only
418 applies to import statements outside of any blocks.
417 applies to import statements outside of any blocks.
419 * Relative imports from the standard library are not allowed, unless that
418 * Relative imports from the standard library are not allowed, unless that
420 library is also a local module.
419 library is also a local module.
421 * Certain modules must be aliased to alternate names to avoid aliasing
420 * Certain modules must be aliased to alternate names to avoid aliasing
422 and readability problems. See `requirealias`.
421 and readability problems. See `requirealias`.
423 """
422 """
424 if not isinstance(module, str):
423 if not isinstance(module, str):
425 module = module.decode('ascii')
424 module = module.decode('ascii')
426 topmodule = module.split('.')[0]
425 topmodule = module.split('.')[0]
427 fromlocal = fromlocalfunc(module, localmods)
426 fromlocal = fromlocalfunc(module, localmods)
428
427
429 # Whether a local/non-stdlib import has been performed.
428 # Whether a local/non-stdlib import has been performed.
430 seenlocal = None
429 seenlocal = None
431 # Whether a local/non-stdlib, non-symbol import has been seen.
430 # Whether a local/non-stdlib, non-symbol import has been seen.
432 seennonsymbollocal = False
431 seennonsymbollocal = False
433 # The last name to be imported (for sorting).
432 # The last name to be imported (for sorting).
434 lastname = None
433 lastname = None
435 laststdlib = None
434 laststdlib = None
436 # Relative import levels encountered so far.
435 # Relative import levels encountered so far.
437 seenlevels = set()
436 seenlevels = set()
438
437
439 for node, newscope in walklocal(root):
438 for node, newscope in walklocal(root):
440
439
441 def msg(fmt, *args):
440 def msg(fmt, *args):
442 return (fmt % args, node.lineno)
441 return (fmt % args, node.lineno)
443
442
444 if newscope:
443 if newscope:
445 # Check for local imports in function
444 # Check for local imports in function
446 for r in verify_modern_convention(
445 for r in verify_modern_convention(
447 module, node, localmods, node.col_offset + 4
446 module, node, localmods, node.col_offset + 4
448 ):
447 ):
449 yield r
448 yield r
450 elif isinstance(node, ast.Import):
449 elif isinstance(node, ast.Import):
451 # Disallow "import foo, bar" and require separate imports
450 # Disallow "import foo, bar" and require separate imports
452 # for each module.
451 # for each module.
453 if len(node.names) > 1:
452 if len(node.names) > 1:
454 yield msg(
453 yield msg(
455 'multiple imported names: %s',
454 'multiple imported names: %s',
456 ', '.join(n.name for n in node.names),
455 ', '.join(n.name for n in node.names),
457 )
456 )
458
457
459 name = node.names[0].name
458 name = node.names[0].name
460 asname = node.names[0].asname
459 asname = node.names[0].asname
461
460
462 stdlib = name in stdlib_modules
461 stdlib = name in stdlib_modules
463
462
464 # Ignore sorting rules on imports inside blocks.
463 # Ignore sorting rules on imports inside blocks.
465 if node.col_offset == root_col_offset:
464 if node.col_offset == root_col_offset:
466 if lastname and name < lastname and laststdlib == stdlib:
465 if lastname and name < lastname and laststdlib == stdlib:
467 yield msg(
466 yield msg(
468 'imports not lexically sorted: %s < %s', name, lastname
467 'imports not lexically sorted: %s < %s', name, lastname
469 )
468 )
470
469
471 lastname = name
470 lastname = name
472 laststdlib = stdlib
471 laststdlib = stdlib
473
472
474 # stdlib imports should be before local imports.
473 # stdlib imports should be before local imports.
475 if stdlib and seenlocal and node.col_offset == root_col_offset:
474 if stdlib and seenlocal and node.col_offset == root_col_offset:
476 yield msg(
475 yield msg(
477 'stdlib import "%s" follows local import: %s',
476 'stdlib import "%s" follows local import: %s',
478 name,
477 name,
479 seenlocal,
478 seenlocal,
480 )
479 )
481
480
482 if not stdlib:
481 if not stdlib:
483 seenlocal = name
482 seenlocal = name
484
483
485 # Import of sibling modules should use relative imports.
484 # Import of sibling modules should use relative imports.
486 topname = name.split('.')[0]
485 topname = name.split('.')[0]
487 if topname == topmodule:
486 if topname == topmodule:
488 yield msg('import should be relative: %s', name)
487 yield msg('import should be relative: %s', name)
489
488
490 if name in requirealias and asname != requirealias[name]:
489 if name in requirealias and asname != requirealias[name]:
491 yield msg(
490 yield msg(
492 '%s module must be "as" aliased to %s',
491 '%s module must be "as" aliased to %s',
493 name,
492 name,
494 requirealias[name],
493 requirealias[name],
495 )
494 )
496
495
497 elif isinstance(node, ast.ImportFrom):
496 elif isinstance(node, ast.ImportFrom):
498 # Resolve the full imported module name.
497 # Resolve the full imported module name.
499 if node.level > 0:
498 if node.level > 0:
500 fullname = '.'.join(module.split('.')[: -node.level])
499 fullname = '.'.join(module.split('.')[: -node.level])
501 if node.module:
500 if node.module:
502 fullname += '.%s' % node.module
501 fullname += '.%s' % node.module
503 else:
502 else:
504 assert node.module
503 assert node.module
505 fullname = node.module
504 fullname = node.module
506
505
507 topname = fullname.split('.')[0]
506 topname = fullname.split('.')[0]
508 if topname == topmodule:
507 if topname == topmodule:
509 yield msg('import should be relative: %s', fullname)
508 yield msg('import should be relative: %s', fullname)
510
509
511 # __future__ is special since it needs to come first and use
510 # __future__ is special since it needs to come first and use
512 # symbol import.
511 # symbol import.
513 if fullname != '__future__':
512 if fullname != '__future__':
514 if not fullname or (
513 if not fullname or (
515 fullname in stdlib_modules
514 fullname in stdlib_modules
516 # allow standard 'from typing import ...' style
515 # allow standard 'from typing import ...' style
517 and fullname.startswith('.')
516 and fullname.startswith('.')
518 and fullname not in localmods
517 and fullname not in localmods
519 and fullname + '.__init__' not in localmods
518 and fullname + '.__init__' not in localmods
520 ):
519 ):
521 yield msg('relative import of stdlib module')
520 yield msg('relative import of stdlib module')
522 else:
521 else:
523 seenlocal = fullname
522 seenlocal = fullname
524
523
525 # Direct symbol import is only allowed from certain modules and
524 # Direct symbol import is only allowed from certain modules and
526 # must occur before non-symbol imports.
525 # must occur before non-symbol imports.
527 found = fromlocal(node.module, node.level)
526 found = fromlocal(node.module, node.level)
528 if found and found[2]: # node.module is a package
527 if found and found[2]: # node.module is a package
529 prefix = found[0] + '.'
528 prefix = found[0] + '.'
530 symbols = (
529 symbols = (
531 n.name for n in node.names if not fromlocal(prefix + n.name)
530 n.name for n in node.names if not fromlocal(prefix + n.name)
532 )
531 )
533 else:
532 else:
534 symbols = (n.name for n in node.names)
533 symbols = (n.name for n in node.names)
535 symbols = [sym for sym in symbols if sym not in directsymbols]
534 symbols = [sym for sym in symbols if sym not in directsymbols]
536 if node.module and node.col_offset == root_col_offset:
535 if node.module and node.col_offset == root_col_offset:
537 if symbols and fullname not in allowsymbolimports:
536 if symbols and fullname not in allowsymbolimports:
538 yield msg(
537 yield msg(
539 'direct symbol import %s from %s',
538 'direct symbol import %s from %s',
540 ', '.join(symbols),
539 ', '.join(symbols),
541 fullname,
540 fullname,
542 )
541 )
543
542
544 if symbols and seennonsymbollocal:
543 if symbols and seennonsymbollocal:
545 yield msg(
544 yield msg(
546 'symbol import follows non-symbol import: %s', fullname
545 'symbol import follows non-symbol import: %s', fullname
547 )
546 )
548 if not symbols and fullname not in stdlib_modules:
547 if not symbols and fullname not in stdlib_modules:
549 seennonsymbollocal = True
548 seennonsymbollocal = True
550
549
551 if not node.module:
550 if not node.module:
552 assert node.level
551 assert node.level
553
552
554 # Only allow 1 group per level.
553 # Only allow 1 group per level.
555 if (
554 if (
556 node.level in seenlevels
555 node.level in seenlevels
557 and node.col_offset == root_col_offset
556 and node.col_offset == root_col_offset
558 ):
557 ):
559 yield msg(
558 yield msg(
560 'multiple "from %s import" statements', '.' * node.level
559 'multiple "from %s import" statements', '.' * node.level
561 )
560 )
562
561
563 # Higher-level groups come before lower-level groups.
562 # Higher-level groups come before lower-level groups.
564 if any(node.level > l for l in seenlevels):
563 if any(node.level > l for l in seenlevels):
565 yield msg(
564 yield msg(
566 'higher-level import should come first: %s', fullname
565 'higher-level import should come first: %s', fullname
567 )
566 )
568
567
569 seenlevels.add(node.level)
568 seenlevels.add(node.level)
570
569
571 # Entries in "from .X import ( ... )" lists must be lexically
570 # Entries in "from .X import ( ... )" lists must be lexically
572 # sorted.
571 # sorted.
573 lastentryname = None
572 lastentryname = None
574
573
575 for n in node.names:
574 for n in node.names:
576 if lastentryname and n.name < lastentryname:
575 if lastentryname and n.name < lastentryname:
577 yield msg(
576 yield msg(
578 'imports from %s not lexically sorted: %s < %s',
577 'imports from %s not lexically sorted: %s < %s',
579 fullname,
578 fullname,
580 n.name,
579 n.name,
581 lastentryname,
580 lastentryname,
582 )
581 )
583
582
584 lastentryname = n.name
583 lastentryname = n.name
585
584
586 if n.name in requirealias and n.asname != requirealias[n.name]:
585 if n.name in requirealias and n.asname != requirealias[n.name]:
587 yield msg(
586 yield msg(
588 '%s from %s must be "as" aliased to %s',
587 '%s from %s must be "as" aliased to %s',
589 n.name,
588 n.name,
590 fullname,
589 fullname,
591 requirealias[n.name],
590 requirealias[n.name],
592 )
591 )
593
592
594
593
595 class CircularImport(Exception):
594 class CircularImport(Exception):
596 pass
595 pass
597
596
598
597
599 def checkmod(mod, imports):
598 def checkmod(mod, imports):
600 shortest = {}
599 shortest = {}
601 visit = [[mod]]
600 visit = [[mod]]
602 while visit:
601 while visit:
603 path = visit.pop(0)
602 path = visit.pop(0)
604 for i in sorted(imports.get(path[-1], [])):
603 for i in sorted(imports.get(path[-1], [])):
605 if len(path) < shortest.get(i, 1000):
604 if len(path) < shortest.get(i, 1000):
606 shortest[i] = len(path)
605 shortest[i] = len(path)
607 if i in path:
606 if i in path:
608 if i == path[0]:
607 if i == path[0]:
609 raise CircularImport(path)
608 raise CircularImport(path)
610 continue
609 continue
611 visit.append(path + [i])
610 visit.append(path + [i])
612
611
613
612
614 def rotatecycle(cycle):
613 def rotatecycle(cycle):
615 """arrange a cycle so that the lexicographically first module listed first
614 """arrange a cycle so that the lexicographically first module listed first
616
615
617 >>> rotatecycle(['foo', 'bar'])
616 >>> rotatecycle(['foo', 'bar'])
618 ['bar', 'foo', 'bar']
617 ['bar', 'foo', 'bar']
619 """
618 """
620 lowest = min(cycle)
619 lowest = min(cycle)
621 idx = cycle.index(lowest)
620 idx = cycle.index(lowest)
622 return cycle[idx:] + cycle[:idx] + [lowest]
621 return cycle[idx:] + cycle[:idx] + [lowest]
623
622
624
623
625 def find_cycles(imports):
624 def find_cycles(imports):
626 """Find cycles in an already-loaded import graph.
625 """Find cycles in an already-loaded import graph.
627
626
628 All module names recorded in `imports` should be absolute one.
627 All module names recorded in `imports` should be absolute one.
629
628
630 >>> from __future__ import print_function
629 >>> from __future__ import print_function
631 >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'],
630 >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'],
632 ... 'top.bar': ['top.baz', 'sys'],
631 ... 'top.bar': ['top.baz', 'sys'],
633 ... 'top.baz': ['top.foo'],
632 ... 'top.baz': ['top.foo'],
634 ... 'top.qux': ['top.foo']}
633 ... 'top.qux': ['top.foo']}
635 >>> print('\\n'.join(sorted(find_cycles(imports))))
634 >>> print('\\n'.join(sorted(find_cycles(imports))))
636 top.bar -> top.baz -> top.foo -> top.bar
635 top.bar -> top.baz -> top.foo -> top.bar
637 top.foo -> top.qux -> top.foo
636 top.foo -> top.qux -> top.foo
638 """
637 """
639 cycles = set()
638 cycles = set()
640 for mod in sorted(imports.keys()):
639 for mod in sorted(imports.keys()):
641 try:
640 try:
642 checkmod(mod, imports)
641 checkmod(mod, imports)
643 except CircularImport as e:
642 except CircularImport as e:
644 cycle = e.args[0]
643 cycle = e.args[0]
645 cycles.add(" -> ".join(rotatecycle(cycle)))
644 cycles.add(" -> ".join(rotatecycle(cycle)))
646 return cycles
645 return cycles
647
646
648
647
649 def _cycle_sortkey(c):
648 def _cycle_sortkey(c):
650 return len(c), c
649 return len(c), c
651
650
652
651
653 def embedded(f, modname, src):
652 def embedded(f, modname, src):
654 """Extract embedded python code
653 """Extract embedded python code
655
654
656 >>> def _forcestr(thing):
655 >>> def _forcestr(thing):
657 ... if not isinstance(thing, str):
656 ... if not isinstance(thing, str):
658 ... return thing.decode('ascii')
657 ... return thing.decode('ascii')
659 ... return thing
658 ... return thing
660 >>> def test(fn, lines):
659 >>> def test(fn, lines):
661 ... for s, m, f, l in embedded(fn, b"example", lines):
660 ... for s, m, f, l in embedded(fn, b"example", lines):
662 ... print("%s %s %d" % (_forcestr(m), _forcestr(f), l))
661 ... print("%s %s %d" % (_forcestr(m), _forcestr(f), l))
663 ... print(repr(_forcestr(s)))
662 ... print(repr(_forcestr(s)))
664 >>> lines = [
663 >>> lines = [
665 ... 'comment',
664 ... 'comment',
666 ... ' >>> from __future__ import print_function',
665 ... ' >>> from __future__ import print_function',
667 ... " >>> ' multiline",
666 ... " >>> ' multiline",
668 ... " ... string'",
667 ... " ... string'",
669 ... ' ',
668 ... ' ',
670 ... 'comment',
669 ... 'comment',
671 ... ' $ cat > foo.py <<EOF',
670 ... ' $ cat > foo.py <<EOF',
672 ... ' > from __future__ import print_function',
671 ... ' > from __future__ import print_function',
673 ... ' > EOF',
672 ... ' > EOF',
674 ... ]
673 ... ]
675 >>> test(b"example.t", lines)
674 >>> test(b"example.t", lines)
676 example[2] doctest.py 1
675 example[2] doctest.py 1
677 "from __future__ import print_function\\n' multiline\\nstring'\\n\\n"
676 "from __future__ import print_function\\n' multiline\\nstring'\\n\\n"
678 example[8] foo.py 7
677 example[8] foo.py 7
679 'from __future__ import print_function\\n'
678 'from __future__ import print_function\\n'
680 """
679 """
681 errors = []
680 errors = []
682 for name, starts, ends, code in testparseutil.pyembedded(f, src, errors):
681 for name, starts, ends, code in testparseutil.pyembedded(f, src, errors):
683 if not name:
682 if not name:
684 # use 'doctest.py', in order to make already existing
683 # use 'doctest.py', in order to make already existing
685 # doctest above pass instantly
684 # doctest above pass instantly
686 name = 'doctest.py'
685 name = 'doctest.py'
687 # "starts" is "line number" (1-origin), but embedded() is
686 # "starts" is "line number" (1-origin), but embedded() is
688 # expected to return "line offset" (0-origin). Therefore, this
687 # expected to return "line offset" (0-origin). Therefore, this
689 # yields "starts - 1".
688 # yields "starts - 1".
690 if not isinstance(modname, str):
689 if not isinstance(modname, str):
691 modname = modname.decode('utf8')
690 modname = modname.decode('utf8')
692 yield code, "%s[%d]" % (modname, starts), name, starts - 1
691 yield code, "%s[%d]" % (modname, starts), name, starts - 1
693
692
694
693
695 def sources(f, modname):
694 def sources(f, modname):
696 """Yields possibly multiple sources from a filepath
695 """Yields possibly multiple sources from a filepath
697
696
698 input: filepath, modulename
697 input: filepath, modulename
699 yields: script(string), modulename, filepath, linenumber
698 yields: script(string), modulename, filepath, linenumber
700
699
701 For embedded scripts, the modulename and filepath will be different
700 For embedded scripts, the modulename and filepath will be different
702 from the function arguments. linenumber is an offset relative to
701 from the function arguments. linenumber is an offset relative to
703 the input file.
702 the input file.
704 """
703 """
705 py = False
704 py = False
706 if not f.endswith('.t'):
705 if not f.endswith('.t'):
707 with open(f, 'rb') as src:
706 with open(f, 'rb') as src:
708 yield src.read(), modname, f, 0
707 yield src.read(), modname, f, 0
709 py = True
708 py = True
710 if py or f.endswith('.t'):
709 if py or f.endswith('.t'):
711 # Strictly speaking we should sniff for the magic header that denotes
710 # Strictly speaking we should sniff for the magic header that denotes
712 # Python source file encoding. But in reality we don't use anything
711 # Python source file encoding. But in reality we don't use anything
713 # other than ASCII (mainly) and UTF-8 (in a few exceptions), so
712 # other than ASCII (mainly) and UTF-8 (in a few exceptions), so
714 # simplicity is fine.
713 # simplicity is fine.
715 with io.open(f, 'r', encoding='utf-8') as src:
714 with io.open(f, 'r', encoding='utf-8') as src:
716 for script, modname, t, line in embedded(f, modname, src):
715 for script, modname, t, line in embedded(f, modname, src):
717 yield script, modname.encode('utf8'), t, line
716 yield script, modname.encode('utf8'), t, line
718
717
719
718
720 def main(argv):
719 def main(argv):
721 if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
720 if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
722 print('Usage: %s {-|file [file] [file] ...}')
721 print('Usage: %s {-|file [file] [file] ...}')
723 return 1
722 return 1
724 if argv[1] == '-':
723 if argv[1] == '-':
725 argv = argv[:1]
724 argv = argv[:1]
726 argv.extend(l.rstrip() for l in sys.stdin.readlines())
725 argv.extend(l.rstrip() for l in sys.stdin.readlines())
727 localmodpaths = {}
726 localmodpaths = {}
728 used_imports = {}
727 used_imports = {}
729 any_errors = False
728 any_errors = False
730 for source_path in argv[1:]:
729 for source_path in argv[1:]:
731 modname = dotted_name_of_path(source_path)
730 modname = dotted_name_of_path(source_path)
732 localmodpaths[modname] = source_path
731 localmodpaths[modname] = source_path
733 localmods = populateextmods(localmodpaths)
732 localmods = populateextmods(localmodpaths)
734 for localmodname, source_path in sorted(localmodpaths.items()):
733 for localmodname, source_path in sorted(localmodpaths.items()):
735 if not isinstance(localmodname, bytes):
734 if not isinstance(localmodname, bytes):
736 # This is only safe because all hg's files are ascii
735 # This is only safe because all hg's files are ascii
737 localmodname = localmodname.encode('ascii')
736 localmodname = localmodname.encode('ascii')
738 for src, modname, name, line in sources(source_path, localmodname):
737 for src, modname, name, line in sources(source_path, localmodname):
739 try:
738 try:
740 used_imports[modname] = sorted(
739 used_imports[modname] = sorted(
741 imported_modules(
740 imported_modules(
742 src, modname, name, localmods, ignore_nested=True
741 src, modname, name, localmods, ignore_nested=True
743 )
742 )
744 )
743 )
745 for error, lineno in verify_import_convention(
744 for error, lineno in verify_import_convention(
746 modname, src, localmods
745 modname, src, localmods
747 ):
746 ):
748 any_errors = True
747 any_errors = True
749 print('%s:%d: %s' % (source_path, lineno + line, error))
748 print('%s:%d: %s' % (source_path, lineno + line, error))
750 except SyntaxError as e:
749 except SyntaxError as e:
751 print(
750 print(
752 '%s:%d: SyntaxError: %s' % (source_path, e.lineno + line, e)
751 '%s:%d: SyntaxError: %s' % (source_path, e.lineno + line, e)
753 )
752 )
754 cycles = find_cycles(used_imports)
753 cycles = find_cycles(used_imports)
755 if cycles:
754 if cycles:
756 firstmods = set()
755 firstmods = set()
757 for c in sorted(cycles, key=_cycle_sortkey):
756 for c in sorted(cycles, key=_cycle_sortkey):
758 first = c.split()[0]
757 first = c.split()[0]
759 # As a rough cut, ignore any cycle that starts with the
758 # As a rough cut, ignore any cycle that starts with the
760 # same module as some other cycle. Otherwise we see lots
759 # same module as some other cycle. Otherwise we see lots
761 # of cycles that are effectively duplicates.
760 # of cycles that are effectively duplicates.
762 if first in firstmods:
761 if first in firstmods:
763 continue
762 continue
764 print('Import cycle:', c)
763 print('Import cycle:', c)
765 firstmods.add(first)
764 firstmods.add(first)
766 any_errors = True
765 any_errors = True
767 return any_errors != 0
766 return any_errors != 0
768
767
769
768
770 if __name__ == '__main__':
769 if __name__ == '__main__':
771 sys.exit(int(main(sys.argv)))
770 sys.exit(int(main(sys.argv)))
@@ -1,37 +1,35 b''
1 # memory.py - track memory usage
1 # memory.py - track memory usage
2 #
2 #
3 # Copyright 2009 Olivia Mackall <olivia@selenic.com> and others
3 # Copyright 2009 Olivia Mackall <olivia@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''helper extension to measure memory usage
8 '''helper extension to measure memory usage
9
9
10 Reads current and peak memory usage from ``/proc/self/status`` and
10 Reads current and peak memory usage from ``/proc/self/status`` and
11 prints it to ``stderr`` on exit.
11 prints it to ``stderr`` on exit.
12 '''
12 '''
13
13
14 from __future__ import absolute_import
15
16
14
17 def memusage(ui):
15 def memusage(ui):
18 """Report memory usage of the current process."""
16 """Report memory usage of the current process."""
19 result = {'peak': 0, 'rss': 0}
17 result = {'peak': 0, 'rss': 0}
20 with open('/proc/self/status', 'r') as status:
18 with open('/proc/self/status', 'r') as status:
21 # This will only work on systems with a /proc file system
19 # This will only work on systems with a /proc file system
22 # (like Linux).
20 # (like Linux).
23 for line in status:
21 for line in status:
24 parts = line.split()
22 parts = line.split()
25 key = parts[0][2:-1].lower()
23 key = parts[0][2:-1].lower()
26 if key in result:
24 if key in result:
27 result[key] = int(parts[1])
25 result[key] = int(parts[1])
28 ui.write_err(
26 ui.write_err(
29 ", ".join(
27 ", ".join(
30 ["%s: %.1f MiB" % (k, v / 1024.0) for k, v in result.iteritems()]
28 ["%s: %.1f MiB" % (k, v / 1024.0) for k, v in result.iteritems()]
31 )
29 )
32 + "\n"
30 + "\n"
33 )
31 )
34
32
35
33
36 def extsetup(ui):
34 def extsetup(ui):
37 ui.atexit(memusage, ui)
35 ui.atexit(memusage, ui)
@@ -1,126 +1,125 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 #
2 #
3 # Copyright 2018 Paul Morelle <Paul.Morelle@octobus.net>
3 # Copyright 2018 Paul Morelle <Paul.Morelle@octobus.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 #
7 #
8 # This script use the output of `hg perfrevlogwrite -T json --details` to draw
8 # This script use the output of `hg perfrevlogwrite -T json --details` to draw
9 # various plot related to write performance in a revlog
9 # various plot related to write performance in a revlog
10 #
10 #
11 # usage: perf-revlog-write-plot.py details.json
11 # usage: perf-revlog-write-plot.py details.json
12 from __future__ import absolute_import, print_function
13 import json
12 import json
14 import re
13 import re
15
14
16 import numpy as np
15 import numpy as np
17 import scipy.signal
16 import scipy.signal
18
17
19 from matplotlib import (
18 from matplotlib import (
20 pyplot as plt,
19 pyplot as plt,
21 ticker as mticker,
20 ticker as mticker,
22 )
21 )
23
22
24
23
25 def plot(data, title=None):
24 def plot(data, title=None):
26 items = {}
25 items = {}
27 re_title = re.compile(r'^revisions #\d+ of \d+, rev (\d+)$')
26 re_title = re.compile(r'^revisions #\d+ of \d+, rev (\d+)$')
28 for item in data:
27 for item in data:
29 m = re_title.match(item['title'])
28 m = re_title.match(item['title'])
30 if m is None:
29 if m is None:
31 continue
30 continue
32
31
33 rev = int(m.group(1))
32 rev = int(m.group(1))
34 items[rev] = item
33 items[rev] = item
35
34
36 min_rev = min(items.keys())
35 min_rev = min(items.keys())
37 max_rev = max(items.keys())
36 max_rev = max(items.keys())
38 ary = np.empty((2, max_rev - min_rev + 1))
37 ary = np.empty((2, max_rev - min_rev + 1))
39 for rev, item in items.items():
38 for rev, item in items.items():
40 ary[0][rev - min_rev] = rev
39 ary[0][rev - min_rev] = rev
41 ary[1][rev - min_rev] = item['wall']
40 ary[1][rev - min_rev] = item['wall']
42
41
43 fig = plt.figure()
42 fig = plt.figure()
44 comb_plt = fig.add_subplot(211)
43 comb_plt = fig.add_subplot(211)
45 other_plt = fig.add_subplot(212)
44 other_plt = fig.add_subplot(212)
46
45
47 comb_plt.plot(
46 comb_plt.plot(
48 ary[0], np.cumsum(ary[1]), color='red', linewidth=1, label='comb'
47 ary[0], np.cumsum(ary[1]), color='red', linewidth=1, label='comb'
49 )
48 )
50
49
51 plots = []
50 plots = []
52 p = other_plt.plot(ary[0], ary[1], color='red', linewidth=1, label='wall')
51 p = other_plt.plot(ary[0], ary[1], color='red', linewidth=1, label='wall')
53 plots.append(p)
52 plots.append(p)
54
53
55 colors = {
54 colors = {
56 10: ('green', 'xkcd:grass green'),
55 10: ('green', 'xkcd:grass green'),
57 100: ('blue', 'xkcd:bright blue'),
56 100: ('blue', 'xkcd:bright blue'),
58 1000: ('purple', 'xkcd:dark pink'),
57 1000: ('purple', 'xkcd:dark pink'),
59 }
58 }
60 for n, color in colors.items():
59 for n, color in colors.items():
61 avg_n = np.convolve(ary[1], np.full(n, 1.0 / n), 'valid')
60 avg_n = np.convolve(ary[1], np.full(n, 1.0 / n), 'valid')
62 p = other_plt.plot(
61 p = other_plt.plot(
63 ary[0][n - 1 :],
62 ary[0][n - 1 :],
64 avg_n,
63 avg_n,
65 color=color[0],
64 color=color[0],
66 linewidth=1,
65 linewidth=1,
67 label='avg time last %d' % n,
66 label='avg time last %d' % n,
68 )
67 )
69 plots.append(p)
68 plots.append(p)
70
69
71 med_n = scipy.signal.medfilt(ary[1], n + 1)
70 med_n = scipy.signal.medfilt(ary[1], n + 1)
72 p = other_plt.plot(
71 p = other_plt.plot(
73 ary[0],
72 ary[0],
74 med_n,
73 med_n,
75 color=color[1],
74 color=color[1],
76 linewidth=1,
75 linewidth=1,
77 label='median time last %d' % n,
76 label='median time last %d' % n,
78 )
77 )
79 plots.append(p)
78 plots.append(p)
80
79
81 formatter = mticker.ScalarFormatter()
80 formatter = mticker.ScalarFormatter()
82 formatter.set_scientific(False)
81 formatter.set_scientific(False)
83 formatter.set_useOffset(False)
82 formatter.set_useOffset(False)
84
83
85 comb_plt.grid()
84 comb_plt.grid()
86 comb_plt.xaxis.set_major_formatter(formatter)
85 comb_plt.xaxis.set_major_formatter(formatter)
87 comb_plt.legend()
86 comb_plt.legend()
88
87
89 other_plt.grid()
88 other_plt.grid()
90 other_plt.xaxis.set_major_formatter(formatter)
89 other_plt.xaxis.set_major_formatter(formatter)
91 leg = other_plt.legend()
90 leg = other_plt.legend()
92 leg2plot = {}
91 leg2plot = {}
93 for legline, plot in zip(leg.get_lines(), plots):
92 for legline, plot in zip(leg.get_lines(), plots):
94 legline.set_picker(5)
93 legline.set_picker(5)
95 leg2plot[legline] = plot
94 leg2plot[legline] = plot
96
95
97 def onpick(event):
96 def onpick(event):
98 legline = event.artist
97 legline = event.artist
99 plot = leg2plot[legline]
98 plot = leg2plot[legline]
100 visible = not plot[0].get_visible()
99 visible = not plot[0].get_visible()
101 for l in plot:
100 for l in plot:
102 l.set_visible(visible)
101 l.set_visible(visible)
103
102
104 if visible:
103 if visible:
105 legline.set_alpha(1.0)
104 legline.set_alpha(1.0)
106 else:
105 else:
107 legline.set_alpha(0.2)
106 legline.set_alpha(0.2)
108 fig.canvas.draw()
107 fig.canvas.draw()
109
108
110 if title is not None:
109 if title is not None:
111 fig.canvas.set_window_title(title)
110 fig.canvas.set_window_title(title)
112 fig.canvas.mpl_connect('pick_event', onpick)
111 fig.canvas.mpl_connect('pick_event', onpick)
113
112
114 plt.show()
113 plt.show()
115
114
116
115
117 if __name__ == '__main__':
116 if __name__ == '__main__':
118 import sys
117 import sys
119
118
120 if len(sys.argv) > 1:
119 if len(sys.argv) > 1:
121 print('reading from %r' % sys.argv[1])
120 print('reading from %r' % sys.argv[1])
122 with open(sys.argv[1], 'r') as fp:
121 with open(sys.argv[1], 'r') as fp:
123 plot(json.load(fp), title=sys.argv[1])
122 plot(json.load(fp), title=sys.argv[1])
124 else:
123 else:
125 print('reading from stdin')
124 print('reading from stdin')
126 plot(json.load(sys.stdin))
125 plot(json.load(sys.stdin))
@@ -1,198 +1,197 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 # Search for interesting discovery instance
2 # Search for interesting discovery instance
3 #
3 #
4 # search-discovery-case REPO [REPO]…
4 # search-discovery-case REPO [REPO]…
5 #
5 #
6 # This use a subsetmaker extension (next to this script) to generate a steam of
6 # This use a subsetmaker extension (next to this script) to generate a steam of
7 # random discovery instance. When interesting case are discovered, information
7 # random discovery instance. When interesting case are discovered, information
8 # about them are print on the stdout.
8 # about them are print on the stdout.
9 from __future__ import print_function
10
9
11 import json
10 import json
12 import os
11 import os
13 import queue
12 import queue
14 import random
13 import random
15 import signal
14 import signal
16 import subprocess
15 import subprocess
17 import sys
16 import sys
18 import threading
17 import threading
19
18
20 this_script = os.path.abspath(sys.argv[0])
19 this_script = os.path.abspath(sys.argv[0])
21 this_dir = os.path.dirname(this_script)
20 this_dir = os.path.dirname(this_script)
22 hg_dir = os.path.join(this_dir, '..', '..')
21 hg_dir = os.path.join(this_dir, '..', '..')
23 HG_REPO = os.path.normpath(hg_dir)
22 HG_REPO = os.path.normpath(hg_dir)
24 HG_BIN = os.path.join(HG_REPO, 'hg')
23 HG_BIN = os.path.join(HG_REPO, 'hg')
25
24
26 JOB = int(os.environ.get('NUMBER_OF_PROCESSORS', 8))
25 JOB = int(os.environ.get('NUMBER_OF_PROCESSORS', 8))
27
26
28
27
29 SLICING = ('scratch', 'randomantichain', 'rev')
28 SLICING = ('scratch', 'randomantichain', 'rev')
30
29
31
30
32 def nb_revs(repo_path):
31 def nb_revs(repo_path):
33 cmd = [
32 cmd = [
34 HG_BIN,
33 HG_BIN,
35 '--repository',
34 '--repository',
36 repo_path,
35 repo_path,
37 'log',
36 'log',
38 '--template',
37 '--template',
39 '{rev}',
38 '{rev}',
40 '--rev',
39 '--rev',
41 'tip',
40 'tip',
42 ]
41 ]
43 s = subprocess.Popen(cmd, stdout=subprocess.PIPE)
42 s = subprocess.Popen(cmd, stdout=subprocess.PIPE)
44 out, err = s.communicate()
43 out, err = s.communicate()
45 return int(out)
44 return int(out)
46
45
47
46
48 repos = []
47 repos = []
49 for repo in sys.argv[1:]:
48 for repo in sys.argv[1:]:
50 size = nb_revs(repo)
49 size = nb_revs(repo)
51 repos.append((repo, size))
50 repos.append((repo, size))
52
51
53
52
54 def pick_one(repo):
53 def pick_one(repo):
55 pick = random.choice(SLICING)
54 pick = random.choice(SLICING)
56 seed = random.randint(0, 100000)
55 seed = random.randint(0, 100000)
57 if pick == 'scratch':
56 if pick == 'scratch':
58 start = int(repo[1] * 0.3)
57 start = int(repo[1] * 0.3)
59 end = int(repo[1] * 0.7)
58 end = int(repo[1] * 0.7)
60 nb = random.randint(start, end)
59 nb = random.randint(start, end)
61 return ('scratch', nb, seed)
60 return ('scratch', nb, seed)
62 elif pick == 'randomantichain':
61 elif pick == 'randomantichain':
63 return ('randomantichain', seed)
62 return ('randomantichain', seed)
64 elif pick == 'rev':
63 elif pick == 'rev':
65 start = int(repo[1] * 0.3)
64 start = int(repo[1] * 0.3)
66 end = int(repo[1])
65 end = int(repo[1])
67 rev = random.randint(start, end)
66 rev = random.randint(start, end)
68 return ('rev', rev)
67 return ('rev', rev)
69 else:
68 else:
70 assert False
69 assert False
71
70
72
71
73 done = threading.Event()
72 done = threading.Event()
74 cases = queue.Queue(maxsize=10 * JOB)
73 cases = queue.Queue(maxsize=10 * JOB)
75 results = queue.Queue()
74 results = queue.Queue()
76
75
77
76
78 def worker():
77 def worker():
79 while not done.is_set():
78 while not done.is_set():
80 c = cases.get()
79 c = cases.get()
81 if c is None:
80 if c is None:
82 return
81 return
83 try:
82 try:
84 res = process(c)
83 res = process(c)
85 results.put((c, res))
84 results.put((c, res))
86 except Exception as exc:
85 except Exception as exc:
87 print('processing-failed: %s %s' % (c, exc), file=sys.stderr)
86 print('processing-failed: %s %s' % (c, exc), file=sys.stderr)
88 c = (c[0], c[2], c[1])
87 c = (c[0], c[2], c[1])
89 try:
88 try:
90 res = process(c)
89 res = process(c)
91 results.put((c, res))
90 results.put((c, res))
92 except Exception as exc:
91 except Exception as exc:
93 print('processing-failed: %s %s' % (c, exc), file=sys.stderr)
92 print('processing-failed: %s %s' % (c, exc), file=sys.stderr)
94
93
95
94
96 SUBSET_PATH = os.path.join(HG_REPO, 'contrib', 'perf-utils', 'subsetmaker.py')
95 SUBSET_PATH = os.path.join(HG_REPO, 'contrib', 'perf-utils', 'subsetmaker.py')
97
96
98
97
99 CMD_BASE = (
98 CMD_BASE = (
100 HG_BIN,
99 HG_BIN,
101 'debugdiscovery',
100 'debugdiscovery',
102 '--template',
101 '--template',
103 'json',
102 'json',
104 '--config',
103 '--config',
105 'extensions.subset=%s' % SUBSET_PATH,
104 'extensions.subset=%s' % SUBSET_PATH,
106 )
105 )
107 # '--local-as-revs "$left" --local-as-revs "$right"'
106 # '--local-as-revs "$left" --local-as-revs "$right"'
108 # > /data/discovery-references/results/disco-mozilla-unified-$1-$2.txt
107 # > /data/discovery-references/results/disco-mozilla-unified-$1-$2.txt
109 # )
108 # )
110
109
111
110
112 def to_revsets(case):
111 def to_revsets(case):
113 t = case[0]
112 t = case[0]
114 if t == 'scratch':
113 if t == 'scratch':
115 return 'not scratch(all(), %d, "%d")' % (case[1], case[2])
114 return 'not scratch(all(), %d, "%d")' % (case[1], case[2])
116 elif t == 'randomantichain':
115 elif t == 'randomantichain':
117 return '::randomantichain(all(), "%d")' % case[1]
116 return '::randomantichain(all(), "%d")' % case[1]
118 elif t == 'rev':
117 elif t == 'rev':
119 return '::%d' % case[1]
118 return '::%d' % case[1]
120 else:
119 else:
121 assert False
120 assert False
122
121
123
122
124 def process(case):
123 def process(case):
125 (repo, left, right) = case
124 (repo, left, right) = case
126 cmd = list(CMD_BASE)
125 cmd = list(CMD_BASE)
127 cmd.append('-R')
126 cmd.append('-R')
128 cmd.append(repo[0])
127 cmd.append(repo[0])
129 cmd.append('--local-as-revs')
128 cmd.append('--local-as-revs')
130 cmd.append(to_revsets(left))
129 cmd.append(to_revsets(left))
131 cmd.append('--remote-as-revs')
130 cmd.append('--remote-as-revs')
132 cmd.append(to_revsets(right))
131 cmd.append(to_revsets(right))
133 s = subprocess.Popen(cmd, stdout=subprocess.PIPE)
132 s = subprocess.Popen(cmd, stdout=subprocess.PIPE)
134 out, err = s.communicate()
133 out, err = s.communicate()
135 return json.loads(out)[0]
134 return json.loads(out)[0]
136
135
137
136
138 def interesting_boundary(res):
137 def interesting_boundary(res):
139 """check if a case is interesting or not
138 """check if a case is interesting or not
140
139
141 For now we are mostly interrested in case were we do multiple roundstrip
140 For now we are mostly interrested in case were we do multiple roundstrip
142 and where the boundary is somewhere in the middle of the undecided set.
141 and where the boundary is somewhere in the middle of the undecided set.
143
142
144 Ideally, we would make this configurable, but this is not a focus for now
143 Ideally, we would make this configurable, but this is not a focus for now
145
144
146 return None or (round-trip, undecided-common, undecided-missing)
145 return None or (round-trip, undecided-common, undecided-missing)
147 """
146 """
148 roundtrips = res["total-roundtrips"]
147 roundtrips = res["total-roundtrips"]
149 if roundtrips <= 1:
148 if roundtrips <= 1:
150 return None
149 return None
151 undecided_common = res["nb-ini_und-common"]
150 undecided_common = res["nb-ini_und-common"]
152 undecided_missing = res["nb-ini_und-missing"]
151 undecided_missing = res["nb-ini_und-missing"]
153 if undecided_common == 0:
152 if undecided_common == 0:
154 return None
153 return None
155 if undecided_missing == 0:
154 if undecided_missing == 0:
156 return None
155 return None
157 return (roundtrips, undecided_common, undecided_missing)
156 return (roundtrips, undecided_common, undecided_missing)
158
157
159
158
160 def end(*args, **kwargs):
159 def end(*args, **kwargs):
161 done.set()
160 done.set()
162
161
163
162
164 def format_case(case):
163 def format_case(case):
165 return '-'.join(str(s) for s in case)
164 return '-'.join(str(s) for s in case)
166
165
167
166
168 signal.signal(signal.SIGINT, end)
167 signal.signal(signal.SIGINT, end)
169
168
170 for i in range(JOB):
169 for i in range(JOB):
171 threading.Thread(target=worker).start()
170 threading.Thread(target=worker).start()
172
171
173 nb_cases = 0
172 nb_cases = 0
174 while not done.is_set():
173 while not done.is_set():
175 repo = random.choice(repos)
174 repo = random.choice(repos)
176 left = pick_one(repo)
175 left = pick_one(repo)
177 right = pick_one(repo)
176 right = pick_one(repo)
178 cases.put((repo, left, right))
177 cases.put((repo, left, right))
179 while not results.empty():
178 while not results.empty():
180 # results has a single reader so this is fine
179 # results has a single reader so this is fine
181 c, res = results.get_nowait()
180 c, res = results.get_nowait()
182 boundary = interesting_boundary(res)
181 boundary = interesting_boundary(res)
183 if boundary is not None:
182 if boundary is not None:
184 print(c[0][0], format_case(c[1]), format_case(c[2]), *boundary)
183 print(c[0][0], format_case(c[1]), format_case(c[2]), *boundary)
185 sys.stdout.flush()
184 sys.stdout.flush()
186
185
187 nb_cases += 1
186 nb_cases += 1
188 if not nb_cases % 100:
187 if not nb_cases % 100:
189 print('[%d cases generated]' % nb_cases, file=sys.stderr)
188 print('[%d cases generated]' % nb_cases, file=sys.stderr)
190
189
191 for i in range(JOB):
190 for i in range(JOB):
192 try:
191 try:
193 cases.put_nowait(None)
192 cases.put_nowait(None)
194 except queue.Full:
193 except queue.Full:
195 pass
194 pass
196
195
197 print('[%d cases generated]' % nb_cases, file=sys.stderr)
196 print('[%d cases generated]' % nb_cases, file=sys.stderr)
198 print('[ouput generation is over]' % nb_cases, file=sys.stderr)
197 print('[ouput generation is over]' % nb_cases, file=sys.stderr)
@@ -1,3980 +1,3979 b''
1 # perf.py - performance test routines
1 # perf.py - performance test routines
2 '''helper extension to measure performance
2 '''helper extension to measure performance
3
3
4 Configurations
4 Configurations
5 ==============
5 ==============
6
6
7 ``perf``
7 ``perf``
8 --------
8 --------
9
9
10 ``all-timing``
10 ``all-timing``
11 When set, additional statistics will be reported for each benchmark: best,
11 When set, additional statistics will be reported for each benchmark: best,
12 worst, median average. If not set only the best timing is reported
12 worst, median average. If not set only the best timing is reported
13 (default: off).
13 (default: off).
14
14
15 ``presleep``
15 ``presleep``
16 number of second to wait before any group of runs (default: 1)
16 number of second to wait before any group of runs (default: 1)
17
17
18 ``pre-run``
18 ``pre-run``
19 number of run to perform before starting measurement.
19 number of run to perform before starting measurement.
20
20
21 ``profile-benchmark``
21 ``profile-benchmark``
22 Enable profiling for the benchmarked section.
22 Enable profiling for the benchmarked section.
23 (The first iteration is benchmarked)
23 (The first iteration is benchmarked)
24
24
25 ``run-limits``
25 ``run-limits``
26 Control the number of runs each benchmark will perform. The option value
26 Control the number of runs each benchmark will perform. The option value
27 should be a list of `<time>-<numberofrun>` pairs. After each run the
27 should be a list of `<time>-<numberofrun>` pairs. After each run the
28 conditions are considered in order with the following logic:
28 conditions are considered in order with the following logic:
29
29
30 If benchmark has been running for <time> seconds, and we have performed
30 If benchmark has been running for <time> seconds, and we have performed
31 <numberofrun> iterations, stop the benchmark,
31 <numberofrun> iterations, stop the benchmark,
32
32
33 The default value is: `3.0-100, 10.0-3`
33 The default value is: `3.0-100, 10.0-3`
34
34
35 ``stub``
35 ``stub``
36 When set, benchmarks will only be run once, useful for testing
36 When set, benchmarks will only be run once, useful for testing
37 (default: off)
37 (default: off)
38 '''
38 '''
39
39
40 # "historical portability" policy of perf.py:
40 # "historical portability" policy of perf.py:
41 #
41 #
42 # We have to do:
42 # We have to do:
43 # - make perf.py "loadable" with as wide Mercurial version as possible
43 # - make perf.py "loadable" with as wide Mercurial version as possible
44 # This doesn't mean that perf commands work correctly with that Mercurial.
44 # This doesn't mean that perf commands work correctly with that Mercurial.
45 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
45 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
46 # - make historical perf command work correctly with as wide Mercurial
46 # - make historical perf command work correctly with as wide Mercurial
47 # version as possible
47 # version as possible
48 #
48 #
49 # We have to do, if possible with reasonable cost:
49 # We have to do, if possible with reasonable cost:
50 # - make recent perf command for historical feature work correctly
50 # - make recent perf command for historical feature work correctly
51 # with early Mercurial
51 # with early Mercurial
52 #
52 #
53 # We don't have to do:
53 # We don't have to do:
54 # - make perf command for recent feature work correctly with early
54 # - make perf command for recent feature work correctly with early
55 # Mercurial
55 # Mercurial
56
56
57 from __future__ import absolute_import
58 import contextlib
57 import contextlib
59 import functools
58 import functools
60 import gc
59 import gc
61 import os
60 import os
62 import random
61 import random
63 import shutil
62 import shutil
64 import struct
63 import struct
65 import sys
64 import sys
66 import tempfile
65 import tempfile
67 import threading
66 import threading
68 import time
67 import time
69
68
70 import mercurial.revlog
69 import mercurial.revlog
71 from mercurial import (
70 from mercurial import (
72 changegroup,
71 changegroup,
73 cmdutil,
72 cmdutil,
74 commands,
73 commands,
75 copies,
74 copies,
76 error,
75 error,
77 extensions,
76 extensions,
78 hg,
77 hg,
79 mdiff,
78 mdiff,
80 merge,
79 merge,
81 util,
80 util,
82 )
81 )
83
82
84 # for "historical portability":
83 # for "historical portability":
85 # try to import modules separately (in dict order), and ignore
84 # try to import modules separately (in dict order), and ignore
86 # failure, because these aren't available with early Mercurial
85 # failure, because these aren't available with early Mercurial
87 try:
86 try:
88 from mercurial import branchmap # since 2.5 (or bcee63733aad)
87 from mercurial import branchmap # since 2.5 (or bcee63733aad)
89 except ImportError:
88 except ImportError:
90 pass
89 pass
91 try:
90 try:
92 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
91 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
93 except ImportError:
92 except ImportError:
94 pass
93 pass
95 try:
94 try:
96 from mercurial import registrar # since 3.7 (or 37d50250b696)
95 from mercurial import registrar # since 3.7 (or 37d50250b696)
97
96
98 dir(registrar) # forcibly load it
97 dir(registrar) # forcibly load it
99 except ImportError:
98 except ImportError:
100 registrar = None
99 registrar = None
101 try:
100 try:
102 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
101 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
103 except ImportError:
102 except ImportError:
104 pass
103 pass
105 try:
104 try:
106 from mercurial.utils import repoviewutil # since 5.0
105 from mercurial.utils import repoviewutil # since 5.0
107 except ImportError:
106 except ImportError:
108 repoviewutil = None
107 repoviewutil = None
109 try:
108 try:
110 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
109 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
111 except ImportError:
110 except ImportError:
112 pass
111 pass
113 try:
112 try:
114 from mercurial import setdiscovery # since 1.9 (or cb98fed52495)
113 from mercurial import setdiscovery # since 1.9 (or cb98fed52495)
115 except ImportError:
114 except ImportError:
116 pass
115 pass
117
116
118 try:
117 try:
119 from mercurial import profiling
118 from mercurial import profiling
120 except ImportError:
119 except ImportError:
121 profiling = None
120 profiling = None
122
121
123 try:
122 try:
124 from mercurial.revlogutils import constants as revlog_constants
123 from mercurial.revlogutils import constants as revlog_constants
125
124
126 perf_rl_kind = (revlog_constants.KIND_OTHER, b'created-by-perf')
125 perf_rl_kind = (revlog_constants.KIND_OTHER, b'created-by-perf')
127
126
128 def revlog(opener, *args, **kwargs):
127 def revlog(opener, *args, **kwargs):
129 return mercurial.revlog.revlog(opener, perf_rl_kind, *args, **kwargs)
128 return mercurial.revlog.revlog(opener, perf_rl_kind, *args, **kwargs)
130
129
131
130
132 except (ImportError, AttributeError):
131 except (ImportError, AttributeError):
133 perf_rl_kind = None
132 perf_rl_kind = None
134
133
135 def revlog(opener, *args, **kwargs):
134 def revlog(opener, *args, **kwargs):
136 return mercurial.revlog.revlog(opener, *args, **kwargs)
135 return mercurial.revlog.revlog(opener, *args, **kwargs)
137
136
138
137
139 def identity(a):
138 def identity(a):
140 return a
139 return a
141
140
142
141
143 try:
142 try:
144 from mercurial import pycompat
143 from mercurial import pycompat
145
144
146 getargspec = pycompat.getargspec # added to module after 4.5
145 getargspec = pycompat.getargspec # added to module after 4.5
147 _byteskwargs = pycompat.byteskwargs # since 4.1 (or fbc3f73dc802)
146 _byteskwargs = pycompat.byteskwargs # since 4.1 (or fbc3f73dc802)
148 _sysstr = pycompat.sysstr # since 4.0 (or 2219f4f82ede)
147 _sysstr = pycompat.sysstr # since 4.0 (or 2219f4f82ede)
149 _bytestr = pycompat.bytestr # since 4.2 (or b70407bd84d5)
148 _bytestr = pycompat.bytestr # since 4.2 (or b70407bd84d5)
150 _xrange = pycompat.xrange # since 4.8 (or 7eba8f83129b)
149 _xrange = pycompat.xrange # since 4.8 (or 7eba8f83129b)
151 fsencode = pycompat.fsencode # since 3.9 (or f4a5e0e86a7e)
150 fsencode = pycompat.fsencode # since 3.9 (or f4a5e0e86a7e)
152 if pycompat.ispy3:
151 if pycompat.ispy3:
153 _maxint = sys.maxsize # per py3 docs for replacing maxint
152 _maxint = sys.maxsize # per py3 docs for replacing maxint
154 else:
153 else:
155 _maxint = sys.maxint
154 _maxint = sys.maxint
156 except (NameError, ImportError, AttributeError):
155 except (NameError, ImportError, AttributeError):
157 import inspect
156 import inspect
158
157
159 getargspec = inspect.getargspec
158 getargspec = inspect.getargspec
160 _byteskwargs = identity
159 _byteskwargs = identity
161 _bytestr = str
160 _bytestr = str
162 fsencode = identity # no py3 support
161 fsencode = identity # no py3 support
163 _maxint = sys.maxint # no py3 support
162 _maxint = sys.maxint # no py3 support
164 _sysstr = lambda x: x # no py3 support
163 _sysstr = lambda x: x # no py3 support
165 _xrange = xrange
164 _xrange = xrange
166
165
167 try:
166 try:
168 # 4.7+
167 # 4.7+
169 queue = pycompat.queue.Queue
168 queue = pycompat.queue.Queue
170 except (NameError, AttributeError, ImportError):
169 except (NameError, AttributeError, ImportError):
171 # <4.7.
170 # <4.7.
172 try:
171 try:
173 queue = pycompat.queue
172 queue = pycompat.queue
174 except (NameError, AttributeError, ImportError):
173 except (NameError, AttributeError, ImportError):
175 import Queue as queue
174 import Queue as queue
176
175
177 try:
176 try:
178 from mercurial import logcmdutil
177 from mercurial import logcmdutil
179
178
180 makelogtemplater = logcmdutil.maketemplater
179 makelogtemplater = logcmdutil.maketemplater
181 except (AttributeError, ImportError):
180 except (AttributeError, ImportError):
182 try:
181 try:
183 makelogtemplater = cmdutil.makelogtemplater
182 makelogtemplater = cmdutil.makelogtemplater
184 except (AttributeError, ImportError):
183 except (AttributeError, ImportError):
185 makelogtemplater = None
184 makelogtemplater = None
186
185
187 # for "historical portability":
186 # for "historical portability":
188 # define util.safehasattr forcibly, because util.safehasattr has been
187 # define util.safehasattr forcibly, because util.safehasattr has been
189 # available since 1.9.3 (or 94b200a11cf7)
188 # available since 1.9.3 (or 94b200a11cf7)
190 _undefined = object()
189 _undefined = object()
191
190
192
191
193 def safehasattr(thing, attr):
192 def safehasattr(thing, attr):
194 return getattr(thing, _sysstr(attr), _undefined) is not _undefined
193 return getattr(thing, _sysstr(attr), _undefined) is not _undefined
195
194
196
195
197 setattr(util, 'safehasattr', safehasattr)
196 setattr(util, 'safehasattr', safehasattr)
198
197
199 # for "historical portability":
198 # for "historical portability":
200 # define util.timer forcibly, because util.timer has been available
199 # define util.timer forcibly, because util.timer has been available
201 # since ae5d60bb70c9
200 # since ae5d60bb70c9
202 if safehasattr(time, 'perf_counter'):
201 if safehasattr(time, 'perf_counter'):
203 util.timer = time.perf_counter
202 util.timer = time.perf_counter
204 elif os.name == b'nt':
203 elif os.name == b'nt':
205 util.timer = time.clock
204 util.timer = time.clock
206 else:
205 else:
207 util.timer = time.time
206 util.timer = time.time
208
207
209 # for "historical portability":
208 # for "historical portability":
210 # use locally defined empty option list, if formatteropts isn't
209 # use locally defined empty option list, if formatteropts isn't
211 # available, because commands.formatteropts has been available since
210 # available, because commands.formatteropts has been available since
212 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
211 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
213 # available since 2.2 (or ae5f92e154d3)
212 # available since 2.2 (or ae5f92e154d3)
214 formatteropts = getattr(
213 formatteropts = getattr(
215 cmdutil, "formatteropts", getattr(commands, "formatteropts", [])
214 cmdutil, "formatteropts", getattr(commands, "formatteropts", [])
216 )
215 )
217
216
218 # for "historical portability":
217 # for "historical portability":
219 # use locally defined option list, if debugrevlogopts isn't available,
218 # use locally defined option list, if debugrevlogopts isn't available,
220 # because commands.debugrevlogopts has been available since 3.7 (or
219 # because commands.debugrevlogopts has been available since 3.7 (or
221 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
220 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
222 # since 1.9 (or a79fea6b3e77).
221 # since 1.9 (or a79fea6b3e77).
223 revlogopts = getattr(
222 revlogopts = getattr(
224 cmdutil,
223 cmdutil,
225 "debugrevlogopts",
224 "debugrevlogopts",
226 getattr(
225 getattr(
227 commands,
226 commands,
228 "debugrevlogopts",
227 "debugrevlogopts",
229 [
228 [
230 (b'c', b'changelog', False, b'open changelog'),
229 (b'c', b'changelog', False, b'open changelog'),
231 (b'm', b'manifest', False, b'open manifest'),
230 (b'm', b'manifest', False, b'open manifest'),
232 (b'', b'dir', False, b'open directory manifest'),
231 (b'', b'dir', False, b'open directory manifest'),
233 ],
232 ],
234 ),
233 ),
235 )
234 )
236
235
237 cmdtable = {}
236 cmdtable = {}
238
237
239 # for "historical portability":
238 # for "historical portability":
240 # define parsealiases locally, because cmdutil.parsealiases has been
239 # define parsealiases locally, because cmdutil.parsealiases has been
241 # available since 1.5 (or 6252852b4332)
240 # available since 1.5 (or 6252852b4332)
242 def parsealiases(cmd):
241 def parsealiases(cmd):
243 return cmd.split(b"|")
242 return cmd.split(b"|")
244
243
245
244
246 if safehasattr(registrar, 'command'):
245 if safehasattr(registrar, 'command'):
247 command = registrar.command(cmdtable)
246 command = registrar.command(cmdtable)
248 elif safehasattr(cmdutil, 'command'):
247 elif safehasattr(cmdutil, 'command'):
249 command = cmdutil.command(cmdtable)
248 command = cmdutil.command(cmdtable)
250 if 'norepo' not in getargspec(command).args:
249 if 'norepo' not in getargspec(command).args:
251 # for "historical portability":
250 # for "historical portability":
252 # wrap original cmdutil.command, because "norepo" option has
251 # wrap original cmdutil.command, because "norepo" option has
253 # been available since 3.1 (or 75a96326cecb)
252 # been available since 3.1 (or 75a96326cecb)
254 _command = command
253 _command = command
255
254
256 def command(name, options=(), synopsis=None, norepo=False):
255 def command(name, options=(), synopsis=None, norepo=False):
257 if norepo:
256 if norepo:
258 commands.norepo += b' %s' % b' '.join(parsealiases(name))
257 commands.norepo += b' %s' % b' '.join(parsealiases(name))
259 return _command(name, list(options), synopsis)
258 return _command(name, list(options), synopsis)
260
259
261
260
262 else:
261 else:
263 # for "historical portability":
262 # for "historical portability":
264 # define "@command" annotation locally, because cmdutil.command
263 # define "@command" annotation locally, because cmdutil.command
265 # has been available since 1.9 (or 2daa5179e73f)
264 # has been available since 1.9 (or 2daa5179e73f)
266 def command(name, options=(), synopsis=None, norepo=False):
265 def command(name, options=(), synopsis=None, norepo=False):
267 def decorator(func):
266 def decorator(func):
268 if synopsis:
267 if synopsis:
269 cmdtable[name] = func, list(options), synopsis
268 cmdtable[name] = func, list(options), synopsis
270 else:
269 else:
271 cmdtable[name] = func, list(options)
270 cmdtable[name] = func, list(options)
272 if norepo:
271 if norepo:
273 commands.norepo += b' %s' % b' '.join(parsealiases(name))
272 commands.norepo += b' %s' % b' '.join(parsealiases(name))
274 return func
273 return func
275
274
276 return decorator
275 return decorator
277
276
278
277
279 try:
278 try:
280 import mercurial.registrar
279 import mercurial.registrar
281 import mercurial.configitems
280 import mercurial.configitems
282
281
283 configtable = {}
282 configtable = {}
284 configitem = mercurial.registrar.configitem(configtable)
283 configitem = mercurial.registrar.configitem(configtable)
285 configitem(
284 configitem(
286 b'perf',
285 b'perf',
287 b'presleep',
286 b'presleep',
288 default=mercurial.configitems.dynamicdefault,
287 default=mercurial.configitems.dynamicdefault,
289 experimental=True,
288 experimental=True,
290 )
289 )
291 configitem(
290 configitem(
292 b'perf',
291 b'perf',
293 b'stub',
292 b'stub',
294 default=mercurial.configitems.dynamicdefault,
293 default=mercurial.configitems.dynamicdefault,
295 experimental=True,
294 experimental=True,
296 )
295 )
297 configitem(
296 configitem(
298 b'perf',
297 b'perf',
299 b'parentscount',
298 b'parentscount',
300 default=mercurial.configitems.dynamicdefault,
299 default=mercurial.configitems.dynamicdefault,
301 experimental=True,
300 experimental=True,
302 )
301 )
303 configitem(
302 configitem(
304 b'perf',
303 b'perf',
305 b'all-timing',
304 b'all-timing',
306 default=mercurial.configitems.dynamicdefault,
305 default=mercurial.configitems.dynamicdefault,
307 experimental=True,
306 experimental=True,
308 )
307 )
309 configitem(
308 configitem(
310 b'perf',
309 b'perf',
311 b'pre-run',
310 b'pre-run',
312 default=mercurial.configitems.dynamicdefault,
311 default=mercurial.configitems.dynamicdefault,
313 )
312 )
314 configitem(
313 configitem(
315 b'perf',
314 b'perf',
316 b'profile-benchmark',
315 b'profile-benchmark',
317 default=mercurial.configitems.dynamicdefault,
316 default=mercurial.configitems.dynamicdefault,
318 )
317 )
319 configitem(
318 configitem(
320 b'perf',
319 b'perf',
321 b'run-limits',
320 b'run-limits',
322 default=mercurial.configitems.dynamicdefault,
321 default=mercurial.configitems.dynamicdefault,
323 experimental=True,
322 experimental=True,
324 )
323 )
325 except (ImportError, AttributeError):
324 except (ImportError, AttributeError):
326 pass
325 pass
327 except TypeError:
326 except TypeError:
328 # compatibility fix for a11fd395e83f
327 # compatibility fix for a11fd395e83f
329 # hg version: 5.2
328 # hg version: 5.2
330 configitem(
329 configitem(
331 b'perf',
330 b'perf',
332 b'presleep',
331 b'presleep',
333 default=mercurial.configitems.dynamicdefault,
332 default=mercurial.configitems.dynamicdefault,
334 )
333 )
335 configitem(
334 configitem(
336 b'perf',
335 b'perf',
337 b'stub',
336 b'stub',
338 default=mercurial.configitems.dynamicdefault,
337 default=mercurial.configitems.dynamicdefault,
339 )
338 )
340 configitem(
339 configitem(
341 b'perf',
340 b'perf',
342 b'parentscount',
341 b'parentscount',
343 default=mercurial.configitems.dynamicdefault,
342 default=mercurial.configitems.dynamicdefault,
344 )
343 )
345 configitem(
344 configitem(
346 b'perf',
345 b'perf',
347 b'all-timing',
346 b'all-timing',
348 default=mercurial.configitems.dynamicdefault,
347 default=mercurial.configitems.dynamicdefault,
349 )
348 )
350 configitem(
349 configitem(
351 b'perf',
350 b'perf',
352 b'pre-run',
351 b'pre-run',
353 default=mercurial.configitems.dynamicdefault,
352 default=mercurial.configitems.dynamicdefault,
354 )
353 )
355 configitem(
354 configitem(
356 b'perf',
355 b'perf',
357 b'profile-benchmark',
356 b'profile-benchmark',
358 default=mercurial.configitems.dynamicdefault,
357 default=mercurial.configitems.dynamicdefault,
359 )
358 )
360 configitem(
359 configitem(
361 b'perf',
360 b'perf',
362 b'run-limits',
361 b'run-limits',
363 default=mercurial.configitems.dynamicdefault,
362 default=mercurial.configitems.dynamicdefault,
364 )
363 )
365
364
366
365
367 def getlen(ui):
366 def getlen(ui):
368 if ui.configbool(b"perf", b"stub", False):
367 if ui.configbool(b"perf", b"stub", False):
369 return lambda x: 1
368 return lambda x: 1
370 return len
369 return len
371
370
372
371
373 class noop(object):
372 class noop(object):
374 """dummy context manager"""
373 """dummy context manager"""
375
374
376 def __enter__(self):
375 def __enter__(self):
377 pass
376 pass
378
377
379 def __exit__(self, *args):
378 def __exit__(self, *args):
380 pass
379 pass
381
380
382
381
383 NOOPCTX = noop()
382 NOOPCTX = noop()
384
383
385
384
386 def gettimer(ui, opts=None):
385 def gettimer(ui, opts=None):
387 """return a timer function and formatter: (timer, formatter)
386 """return a timer function and formatter: (timer, formatter)
388
387
389 This function exists to gather the creation of formatter in a single
388 This function exists to gather the creation of formatter in a single
390 place instead of duplicating it in all performance commands."""
389 place instead of duplicating it in all performance commands."""
391
390
392 # enforce an idle period before execution to counteract power management
391 # enforce an idle period before execution to counteract power management
393 # experimental config: perf.presleep
392 # experimental config: perf.presleep
394 time.sleep(getint(ui, b"perf", b"presleep", 1))
393 time.sleep(getint(ui, b"perf", b"presleep", 1))
395
394
396 if opts is None:
395 if opts is None:
397 opts = {}
396 opts = {}
398 # redirect all to stderr unless buffer api is in use
397 # redirect all to stderr unless buffer api is in use
399 if not ui._buffers:
398 if not ui._buffers:
400 ui = ui.copy()
399 ui = ui.copy()
401 uifout = safeattrsetter(ui, b'fout', ignoremissing=True)
400 uifout = safeattrsetter(ui, b'fout', ignoremissing=True)
402 if uifout:
401 if uifout:
403 # for "historical portability":
402 # for "historical portability":
404 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
403 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
405 uifout.set(ui.ferr)
404 uifout.set(ui.ferr)
406
405
407 # get a formatter
406 # get a formatter
408 uiformatter = getattr(ui, 'formatter', None)
407 uiformatter = getattr(ui, 'formatter', None)
409 if uiformatter:
408 if uiformatter:
410 fm = uiformatter(b'perf', opts)
409 fm = uiformatter(b'perf', opts)
411 else:
410 else:
412 # for "historical portability":
411 # for "historical portability":
413 # define formatter locally, because ui.formatter has been
412 # define formatter locally, because ui.formatter has been
414 # available since 2.2 (or ae5f92e154d3)
413 # available since 2.2 (or ae5f92e154d3)
415 from mercurial import node
414 from mercurial import node
416
415
417 class defaultformatter(object):
416 class defaultformatter(object):
418 """Minimized composition of baseformatter and plainformatter"""
417 """Minimized composition of baseformatter and plainformatter"""
419
418
420 def __init__(self, ui, topic, opts):
419 def __init__(self, ui, topic, opts):
421 self._ui = ui
420 self._ui = ui
422 if ui.debugflag:
421 if ui.debugflag:
423 self.hexfunc = node.hex
422 self.hexfunc = node.hex
424 else:
423 else:
425 self.hexfunc = node.short
424 self.hexfunc = node.short
426
425
427 def __nonzero__(self):
426 def __nonzero__(self):
428 return False
427 return False
429
428
430 __bool__ = __nonzero__
429 __bool__ = __nonzero__
431
430
432 def startitem(self):
431 def startitem(self):
433 pass
432 pass
434
433
435 def data(self, **data):
434 def data(self, **data):
436 pass
435 pass
437
436
438 def write(self, fields, deftext, *fielddata, **opts):
437 def write(self, fields, deftext, *fielddata, **opts):
439 self._ui.write(deftext % fielddata, **opts)
438 self._ui.write(deftext % fielddata, **opts)
440
439
441 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
440 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
442 if cond:
441 if cond:
443 self._ui.write(deftext % fielddata, **opts)
442 self._ui.write(deftext % fielddata, **opts)
444
443
445 def plain(self, text, **opts):
444 def plain(self, text, **opts):
446 self._ui.write(text, **opts)
445 self._ui.write(text, **opts)
447
446
448 def end(self):
447 def end(self):
449 pass
448 pass
450
449
451 fm = defaultformatter(ui, b'perf', opts)
450 fm = defaultformatter(ui, b'perf', opts)
452
451
453 # stub function, runs code only once instead of in a loop
452 # stub function, runs code only once instead of in a loop
454 # experimental config: perf.stub
453 # experimental config: perf.stub
455 if ui.configbool(b"perf", b"stub", False):
454 if ui.configbool(b"perf", b"stub", False):
456 return functools.partial(stub_timer, fm), fm
455 return functools.partial(stub_timer, fm), fm
457
456
458 # experimental config: perf.all-timing
457 # experimental config: perf.all-timing
459 displayall = ui.configbool(b"perf", b"all-timing", False)
458 displayall = ui.configbool(b"perf", b"all-timing", False)
460
459
461 # experimental config: perf.run-limits
460 # experimental config: perf.run-limits
462 limitspec = ui.configlist(b"perf", b"run-limits", [])
461 limitspec = ui.configlist(b"perf", b"run-limits", [])
463 limits = []
462 limits = []
464 for item in limitspec:
463 for item in limitspec:
465 parts = item.split(b'-', 1)
464 parts = item.split(b'-', 1)
466 if len(parts) < 2:
465 if len(parts) < 2:
467 ui.warn((b'malformatted run limit entry, missing "-": %s\n' % item))
466 ui.warn((b'malformatted run limit entry, missing "-": %s\n' % item))
468 continue
467 continue
469 try:
468 try:
470 time_limit = float(_sysstr(parts[0]))
469 time_limit = float(_sysstr(parts[0]))
471 except ValueError as e:
470 except ValueError as e:
472 ui.warn(
471 ui.warn(
473 (
472 (
474 b'malformatted run limit entry, %s: %s\n'
473 b'malformatted run limit entry, %s: %s\n'
475 % (_bytestr(e), item)
474 % (_bytestr(e), item)
476 )
475 )
477 )
476 )
478 continue
477 continue
479 try:
478 try:
480 run_limit = int(_sysstr(parts[1]))
479 run_limit = int(_sysstr(parts[1]))
481 except ValueError as e:
480 except ValueError as e:
482 ui.warn(
481 ui.warn(
483 (
482 (
484 b'malformatted run limit entry, %s: %s\n'
483 b'malformatted run limit entry, %s: %s\n'
485 % (_bytestr(e), item)
484 % (_bytestr(e), item)
486 )
485 )
487 )
486 )
488 continue
487 continue
489 limits.append((time_limit, run_limit))
488 limits.append((time_limit, run_limit))
490 if not limits:
489 if not limits:
491 limits = DEFAULTLIMITS
490 limits = DEFAULTLIMITS
492
491
493 profiler = None
492 profiler = None
494 if profiling is not None:
493 if profiling is not None:
495 if ui.configbool(b"perf", b"profile-benchmark", False):
494 if ui.configbool(b"perf", b"profile-benchmark", False):
496 profiler = profiling.profile(ui)
495 profiler = profiling.profile(ui)
497
496
498 prerun = getint(ui, b"perf", b"pre-run", 0)
497 prerun = getint(ui, b"perf", b"pre-run", 0)
499 t = functools.partial(
498 t = functools.partial(
500 _timer,
499 _timer,
501 fm,
500 fm,
502 displayall=displayall,
501 displayall=displayall,
503 limits=limits,
502 limits=limits,
504 prerun=prerun,
503 prerun=prerun,
505 profiler=profiler,
504 profiler=profiler,
506 )
505 )
507 return t, fm
506 return t, fm
508
507
509
508
510 def stub_timer(fm, func, setup=None, title=None):
509 def stub_timer(fm, func, setup=None, title=None):
511 if setup is not None:
510 if setup is not None:
512 setup()
511 setup()
513 func()
512 func()
514
513
515
514
516 @contextlib.contextmanager
515 @contextlib.contextmanager
517 def timeone():
516 def timeone():
518 r = []
517 r = []
519 ostart = os.times()
518 ostart = os.times()
520 cstart = util.timer()
519 cstart = util.timer()
521 yield r
520 yield r
522 cstop = util.timer()
521 cstop = util.timer()
523 ostop = os.times()
522 ostop = os.times()
524 a, b = ostart, ostop
523 a, b = ostart, ostop
525 r.append((cstop - cstart, b[0] - a[0], b[1] - a[1]))
524 r.append((cstop - cstart, b[0] - a[0], b[1] - a[1]))
526
525
527
526
528 # list of stop condition (elapsed time, minimal run count)
527 # list of stop condition (elapsed time, minimal run count)
529 DEFAULTLIMITS = (
528 DEFAULTLIMITS = (
530 (3.0, 100),
529 (3.0, 100),
531 (10.0, 3),
530 (10.0, 3),
532 )
531 )
533
532
534
533
535 def _timer(
534 def _timer(
536 fm,
535 fm,
537 func,
536 func,
538 setup=None,
537 setup=None,
539 title=None,
538 title=None,
540 displayall=False,
539 displayall=False,
541 limits=DEFAULTLIMITS,
540 limits=DEFAULTLIMITS,
542 prerun=0,
541 prerun=0,
543 profiler=None,
542 profiler=None,
544 ):
543 ):
545 gc.collect()
544 gc.collect()
546 results = []
545 results = []
547 begin = util.timer()
546 begin = util.timer()
548 count = 0
547 count = 0
549 if profiler is None:
548 if profiler is None:
550 profiler = NOOPCTX
549 profiler = NOOPCTX
551 for i in range(prerun):
550 for i in range(prerun):
552 if setup is not None:
551 if setup is not None:
553 setup()
552 setup()
554 func()
553 func()
555 keepgoing = True
554 keepgoing = True
556 while keepgoing:
555 while keepgoing:
557 if setup is not None:
556 if setup is not None:
558 setup()
557 setup()
559 with profiler:
558 with profiler:
560 with timeone() as item:
559 with timeone() as item:
561 r = func()
560 r = func()
562 profiler = NOOPCTX
561 profiler = NOOPCTX
563 count += 1
562 count += 1
564 results.append(item[0])
563 results.append(item[0])
565 cstop = util.timer()
564 cstop = util.timer()
566 # Look for a stop condition.
565 # Look for a stop condition.
567 elapsed = cstop - begin
566 elapsed = cstop - begin
568 for t, mincount in limits:
567 for t, mincount in limits:
569 if elapsed >= t and count >= mincount:
568 if elapsed >= t and count >= mincount:
570 keepgoing = False
569 keepgoing = False
571 break
570 break
572
571
573 formatone(fm, results, title=title, result=r, displayall=displayall)
572 formatone(fm, results, title=title, result=r, displayall=displayall)
574
573
575
574
576 def formatone(fm, timings, title=None, result=None, displayall=False):
575 def formatone(fm, timings, title=None, result=None, displayall=False):
577
576
578 count = len(timings)
577 count = len(timings)
579
578
580 fm.startitem()
579 fm.startitem()
581
580
582 if title:
581 if title:
583 fm.write(b'title', b'! %s\n', title)
582 fm.write(b'title', b'! %s\n', title)
584 if result:
583 if result:
585 fm.write(b'result', b'! result: %s\n', result)
584 fm.write(b'result', b'! result: %s\n', result)
586
585
587 def display(role, entry):
586 def display(role, entry):
588 prefix = b''
587 prefix = b''
589 if role != b'best':
588 if role != b'best':
590 prefix = b'%s.' % role
589 prefix = b'%s.' % role
591 fm.plain(b'!')
590 fm.plain(b'!')
592 fm.write(prefix + b'wall', b' wall %f', entry[0])
591 fm.write(prefix + b'wall', b' wall %f', entry[0])
593 fm.write(prefix + b'comb', b' comb %f', entry[1] + entry[2])
592 fm.write(prefix + b'comb', b' comb %f', entry[1] + entry[2])
594 fm.write(prefix + b'user', b' user %f', entry[1])
593 fm.write(prefix + b'user', b' user %f', entry[1])
595 fm.write(prefix + b'sys', b' sys %f', entry[2])
594 fm.write(prefix + b'sys', b' sys %f', entry[2])
596 fm.write(prefix + b'count', b' (%s of %%d)' % role, count)
595 fm.write(prefix + b'count', b' (%s of %%d)' % role, count)
597 fm.plain(b'\n')
596 fm.plain(b'\n')
598
597
599 timings.sort()
598 timings.sort()
600 min_val = timings[0]
599 min_val = timings[0]
601 display(b'best', min_val)
600 display(b'best', min_val)
602 if displayall:
601 if displayall:
603 max_val = timings[-1]
602 max_val = timings[-1]
604 display(b'max', max_val)
603 display(b'max', max_val)
605 avg = tuple([sum(x) / count for x in zip(*timings)])
604 avg = tuple([sum(x) / count for x in zip(*timings)])
606 display(b'avg', avg)
605 display(b'avg', avg)
607 median = timings[len(timings) // 2]
606 median = timings[len(timings) // 2]
608 display(b'median', median)
607 display(b'median', median)
609
608
610
609
611 # utilities for historical portability
610 # utilities for historical portability
612
611
613
612
614 def getint(ui, section, name, default):
613 def getint(ui, section, name, default):
615 # for "historical portability":
614 # for "historical portability":
616 # ui.configint has been available since 1.9 (or fa2b596db182)
615 # ui.configint has been available since 1.9 (or fa2b596db182)
617 v = ui.config(section, name, None)
616 v = ui.config(section, name, None)
618 if v is None:
617 if v is None:
619 return default
618 return default
620 try:
619 try:
621 return int(v)
620 return int(v)
622 except ValueError:
621 except ValueError:
623 raise error.ConfigError(
622 raise error.ConfigError(
624 b"%s.%s is not an integer ('%s')" % (section, name, v)
623 b"%s.%s is not an integer ('%s')" % (section, name, v)
625 )
624 )
626
625
627
626
628 def safeattrsetter(obj, name, ignoremissing=False):
627 def safeattrsetter(obj, name, ignoremissing=False):
629 """Ensure that 'obj' has 'name' attribute before subsequent setattr
628 """Ensure that 'obj' has 'name' attribute before subsequent setattr
630
629
631 This function is aborted, if 'obj' doesn't have 'name' attribute
630 This function is aborted, if 'obj' doesn't have 'name' attribute
632 at runtime. This avoids overlooking removal of an attribute, which
631 at runtime. This avoids overlooking removal of an attribute, which
633 breaks assumption of performance measurement, in the future.
632 breaks assumption of performance measurement, in the future.
634
633
635 This function returns the object to (1) assign a new value, and
634 This function returns the object to (1) assign a new value, and
636 (2) restore an original value to the attribute.
635 (2) restore an original value to the attribute.
637
636
638 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
637 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
639 abortion, and this function returns None. This is useful to
638 abortion, and this function returns None. This is useful to
640 examine an attribute, which isn't ensured in all Mercurial
639 examine an attribute, which isn't ensured in all Mercurial
641 versions.
640 versions.
642 """
641 """
643 if not util.safehasattr(obj, name):
642 if not util.safehasattr(obj, name):
644 if ignoremissing:
643 if ignoremissing:
645 return None
644 return None
646 raise error.Abort(
645 raise error.Abort(
647 (
646 (
648 b"missing attribute %s of %s might break assumption"
647 b"missing attribute %s of %s might break assumption"
649 b" of performance measurement"
648 b" of performance measurement"
650 )
649 )
651 % (name, obj)
650 % (name, obj)
652 )
651 )
653
652
654 origvalue = getattr(obj, _sysstr(name))
653 origvalue = getattr(obj, _sysstr(name))
655
654
656 class attrutil(object):
655 class attrutil(object):
657 def set(self, newvalue):
656 def set(self, newvalue):
658 setattr(obj, _sysstr(name), newvalue)
657 setattr(obj, _sysstr(name), newvalue)
659
658
660 def restore(self):
659 def restore(self):
661 setattr(obj, _sysstr(name), origvalue)
660 setattr(obj, _sysstr(name), origvalue)
662
661
663 return attrutil()
662 return attrutil()
664
663
665
664
666 # utilities to examine each internal API changes
665 # utilities to examine each internal API changes
667
666
668
667
669 def getbranchmapsubsettable():
668 def getbranchmapsubsettable():
670 # for "historical portability":
669 # for "historical portability":
671 # subsettable is defined in:
670 # subsettable is defined in:
672 # - branchmap since 2.9 (or 175c6fd8cacc)
671 # - branchmap since 2.9 (or 175c6fd8cacc)
673 # - repoview since 2.5 (or 59a9f18d4587)
672 # - repoview since 2.5 (or 59a9f18d4587)
674 # - repoviewutil since 5.0
673 # - repoviewutil since 5.0
675 for mod in (branchmap, repoview, repoviewutil):
674 for mod in (branchmap, repoview, repoviewutil):
676 subsettable = getattr(mod, 'subsettable', None)
675 subsettable = getattr(mod, 'subsettable', None)
677 if subsettable:
676 if subsettable:
678 return subsettable
677 return subsettable
679
678
680 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
679 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
681 # branchmap and repoview modules exist, but subsettable attribute
680 # branchmap and repoview modules exist, but subsettable attribute
682 # doesn't)
681 # doesn't)
683 raise error.Abort(
682 raise error.Abort(
684 b"perfbranchmap not available with this Mercurial",
683 b"perfbranchmap not available with this Mercurial",
685 hint=b"use 2.5 or later",
684 hint=b"use 2.5 or later",
686 )
685 )
687
686
688
687
689 def getsvfs(repo):
688 def getsvfs(repo):
690 """Return appropriate object to access files under .hg/store"""
689 """Return appropriate object to access files under .hg/store"""
691 # for "historical portability":
690 # for "historical portability":
692 # repo.svfs has been available since 2.3 (or 7034365089bf)
691 # repo.svfs has been available since 2.3 (or 7034365089bf)
693 svfs = getattr(repo, 'svfs', None)
692 svfs = getattr(repo, 'svfs', None)
694 if svfs:
693 if svfs:
695 return svfs
694 return svfs
696 else:
695 else:
697 return getattr(repo, 'sopener')
696 return getattr(repo, 'sopener')
698
697
699
698
700 def getvfs(repo):
699 def getvfs(repo):
701 """Return appropriate object to access files under .hg"""
700 """Return appropriate object to access files under .hg"""
702 # for "historical portability":
701 # for "historical portability":
703 # repo.vfs has been available since 2.3 (or 7034365089bf)
702 # repo.vfs has been available since 2.3 (or 7034365089bf)
704 vfs = getattr(repo, 'vfs', None)
703 vfs = getattr(repo, 'vfs', None)
705 if vfs:
704 if vfs:
706 return vfs
705 return vfs
707 else:
706 else:
708 return getattr(repo, 'opener')
707 return getattr(repo, 'opener')
709
708
710
709
711 def repocleartagscachefunc(repo):
710 def repocleartagscachefunc(repo):
712 """Return the function to clear tags cache according to repo internal API"""
711 """Return the function to clear tags cache according to repo internal API"""
713 if util.safehasattr(repo, b'_tagscache'): # since 2.0 (or 9dca7653b525)
712 if util.safehasattr(repo, b'_tagscache'): # since 2.0 (or 9dca7653b525)
714 # in this case, setattr(repo, '_tagscache', None) or so isn't
713 # in this case, setattr(repo, '_tagscache', None) or so isn't
715 # correct way to clear tags cache, because existing code paths
714 # correct way to clear tags cache, because existing code paths
716 # expect _tagscache to be a structured object.
715 # expect _tagscache to be a structured object.
717 def clearcache():
716 def clearcache():
718 # _tagscache has been filteredpropertycache since 2.5 (or
717 # _tagscache has been filteredpropertycache since 2.5 (or
719 # 98c867ac1330), and delattr() can't work in such case
718 # 98c867ac1330), and delattr() can't work in such case
720 if '_tagscache' in vars(repo):
719 if '_tagscache' in vars(repo):
721 del repo.__dict__['_tagscache']
720 del repo.__dict__['_tagscache']
722
721
723 return clearcache
722 return clearcache
724
723
725 repotags = safeattrsetter(repo, b'_tags', ignoremissing=True)
724 repotags = safeattrsetter(repo, b'_tags', ignoremissing=True)
726 if repotags: # since 1.4 (or 5614a628d173)
725 if repotags: # since 1.4 (or 5614a628d173)
727 return lambda: repotags.set(None)
726 return lambda: repotags.set(None)
728
727
729 repotagscache = safeattrsetter(repo, b'tagscache', ignoremissing=True)
728 repotagscache = safeattrsetter(repo, b'tagscache', ignoremissing=True)
730 if repotagscache: # since 0.6 (or d7df759d0e97)
729 if repotagscache: # since 0.6 (or d7df759d0e97)
731 return lambda: repotagscache.set(None)
730 return lambda: repotagscache.set(None)
732
731
733 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
732 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
734 # this point, but it isn't so problematic, because:
733 # this point, but it isn't so problematic, because:
735 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
734 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
736 # in perftags() causes failure soon
735 # in perftags() causes failure soon
737 # - perf.py itself has been available since 1.1 (or eb240755386d)
736 # - perf.py itself has been available since 1.1 (or eb240755386d)
738 raise error.Abort(b"tags API of this hg command is unknown")
737 raise error.Abort(b"tags API of this hg command is unknown")
739
738
740
739
741 # utilities to clear cache
740 # utilities to clear cache
742
741
743
742
744 def clearfilecache(obj, attrname):
743 def clearfilecache(obj, attrname):
745 unfiltered = getattr(obj, 'unfiltered', None)
744 unfiltered = getattr(obj, 'unfiltered', None)
746 if unfiltered is not None:
745 if unfiltered is not None:
747 obj = obj.unfiltered()
746 obj = obj.unfiltered()
748 if attrname in vars(obj):
747 if attrname in vars(obj):
749 delattr(obj, attrname)
748 delattr(obj, attrname)
750 obj._filecache.pop(attrname, None)
749 obj._filecache.pop(attrname, None)
751
750
752
751
753 def clearchangelog(repo):
752 def clearchangelog(repo):
754 if repo is not repo.unfiltered():
753 if repo is not repo.unfiltered():
755 object.__setattr__(repo, '_clcachekey', None)
754 object.__setattr__(repo, '_clcachekey', None)
756 object.__setattr__(repo, '_clcache', None)
755 object.__setattr__(repo, '_clcache', None)
757 clearfilecache(repo.unfiltered(), 'changelog')
756 clearfilecache(repo.unfiltered(), 'changelog')
758
757
759
758
760 # perf commands
759 # perf commands
761
760
762
761
763 @command(b'perf::walk|perfwalk', formatteropts)
762 @command(b'perf::walk|perfwalk', formatteropts)
764 def perfwalk(ui, repo, *pats, **opts):
763 def perfwalk(ui, repo, *pats, **opts):
765 opts = _byteskwargs(opts)
764 opts = _byteskwargs(opts)
766 timer, fm = gettimer(ui, opts)
765 timer, fm = gettimer(ui, opts)
767 m = scmutil.match(repo[None], pats, {})
766 m = scmutil.match(repo[None], pats, {})
768 timer(
767 timer(
769 lambda: len(
768 lambda: len(
770 list(
769 list(
771 repo.dirstate.walk(m, subrepos=[], unknown=True, ignored=False)
770 repo.dirstate.walk(m, subrepos=[], unknown=True, ignored=False)
772 )
771 )
773 )
772 )
774 )
773 )
775 fm.end()
774 fm.end()
776
775
777
776
778 @command(b'perf::annotate|perfannotate', formatteropts)
777 @command(b'perf::annotate|perfannotate', formatteropts)
779 def perfannotate(ui, repo, f, **opts):
778 def perfannotate(ui, repo, f, **opts):
780 opts = _byteskwargs(opts)
779 opts = _byteskwargs(opts)
781 timer, fm = gettimer(ui, opts)
780 timer, fm = gettimer(ui, opts)
782 fc = repo[b'.'][f]
781 fc = repo[b'.'][f]
783 timer(lambda: len(fc.annotate(True)))
782 timer(lambda: len(fc.annotate(True)))
784 fm.end()
783 fm.end()
785
784
786
785
787 @command(
786 @command(
788 b'perf::status|perfstatus',
787 b'perf::status|perfstatus',
789 [
788 [
790 (b'u', b'unknown', False, b'ask status to look for unknown files'),
789 (b'u', b'unknown', False, b'ask status to look for unknown files'),
791 (b'', b'dirstate', False, b'benchmark the internal dirstate call'),
790 (b'', b'dirstate', False, b'benchmark the internal dirstate call'),
792 ]
791 ]
793 + formatteropts,
792 + formatteropts,
794 )
793 )
795 def perfstatus(ui, repo, **opts):
794 def perfstatus(ui, repo, **opts):
796 """benchmark the performance of a single status call
795 """benchmark the performance of a single status call
797
796
798 The repository data are preserved between each call.
797 The repository data are preserved between each call.
799
798
800 By default, only the status of the tracked file are requested. If
799 By default, only the status of the tracked file are requested. If
801 `--unknown` is passed, the "unknown" files are also tracked.
800 `--unknown` is passed, the "unknown" files are also tracked.
802 """
801 """
803 opts = _byteskwargs(opts)
802 opts = _byteskwargs(opts)
804 # m = match.always(repo.root, repo.getcwd())
803 # m = match.always(repo.root, repo.getcwd())
805 # timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
804 # timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
806 # False))))
805 # False))))
807 timer, fm = gettimer(ui, opts)
806 timer, fm = gettimer(ui, opts)
808 if opts[b'dirstate']:
807 if opts[b'dirstate']:
809 dirstate = repo.dirstate
808 dirstate = repo.dirstate
810 m = scmutil.matchall(repo)
809 m = scmutil.matchall(repo)
811 unknown = opts[b'unknown']
810 unknown = opts[b'unknown']
812
811
813 def status_dirstate():
812 def status_dirstate():
814 s = dirstate.status(
813 s = dirstate.status(
815 m, subrepos=[], ignored=False, clean=False, unknown=unknown
814 m, subrepos=[], ignored=False, clean=False, unknown=unknown
816 )
815 )
817 sum(map(bool, s))
816 sum(map(bool, s))
818
817
819 timer(status_dirstate)
818 timer(status_dirstate)
820 else:
819 else:
821 timer(lambda: sum(map(len, repo.status(unknown=opts[b'unknown']))))
820 timer(lambda: sum(map(len, repo.status(unknown=opts[b'unknown']))))
822 fm.end()
821 fm.end()
823
822
824
823
825 @command(b'perf::addremove|perfaddremove', formatteropts)
824 @command(b'perf::addremove|perfaddremove', formatteropts)
826 def perfaddremove(ui, repo, **opts):
825 def perfaddremove(ui, repo, **opts):
827 opts = _byteskwargs(opts)
826 opts = _byteskwargs(opts)
828 timer, fm = gettimer(ui, opts)
827 timer, fm = gettimer(ui, opts)
829 try:
828 try:
830 oldquiet = repo.ui.quiet
829 oldquiet = repo.ui.quiet
831 repo.ui.quiet = True
830 repo.ui.quiet = True
832 matcher = scmutil.match(repo[None])
831 matcher = scmutil.match(repo[None])
833 opts[b'dry_run'] = True
832 opts[b'dry_run'] = True
834 if 'uipathfn' in getargspec(scmutil.addremove).args:
833 if 'uipathfn' in getargspec(scmutil.addremove).args:
835 uipathfn = scmutil.getuipathfn(repo)
834 uipathfn = scmutil.getuipathfn(repo)
836 timer(lambda: scmutil.addremove(repo, matcher, b"", uipathfn, opts))
835 timer(lambda: scmutil.addremove(repo, matcher, b"", uipathfn, opts))
837 else:
836 else:
838 timer(lambda: scmutil.addremove(repo, matcher, b"", opts))
837 timer(lambda: scmutil.addremove(repo, matcher, b"", opts))
839 finally:
838 finally:
840 repo.ui.quiet = oldquiet
839 repo.ui.quiet = oldquiet
841 fm.end()
840 fm.end()
842
841
843
842
844 def clearcaches(cl):
843 def clearcaches(cl):
845 # behave somewhat consistently across internal API changes
844 # behave somewhat consistently across internal API changes
846 if util.safehasattr(cl, b'clearcaches'):
845 if util.safehasattr(cl, b'clearcaches'):
847 cl.clearcaches()
846 cl.clearcaches()
848 elif util.safehasattr(cl, b'_nodecache'):
847 elif util.safehasattr(cl, b'_nodecache'):
849 # <= hg-5.2
848 # <= hg-5.2
850 from mercurial.node import nullid, nullrev
849 from mercurial.node import nullid, nullrev
851
850
852 cl._nodecache = {nullid: nullrev}
851 cl._nodecache = {nullid: nullrev}
853 cl._nodepos = None
852 cl._nodepos = None
854
853
855
854
856 @command(b'perf::heads|perfheads', formatteropts)
855 @command(b'perf::heads|perfheads', formatteropts)
857 def perfheads(ui, repo, **opts):
856 def perfheads(ui, repo, **opts):
858 """benchmark the computation of a changelog heads"""
857 """benchmark the computation of a changelog heads"""
859 opts = _byteskwargs(opts)
858 opts = _byteskwargs(opts)
860 timer, fm = gettimer(ui, opts)
859 timer, fm = gettimer(ui, opts)
861 cl = repo.changelog
860 cl = repo.changelog
862
861
863 def s():
862 def s():
864 clearcaches(cl)
863 clearcaches(cl)
865
864
866 def d():
865 def d():
867 len(cl.headrevs())
866 len(cl.headrevs())
868
867
869 timer(d, setup=s)
868 timer(d, setup=s)
870 fm.end()
869 fm.end()
871
870
872
871
873 @command(
872 @command(
874 b'perf::tags|perftags',
873 b'perf::tags|perftags',
875 formatteropts
874 formatteropts
876 + [
875 + [
877 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
876 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
878 ],
877 ],
879 )
878 )
880 def perftags(ui, repo, **opts):
879 def perftags(ui, repo, **opts):
881 opts = _byteskwargs(opts)
880 opts = _byteskwargs(opts)
882 timer, fm = gettimer(ui, opts)
881 timer, fm = gettimer(ui, opts)
883 repocleartagscache = repocleartagscachefunc(repo)
882 repocleartagscache = repocleartagscachefunc(repo)
884 clearrevlogs = opts[b'clear_revlogs']
883 clearrevlogs = opts[b'clear_revlogs']
885
884
886 def s():
885 def s():
887 if clearrevlogs:
886 if clearrevlogs:
888 clearchangelog(repo)
887 clearchangelog(repo)
889 clearfilecache(repo.unfiltered(), 'manifest')
888 clearfilecache(repo.unfiltered(), 'manifest')
890 repocleartagscache()
889 repocleartagscache()
891
890
892 def t():
891 def t():
893 return len(repo.tags())
892 return len(repo.tags())
894
893
895 timer(t, setup=s)
894 timer(t, setup=s)
896 fm.end()
895 fm.end()
897
896
898
897
899 @command(b'perf::ancestors|perfancestors', formatteropts)
898 @command(b'perf::ancestors|perfancestors', formatteropts)
900 def perfancestors(ui, repo, **opts):
899 def perfancestors(ui, repo, **opts):
901 opts = _byteskwargs(opts)
900 opts = _byteskwargs(opts)
902 timer, fm = gettimer(ui, opts)
901 timer, fm = gettimer(ui, opts)
903 heads = repo.changelog.headrevs()
902 heads = repo.changelog.headrevs()
904
903
905 def d():
904 def d():
906 for a in repo.changelog.ancestors(heads):
905 for a in repo.changelog.ancestors(heads):
907 pass
906 pass
908
907
909 timer(d)
908 timer(d)
910 fm.end()
909 fm.end()
911
910
912
911
913 @command(b'perf::ancestorset|perfancestorset', formatteropts)
912 @command(b'perf::ancestorset|perfancestorset', formatteropts)
914 def perfancestorset(ui, repo, revset, **opts):
913 def perfancestorset(ui, repo, revset, **opts):
915 opts = _byteskwargs(opts)
914 opts = _byteskwargs(opts)
916 timer, fm = gettimer(ui, opts)
915 timer, fm = gettimer(ui, opts)
917 revs = repo.revs(revset)
916 revs = repo.revs(revset)
918 heads = repo.changelog.headrevs()
917 heads = repo.changelog.headrevs()
919
918
920 def d():
919 def d():
921 s = repo.changelog.ancestors(heads)
920 s = repo.changelog.ancestors(heads)
922 for rev in revs:
921 for rev in revs:
923 rev in s
922 rev in s
924
923
925 timer(d)
924 timer(d)
926 fm.end()
925 fm.end()
927
926
928
927
929 @command(b'perf::discovery|perfdiscovery', formatteropts, b'PATH')
928 @command(b'perf::discovery|perfdiscovery', formatteropts, b'PATH')
930 def perfdiscovery(ui, repo, path, **opts):
929 def perfdiscovery(ui, repo, path, **opts):
931 """benchmark discovery between local repo and the peer at given path"""
930 """benchmark discovery between local repo and the peer at given path"""
932 repos = [repo, None]
931 repos = [repo, None]
933 timer, fm = gettimer(ui, opts)
932 timer, fm = gettimer(ui, opts)
934
933
935 try:
934 try:
936 from mercurial.utils.urlutil import get_unique_pull_path
935 from mercurial.utils.urlutil import get_unique_pull_path
937
936
938 path = get_unique_pull_path(b'perfdiscovery', repo, ui, path)[0]
937 path = get_unique_pull_path(b'perfdiscovery', repo, ui, path)[0]
939 except ImportError:
938 except ImportError:
940 path = ui.expandpath(path)
939 path = ui.expandpath(path)
941
940
942 def s():
941 def s():
943 repos[1] = hg.peer(ui, opts, path)
942 repos[1] = hg.peer(ui, opts, path)
944
943
945 def d():
944 def d():
946 setdiscovery.findcommonheads(ui, *repos)
945 setdiscovery.findcommonheads(ui, *repos)
947
946
948 timer(d, setup=s)
947 timer(d, setup=s)
949 fm.end()
948 fm.end()
950
949
951
950
952 @command(
951 @command(
953 b'perf::bookmarks|perfbookmarks',
952 b'perf::bookmarks|perfbookmarks',
954 formatteropts
953 formatteropts
955 + [
954 + [
956 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
955 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
957 ],
956 ],
958 )
957 )
959 def perfbookmarks(ui, repo, **opts):
958 def perfbookmarks(ui, repo, **opts):
960 """benchmark parsing bookmarks from disk to memory"""
959 """benchmark parsing bookmarks from disk to memory"""
961 opts = _byteskwargs(opts)
960 opts = _byteskwargs(opts)
962 timer, fm = gettimer(ui, opts)
961 timer, fm = gettimer(ui, opts)
963
962
964 clearrevlogs = opts[b'clear_revlogs']
963 clearrevlogs = opts[b'clear_revlogs']
965
964
966 def s():
965 def s():
967 if clearrevlogs:
966 if clearrevlogs:
968 clearchangelog(repo)
967 clearchangelog(repo)
969 clearfilecache(repo, b'_bookmarks')
968 clearfilecache(repo, b'_bookmarks')
970
969
971 def d():
970 def d():
972 repo._bookmarks
971 repo._bookmarks
973
972
974 timer(d, setup=s)
973 timer(d, setup=s)
975 fm.end()
974 fm.end()
976
975
977
976
978 @command(b'perf::bundleread|perfbundleread', formatteropts, b'BUNDLE')
977 @command(b'perf::bundleread|perfbundleread', formatteropts, b'BUNDLE')
979 def perfbundleread(ui, repo, bundlepath, **opts):
978 def perfbundleread(ui, repo, bundlepath, **opts):
980 """Benchmark reading of bundle files.
979 """Benchmark reading of bundle files.
981
980
982 This command is meant to isolate the I/O part of bundle reading as
981 This command is meant to isolate the I/O part of bundle reading as
983 much as possible.
982 much as possible.
984 """
983 """
985 from mercurial import (
984 from mercurial import (
986 bundle2,
985 bundle2,
987 exchange,
986 exchange,
988 streamclone,
987 streamclone,
989 )
988 )
990
989
991 opts = _byteskwargs(opts)
990 opts = _byteskwargs(opts)
992
991
993 def makebench(fn):
992 def makebench(fn):
994 def run():
993 def run():
995 with open(bundlepath, b'rb') as fh:
994 with open(bundlepath, b'rb') as fh:
996 bundle = exchange.readbundle(ui, fh, bundlepath)
995 bundle = exchange.readbundle(ui, fh, bundlepath)
997 fn(bundle)
996 fn(bundle)
998
997
999 return run
998 return run
1000
999
1001 def makereadnbytes(size):
1000 def makereadnbytes(size):
1002 def run():
1001 def run():
1003 with open(bundlepath, b'rb') as fh:
1002 with open(bundlepath, b'rb') as fh:
1004 bundle = exchange.readbundle(ui, fh, bundlepath)
1003 bundle = exchange.readbundle(ui, fh, bundlepath)
1005 while bundle.read(size):
1004 while bundle.read(size):
1006 pass
1005 pass
1007
1006
1008 return run
1007 return run
1009
1008
1010 def makestdioread(size):
1009 def makestdioread(size):
1011 def run():
1010 def run():
1012 with open(bundlepath, b'rb') as fh:
1011 with open(bundlepath, b'rb') as fh:
1013 while fh.read(size):
1012 while fh.read(size):
1014 pass
1013 pass
1015
1014
1016 return run
1015 return run
1017
1016
1018 # bundle1
1017 # bundle1
1019
1018
1020 def deltaiter(bundle):
1019 def deltaiter(bundle):
1021 for delta in bundle.deltaiter():
1020 for delta in bundle.deltaiter():
1022 pass
1021 pass
1023
1022
1024 def iterchunks(bundle):
1023 def iterchunks(bundle):
1025 for chunk in bundle.getchunks():
1024 for chunk in bundle.getchunks():
1026 pass
1025 pass
1027
1026
1028 # bundle2
1027 # bundle2
1029
1028
1030 def forwardchunks(bundle):
1029 def forwardchunks(bundle):
1031 for chunk in bundle._forwardchunks():
1030 for chunk in bundle._forwardchunks():
1032 pass
1031 pass
1033
1032
1034 def iterparts(bundle):
1033 def iterparts(bundle):
1035 for part in bundle.iterparts():
1034 for part in bundle.iterparts():
1036 pass
1035 pass
1037
1036
1038 def iterpartsseekable(bundle):
1037 def iterpartsseekable(bundle):
1039 for part in bundle.iterparts(seekable=True):
1038 for part in bundle.iterparts(seekable=True):
1040 pass
1039 pass
1041
1040
1042 def seek(bundle):
1041 def seek(bundle):
1043 for part in bundle.iterparts(seekable=True):
1042 for part in bundle.iterparts(seekable=True):
1044 part.seek(0, os.SEEK_END)
1043 part.seek(0, os.SEEK_END)
1045
1044
1046 def makepartreadnbytes(size):
1045 def makepartreadnbytes(size):
1047 def run():
1046 def run():
1048 with open(bundlepath, b'rb') as fh:
1047 with open(bundlepath, b'rb') as fh:
1049 bundle = exchange.readbundle(ui, fh, bundlepath)
1048 bundle = exchange.readbundle(ui, fh, bundlepath)
1050 for part in bundle.iterparts():
1049 for part in bundle.iterparts():
1051 while part.read(size):
1050 while part.read(size):
1052 pass
1051 pass
1053
1052
1054 return run
1053 return run
1055
1054
1056 benches = [
1055 benches = [
1057 (makestdioread(8192), b'read(8k)'),
1056 (makestdioread(8192), b'read(8k)'),
1058 (makestdioread(16384), b'read(16k)'),
1057 (makestdioread(16384), b'read(16k)'),
1059 (makestdioread(32768), b'read(32k)'),
1058 (makestdioread(32768), b'read(32k)'),
1060 (makestdioread(131072), b'read(128k)'),
1059 (makestdioread(131072), b'read(128k)'),
1061 ]
1060 ]
1062
1061
1063 with open(bundlepath, b'rb') as fh:
1062 with open(bundlepath, b'rb') as fh:
1064 bundle = exchange.readbundle(ui, fh, bundlepath)
1063 bundle = exchange.readbundle(ui, fh, bundlepath)
1065
1064
1066 if isinstance(bundle, changegroup.cg1unpacker):
1065 if isinstance(bundle, changegroup.cg1unpacker):
1067 benches.extend(
1066 benches.extend(
1068 [
1067 [
1069 (makebench(deltaiter), b'cg1 deltaiter()'),
1068 (makebench(deltaiter), b'cg1 deltaiter()'),
1070 (makebench(iterchunks), b'cg1 getchunks()'),
1069 (makebench(iterchunks), b'cg1 getchunks()'),
1071 (makereadnbytes(8192), b'cg1 read(8k)'),
1070 (makereadnbytes(8192), b'cg1 read(8k)'),
1072 (makereadnbytes(16384), b'cg1 read(16k)'),
1071 (makereadnbytes(16384), b'cg1 read(16k)'),
1073 (makereadnbytes(32768), b'cg1 read(32k)'),
1072 (makereadnbytes(32768), b'cg1 read(32k)'),
1074 (makereadnbytes(131072), b'cg1 read(128k)'),
1073 (makereadnbytes(131072), b'cg1 read(128k)'),
1075 ]
1074 ]
1076 )
1075 )
1077 elif isinstance(bundle, bundle2.unbundle20):
1076 elif isinstance(bundle, bundle2.unbundle20):
1078 benches.extend(
1077 benches.extend(
1079 [
1078 [
1080 (makebench(forwardchunks), b'bundle2 forwardchunks()'),
1079 (makebench(forwardchunks), b'bundle2 forwardchunks()'),
1081 (makebench(iterparts), b'bundle2 iterparts()'),
1080 (makebench(iterparts), b'bundle2 iterparts()'),
1082 (
1081 (
1083 makebench(iterpartsseekable),
1082 makebench(iterpartsseekable),
1084 b'bundle2 iterparts() seekable',
1083 b'bundle2 iterparts() seekable',
1085 ),
1084 ),
1086 (makebench(seek), b'bundle2 part seek()'),
1085 (makebench(seek), b'bundle2 part seek()'),
1087 (makepartreadnbytes(8192), b'bundle2 part read(8k)'),
1086 (makepartreadnbytes(8192), b'bundle2 part read(8k)'),
1088 (makepartreadnbytes(16384), b'bundle2 part read(16k)'),
1087 (makepartreadnbytes(16384), b'bundle2 part read(16k)'),
1089 (makepartreadnbytes(32768), b'bundle2 part read(32k)'),
1088 (makepartreadnbytes(32768), b'bundle2 part read(32k)'),
1090 (makepartreadnbytes(131072), b'bundle2 part read(128k)'),
1089 (makepartreadnbytes(131072), b'bundle2 part read(128k)'),
1091 ]
1090 ]
1092 )
1091 )
1093 elif isinstance(bundle, streamclone.streamcloneapplier):
1092 elif isinstance(bundle, streamclone.streamcloneapplier):
1094 raise error.Abort(b'stream clone bundles not supported')
1093 raise error.Abort(b'stream clone bundles not supported')
1095 else:
1094 else:
1096 raise error.Abort(b'unhandled bundle type: %s' % type(bundle))
1095 raise error.Abort(b'unhandled bundle type: %s' % type(bundle))
1097
1096
1098 for fn, title in benches:
1097 for fn, title in benches:
1099 timer, fm = gettimer(ui, opts)
1098 timer, fm = gettimer(ui, opts)
1100 timer(fn, title=title)
1099 timer(fn, title=title)
1101 fm.end()
1100 fm.end()
1102
1101
1103
1102
1104 @command(
1103 @command(
1105 b'perf::changegroupchangelog|perfchangegroupchangelog',
1104 b'perf::changegroupchangelog|perfchangegroupchangelog',
1106 formatteropts
1105 formatteropts
1107 + [
1106 + [
1108 (b'', b'cgversion', b'02', b'changegroup version'),
1107 (b'', b'cgversion', b'02', b'changegroup version'),
1109 (b'r', b'rev', b'', b'revisions to add to changegroup'),
1108 (b'r', b'rev', b'', b'revisions to add to changegroup'),
1110 ],
1109 ],
1111 )
1110 )
1112 def perfchangegroupchangelog(ui, repo, cgversion=b'02', rev=None, **opts):
1111 def perfchangegroupchangelog(ui, repo, cgversion=b'02', rev=None, **opts):
1113 """Benchmark producing a changelog group for a changegroup.
1112 """Benchmark producing a changelog group for a changegroup.
1114
1113
1115 This measures the time spent processing the changelog during a
1114 This measures the time spent processing the changelog during a
1116 bundle operation. This occurs during `hg bundle` and on a server
1115 bundle operation. This occurs during `hg bundle` and on a server
1117 processing a `getbundle` wire protocol request (handles clones
1116 processing a `getbundle` wire protocol request (handles clones
1118 and pull requests).
1117 and pull requests).
1119
1118
1120 By default, all revisions are added to the changegroup.
1119 By default, all revisions are added to the changegroup.
1121 """
1120 """
1122 opts = _byteskwargs(opts)
1121 opts = _byteskwargs(opts)
1123 cl = repo.changelog
1122 cl = repo.changelog
1124 nodes = [cl.lookup(r) for r in repo.revs(rev or b'all()')]
1123 nodes = [cl.lookup(r) for r in repo.revs(rev or b'all()')]
1125 bundler = changegroup.getbundler(cgversion, repo)
1124 bundler = changegroup.getbundler(cgversion, repo)
1126
1125
1127 def d():
1126 def d():
1128 state, chunks = bundler._generatechangelog(cl, nodes)
1127 state, chunks = bundler._generatechangelog(cl, nodes)
1129 for chunk in chunks:
1128 for chunk in chunks:
1130 pass
1129 pass
1131
1130
1132 timer, fm = gettimer(ui, opts)
1131 timer, fm = gettimer(ui, opts)
1133
1132
1134 # Terminal printing can interfere with timing. So disable it.
1133 # Terminal printing can interfere with timing. So disable it.
1135 with ui.configoverride({(b'progress', b'disable'): True}):
1134 with ui.configoverride({(b'progress', b'disable'): True}):
1136 timer(d)
1135 timer(d)
1137
1136
1138 fm.end()
1137 fm.end()
1139
1138
1140
1139
1141 @command(b'perf::dirs|perfdirs', formatteropts)
1140 @command(b'perf::dirs|perfdirs', formatteropts)
1142 def perfdirs(ui, repo, **opts):
1141 def perfdirs(ui, repo, **opts):
1143 opts = _byteskwargs(opts)
1142 opts = _byteskwargs(opts)
1144 timer, fm = gettimer(ui, opts)
1143 timer, fm = gettimer(ui, opts)
1145 dirstate = repo.dirstate
1144 dirstate = repo.dirstate
1146 b'a' in dirstate
1145 b'a' in dirstate
1147
1146
1148 def d():
1147 def d():
1149 dirstate.hasdir(b'a')
1148 dirstate.hasdir(b'a')
1150 try:
1149 try:
1151 del dirstate._map._dirs
1150 del dirstate._map._dirs
1152 except AttributeError:
1151 except AttributeError:
1153 pass
1152 pass
1154
1153
1155 timer(d)
1154 timer(d)
1156 fm.end()
1155 fm.end()
1157
1156
1158
1157
1159 @command(
1158 @command(
1160 b'perf::dirstate|perfdirstate',
1159 b'perf::dirstate|perfdirstate',
1161 [
1160 [
1162 (
1161 (
1163 b'',
1162 b'',
1164 b'iteration',
1163 b'iteration',
1165 None,
1164 None,
1166 b'benchmark a full iteration for the dirstate',
1165 b'benchmark a full iteration for the dirstate',
1167 ),
1166 ),
1168 (
1167 (
1169 b'',
1168 b'',
1170 b'contains',
1169 b'contains',
1171 None,
1170 None,
1172 b'benchmark a large amount of `nf in dirstate` calls',
1171 b'benchmark a large amount of `nf in dirstate` calls',
1173 ),
1172 ),
1174 ]
1173 ]
1175 + formatteropts,
1174 + formatteropts,
1176 )
1175 )
1177 def perfdirstate(ui, repo, **opts):
1176 def perfdirstate(ui, repo, **opts):
1178 """benchmap the time of various distate operations
1177 """benchmap the time of various distate operations
1179
1178
1180 By default benchmark the time necessary to load a dirstate from scratch.
1179 By default benchmark the time necessary to load a dirstate from scratch.
1181 The dirstate is loaded to the point were a "contains" request can be
1180 The dirstate is loaded to the point were a "contains" request can be
1182 answered.
1181 answered.
1183 """
1182 """
1184 opts = _byteskwargs(opts)
1183 opts = _byteskwargs(opts)
1185 timer, fm = gettimer(ui, opts)
1184 timer, fm = gettimer(ui, opts)
1186 b"a" in repo.dirstate
1185 b"a" in repo.dirstate
1187
1186
1188 if opts[b'iteration'] and opts[b'contains']:
1187 if opts[b'iteration'] and opts[b'contains']:
1189 msg = b'only specify one of --iteration or --contains'
1188 msg = b'only specify one of --iteration or --contains'
1190 raise error.Abort(msg)
1189 raise error.Abort(msg)
1191
1190
1192 if opts[b'iteration']:
1191 if opts[b'iteration']:
1193 setup = None
1192 setup = None
1194 dirstate = repo.dirstate
1193 dirstate = repo.dirstate
1195
1194
1196 def d():
1195 def d():
1197 for f in dirstate:
1196 for f in dirstate:
1198 pass
1197 pass
1199
1198
1200 elif opts[b'contains']:
1199 elif opts[b'contains']:
1201 setup = None
1200 setup = None
1202 dirstate = repo.dirstate
1201 dirstate = repo.dirstate
1203 allfiles = list(dirstate)
1202 allfiles = list(dirstate)
1204 # also add file path that will be "missing" from the dirstate
1203 # also add file path that will be "missing" from the dirstate
1205 allfiles.extend([f[::-1] for f in allfiles])
1204 allfiles.extend([f[::-1] for f in allfiles])
1206
1205
1207 def d():
1206 def d():
1208 for f in allfiles:
1207 for f in allfiles:
1209 f in dirstate
1208 f in dirstate
1210
1209
1211 else:
1210 else:
1212
1211
1213 def setup():
1212 def setup():
1214 repo.dirstate.invalidate()
1213 repo.dirstate.invalidate()
1215
1214
1216 def d():
1215 def d():
1217 b"a" in repo.dirstate
1216 b"a" in repo.dirstate
1218
1217
1219 timer(d, setup=setup)
1218 timer(d, setup=setup)
1220 fm.end()
1219 fm.end()
1221
1220
1222
1221
1223 @command(b'perf::dirstatedirs|perfdirstatedirs', formatteropts)
1222 @command(b'perf::dirstatedirs|perfdirstatedirs', formatteropts)
1224 def perfdirstatedirs(ui, repo, **opts):
1223 def perfdirstatedirs(ui, repo, **opts):
1225 """benchmap a 'dirstate.hasdir' call from an empty `dirs` cache"""
1224 """benchmap a 'dirstate.hasdir' call from an empty `dirs` cache"""
1226 opts = _byteskwargs(opts)
1225 opts = _byteskwargs(opts)
1227 timer, fm = gettimer(ui, opts)
1226 timer, fm = gettimer(ui, opts)
1228 repo.dirstate.hasdir(b"a")
1227 repo.dirstate.hasdir(b"a")
1229
1228
1230 def setup():
1229 def setup():
1231 try:
1230 try:
1232 del repo.dirstate._map._dirs
1231 del repo.dirstate._map._dirs
1233 except AttributeError:
1232 except AttributeError:
1234 pass
1233 pass
1235
1234
1236 def d():
1235 def d():
1237 repo.dirstate.hasdir(b"a")
1236 repo.dirstate.hasdir(b"a")
1238
1237
1239 timer(d, setup=setup)
1238 timer(d, setup=setup)
1240 fm.end()
1239 fm.end()
1241
1240
1242
1241
1243 @command(b'perf::dirstatefoldmap|perfdirstatefoldmap', formatteropts)
1242 @command(b'perf::dirstatefoldmap|perfdirstatefoldmap', formatteropts)
1244 def perfdirstatefoldmap(ui, repo, **opts):
1243 def perfdirstatefoldmap(ui, repo, **opts):
1245 """benchmap a `dirstate._map.filefoldmap.get()` request
1244 """benchmap a `dirstate._map.filefoldmap.get()` request
1246
1245
1247 The dirstate filefoldmap cache is dropped between every request.
1246 The dirstate filefoldmap cache is dropped between every request.
1248 """
1247 """
1249 opts = _byteskwargs(opts)
1248 opts = _byteskwargs(opts)
1250 timer, fm = gettimer(ui, opts)
1249 timer, fm = gettimer(ui, opts)
1251 dirstate = repo.dirstate
1250 dirstate = repo.dirstate
1252 dirstate._map.filefoldmap.get(b'a')
1251 dirstate._map.filefoldmap.get(b'a')
1253
1252
1254 def setup():
1253 def setup():
1255 del dirstate._map.filefoldmap
1254 del dirstate._map.filefoldmap
1256
1255
1257 def d():
1256 def d():
1258 dirstate._map.filefoldmap.get(b'a')
1257 dirstate._map.filefoldmap.get(b'a')
1259
1258
1260 timer(d, setup=setup)
1259 timer(d, setup=setup)
1261 fm.end()
1260 fm.end()
1262
1261
1263
1262
1264 @command(b'perf::dirfoldmap|perfdirfoldmap', formatteropts)
1263 @command(b'perf::dirfoldmap|perfdirfoldmap', formatteropts)
1265 def perfdirfoldmap(ui, repo, **opts):
1264 def perfdirfoldmap(ui, repo, **opts):
1266 """benchmap a `dirstate._map.dirfoldmap.get()` request
1265 """benchmap a `dirstate._map.dirfoldmap.get()` request
1267
1266
1268 The dirstate dirfoldmap cache is dropped between every request.
1267 The dirstate dirfoldmap cache is dropped between every request.
1269 """
1268 """
1270 opts = _byteskwargs(opts)
1269 opts = _byteskwargs(opts)
1271 timer, fm = gettimer(ui, opts)
1270 timer, fm = gettimer(ui, opts)
1272 dirstate = repo.dirstate
1271 dirstate = repo.dirstate
1273 dirstate._map.dirfoldmap.get(b'a')
1272 dirstate._map.dirfoldmap.get(b'a')
1274
1273
1275 def setup():
1274 def setup():
1276 del dirstate._map.dirfoldmap
1275 del dirstate._map.dirfoldmap
1277 try:
1276 try:
1278 del dirstate._map._dirs
1277 del dirstate._map._dirs
1279 except AttributeError:
1278 except AttributeError:
1280 pass
1279 pass
1281
1280
1282 def d():
1281 def d():
1283 dirstate._map.dirfoldmap.get(b'a')
1282 dirstate._map.dirfoldmap.get(b'a')
1284
1283
1285 timer(d, setup=setup)
1284 timer(d, setup=setup)
1286 fm.end()
1285 fm.end()
1287
1286
1288
1287
1289 @command(b'perf::dirstatewrite|perfdirstatewrite', formatteropts)
1288 @command(b'perf::dirstatewrite|perfdirstatewrite', formatteropts)
1290 def perfdirstatewrite(ui, repo, **opts):
1289 def perfdirstatewrite(ui, repo, **opts):
1291 """benchmap the time it take to write a dirstate on disk"""
1290 """benchmap the time it take to write a dirstate on disk"""
1292 opts = _byteskwargs(opts)
1291 opts = _byteskwargs(opts)
1293 timer, fm = gettimer(ui, opts)
1292 timer, fm = gettimer(ui, opts)
1294 ds = repo.dirstate
1293 ds = repo.dirstate
1295 b"a" in ds
1294 b"a" in ds
1296
1295
1297 def setup():
1296 def setup():
1298 ds._dirty = True
1297 ds._dirty = True
1299
1298
1300 def d():
1299 def d():
1301 ds.write(repo.currenttransaction())
1300 ds.write(repo.currenttransaction())
1302
1301
1303 timer(d, setup=setup)
1302 timer(d, setup=setup)
1304 fm.end()
1303 fm.end()
1305
1304
1306
1305
1307 def _getmergerevs(repo, opts):
1306 def _getmergerevs(repo, opts):
1308 """parse command argument to return rev involved in merge
1307 """parse command argument to return rev involved in merge
1309
1308
1310 input: options dictionnary with `rev`, `from` and `bse`
1309 input: options dictionnary with `rev`, `from` and `bse`
1311 output: (localctx, otherctx, basectx)
1310 output: (localctx, otherctx, basectx)
1312 """
1311 """
1313 if opts[b'from']:
1312 if opts[b'from']:
1314 fromrev = scmutil.revsingle(repo, opts[b'from'])
1313 fromrev = scmutil.revsingle(repo, opts[b'from'])
1315 wctx = repo[fromrev]
1314 wctx = repo[fromrev]
1316 else:
1315 else:
1317 wctx = repo[None]
1316 wctx = repo[None]
1318 # we don't want working dir files to be stat'd in the benchmark, so
1317 # we don't want working dir files to be stat'd in the benchmark, so
1319 # prime that cache
1318 # prime that cache
1320 wctx.dirty()
1319 wctx.dirty()
1321 rctx = scmutil.revsingle(repo, opts[b'rev'], opts[b'rev'])
1320 rctx = scmutil.revsingle(repo, opts[b'rev'], opts[b'rev'])
1322 if opts[b'base']:
1321 if opts[b'base']:
1323 fromrev = scmutil.revsingle(repo, opts[b'base'])
1322 fromrev = scmutil.revsingle(repo, opts[b'base'])
1324 ancestor = repo[fromrev]
1323 ancestor = repo[fromrev]
1325 else:
1324 else:
1326 ancestor = wctx.ancestor(rctx)
1325 ancestor = wctx.ancestor(rctx)
1327 return (wctx, rctx, ancestor)
1326 return (wctx, rctx, ancestor)
1328
1327
1329
1328
1330 @command(
1329 @command(
1331 b'perf::mergecalculate|perfmergecalculate',
1330 b'perf::mergecalculate|perfmergecalculate',
1332 [
1331 [
1333 (b'r', b'rev', b'.', b'rev to merge against'),
1332 (b'r', b'rev', b'.', b'rev to merge against'),
1334 (b'', b'from', b'', b'rev to merge from'),
1333 (b'', b'from', b'', b'rev to merge from'),
1335 (b'', b'base', b'', b'the revision to use as base'),
1334 (b'', b'base', b'', b'the revision to use as base'),
1336 ]
1335 ]
1337 + formatteropts,
1336 + formatteropts,
1338 )
1337 )
1339 def perfmergecalculate(ui, repo, **opts):
1338 def perfmergecalculate(ui, repo, **opts):
1340 opts = _byteskwargs(opts)
1339 opts = _byteskwargs(opts)
1341 timer, fm = gettimer(ui, opts)
1340 timer, fm = gettimer(ui, opts)
1342
1341
1343 wctx, rctx, ancestor = _getmergerevs(repo, opts)
1342 wctx, rctx, ancestor = _getmergerevs(repo, opts)
1344
1343
1345 def d():
1344 def d():
1346 # acceptremote is True because we don't want prompts in the middle of
1345 # acceptremote is True because we don't want prompts in the middle of
1347 # our benchmark
1346 # our benchmark
1348 merge.calculateupdates(
1347 merge.calculateupdates(
1349 repo,
1348 repo,
1350 wctx,
1349 wctx,
1351 rctx,
1350 rctx,
1352 [ancestor],
1351 [ancestor],
1353 branchmerge=False,
1352 branchmerge=False,
1354 force=False,
1353 force=False,
1355 acceptremote=True,
1354 acceptremote=True,
1356 followcopies=True,
1355 followcopies=True,
1357 )
1356 )
1358
1357
1359 timer(d)
1358 timer(d)
1360 fm.end()
1359 fm.end()
1361
1360
1362
1361
1363 @command(
1362 @command(
1364 b'perf::mergecopies|perfmergecopies',
1363 b'perf::mergecopies|perfmergecopies',
1365 [
1364 [
1366 (b'r', b'rev', b'.', b'rev to merge against'),
1365 (b'r', b'rev', b'.', b'rev to merge against'),
1367 (b'', b'from', b'', b'rev to merge from'),
1366 (b'', b'from', b'', b'rev to merge from'),
1368 (b'', b'base', b'', b'the revision to use as base'),
1367 (b'', b'base', b'', b'the revision to use as base'),
1369 ]
1368 ]
1370 + formatteropts,
1369 + formatteropts,
1371 )
1370 )
1372 def perfmergecopies(ui, repo, **opts):
1371 def perfmergecopies(ui, repo, **opts):
1373 """measure runtime of `copies.mergecopies`"""
1372 """measure runtime of `copies.mergecopies`"""
1374 opts = _byteskwargs(opts)
1373 opts = _byteskwargs(opts)
1375 timer, fm = gettimer(ui, opts)
1374 timer, fm = gettimer(ui, opts)
1376 wctx, rctx, ancestor = _getmergerevs(repo, opts)
1375 wctx, rctx, ancestor = _getmergerevs(repo, opts)
1377
1376
1378 def d():
1377 def d():
1379 # acceptremote is True because we don't want prompts in the middle of
1378 # acceptremote is True because we don't want prompts in the middle of
1380 # our benchmark
1379 # our benchmark
1381 copies.mergecopies(repo, wctx, rctx, ancestor)
1380 copies.mergecopies(repo, wctx, rctx, ancestor)
1382
1381
1383 timer(d)
1382 timer(d)
1384 fm.end()
1383 fm.end()
1385
1384
1386
1385
1387 @command(b'perf::pathcopies|perfpathcopies', [], b"REV REV")
1386 @command(b'perf::pathcopies|perfpathcopies', [], b"REV REV")
1388 def perfpathcopies(ui, repo, rev1, rev2, **opts):
1387 def perfpathcopies(ui, repo, rev1, rev2, **opts):
1389 """benchmark the copy tracing logic"""
1388 """benchmark the copy tracing logic"""
1390 opts = _byteskwargs(opts)
1389 opts = _byteskwargs(opts)
1391 timer, fm = gettimer(ui, opts)
1390 timer, fm = gettimer(ui, opts)
1392 ctx1 = scmutil.revsingle(repo, rev1, rev1)
1391 ctx1 = scmutil.revsingle(repo, rev1, rev1)
1393 ctx2 = scmutil.revsingle(repo, rev2, rev2)
1392 ctx2 = scmutil.revsingle(repo, rev2, rev2)
1394
1393
1395 def d():
1394 def d():
1396 copies.pathcopies(ctx1, ctx2)
1395 copies.pathcopies(ctx1, ctx2)
1397
1396
1398 timer(d)
1397 timer(d)
1399 fm.end()
1398 fm.end()
1400
1399
1401
1400
1402 @command(
1401 @command(
1403 b'perf::phases|perfphases',
1402 b'perf::phases|perfphases',
1404 [
1403 [
1405 (b'', b'full', False, b'include file reading time too'),
1404 (b'', b'full', False, b'include file reading time too'),
1406 ],
1405 ],
1407 b"",
1406 b"",
1408 )
1407 )
1409 def perfphases(ui, repo, **opts):
1408 def perfphases(ui, repo, **opts):
1410 """benchmark phasesets computation"""
1409 """benchmark phasesets computation"""
1411 opts = _byteskwargs(opts)
1410 opts = _byteskwargs(opts)
1412 timer, fm = gettimer(ui, opts)
1411 timer, fm = gettimer(ui, opts)
1413 _phases = repo._phasecache
1412 _phases = repo._phasecache
1414 full = opts.get(b'full')
1413 full = opts.get(b'full')
1415
1414
1416 def d():
1415 def d():
1417 phases = _phases
1416 phases = _phases
1418 if full:
1417 if full:
1419 clearfilecache(repo, b'_phasecache')
1418 clearfilecache(repo, b'_phasecache')
1420 phases = repo._phasecache
1419 phases = repo._phasecache
1421 phases.invalidate()
1420 phases.invalidate()
1422 phases.loadphaserevs(repo)
1421 phases.loadphaserevs(repo)
1423
1422
1424 timer(d)
1423 timer(d)
1425 fm.end()
1424 fm.end()
1426
1425
1427
1426
1428 @command(b'perf::phasesremote|perfphasesremote', [], b"[DEST]")
1427 @command(b'perf::phasesremote|perfphasesremote', [], b"[DEST]")
1429 def perfphasesremote(ui, repo, dest=None, **opts):
1428 def perfphasesremote(ui, repo, dest=None, **opts):
1430 """benchmark time needed to analyse phases of the remote server"""
1429 """benchmark time needed to analyse phases of the remote server"""
1431 from mercurial.node import bin
1430 from mercurial.node import bin
1432 from mercurial import (
1431 from mercurial import (
1433 exchange,
1432 exchange,
1434 hg,
1433 hg,
1435 phases,
1434 phases,
1436 )
1435 )
1437
1436
1438 opts = _byteskwargs(opts)
1437 opts = _byteskwargs(opts)
1439 timer, fm = gettimer(ui, opts)
1438 timer, fm = gettimer(ui, opts)
1440
1439
1441 path = ui.getpath(dest, default=(b'default-push', b'default'))
1440 path = ui.getpath(dest, default=(b'default-push', b'default'))
1442 if not path:
1441 if not path:
1443 raise error.Abort(
1442 raise error.Abort(
1444 b'default repository not configured!',
1443 b'default repository not configured!',
1445 hint=b"see 'hg help config.paths'",
1444 hint=b"see 'hg help config.paths'",
1446 )
1445 )
1447 dest = path.pushloc or path.loc
1446 dest = path.pushloc or path.loc
1448 ui.statusnoi18n(b'analysing phase of %s\n' % util.hidepassword(dest))
1447 ui.statusnoi18n(b'analysing phase of %s\n' % util.hidepassword(dest))
1449 other = hg.peer(repo, opts, dest)
1448 other = hg.peer(repo, opts, dest)
1450
1449
1451 # easier to perform discovery through the operation
1450 # easier to perform discovery through the operation
1452 op = exchange.pushoperation(repo, other)
1451 op = exchange.pushoperation(repo, other)
1453 exchange._pushdiscoverychangeset(op)
1452 exchange._pushdiscoverychangeset(op)
1454
1453
1455 remotesubset = op.fallbackheads
1454 remotesubset = op.fallbackheads
1456
1455
1457 with other.commandexecutor() as e:
1456 with other.commandexecutor() as e:
1458 remotephases = e.callcommand(
1457 remotephases = e.callcommand(
1459 b'listkeys', {b'namespace': b'phases'}
1458 b'listkeys', {b'namespace': b'phases'}
1460 ).result()
1459 ).result()
1461 del other
1460 del other
1462 publishing = remotephases.get(b'publishing', False)
1461 publishing = remotephases.get(b'publishing', False)
1463 if publishing:
1462 if publishing:
1464 ui.statusnoi18n(b'publishing: yes\n')
1463 ui.statusnoi18n(b'publishing: yes\n')
1465 else:
1464 else:
1466 ui.statusnoi18n(b'publishing: no\n')
1465 ui.statusnoi18n(b'publishing: no\n')
1467
1466
1468 has_node = getattr(repo.changelog.index, 'has_node', None)
1467 has_node = getattr(repo.changelog.index, 'has_node', None)
1469 if has_node is None:
1468 if has_node is None:
1470 has_node = repo.changelog.nodemap.__contains__
1469 has_node = repo.changelog.nodemap.__contains__
1471 nonpublishroots = 0
1470 nonpublishroots = 0
1472 for nhex, phase in remotephases.iteritems():
1471 for nhex, phase in remotephases.iteritems():
1473 if nhex == b'publishing': # ignore data related to publish option
1472 if nhex == b'publishing': # ignore data related to publish option
1474 continue
1473 continue
1475 node = bin(nhex)
1474 node = bin(nhex)
1476 if has_node(node) and int(phase):
1475 if has_node(node) and int(phase):
1477 nonpublishroots += 1
1476 nonpublishroots += 1
1478 ui.statusnoi18n(b'number of roots: %d\n' % len(remotephases))
1477 ui.statusnoi18n(b'number of roots: %d\n' % len(remotephases))
1479 ui.statusnoi18n(b'number of known non public roots: %d\n' % nonpublishroots)
1478 ui.statusnoi18n(b'number of known non public roots: %d\n' % nonpublishroots)
1480
1479
1481 def d():
1480 def d():
1482 phases.remotephasessummary(repo, remotesubset, remotephases)
1481 phases.remotephasessummary(repo, remotesubset, remotephases)
1483
1482
1484 timer(d)
1483 timer(d)
1485 fm.end()
1484 fm.end()
1486
1485
1487
1486
1488 @command(
1487 @command(
1489 b'perf::manifest|perfmanifest',
1488 b'perf::manifest|perfmanifest',
1490 [
1489 [
1491 (b'm', b'manifest-rev', False, b'Look up a manifest node revision'),
1490 (b'm', b'manifest-rev', False, b'Look up a manifest node revision'),
1492 (b'', b'clear-disk', False, b'clear on-disk caches too'),
1491 (b'', b'clear-disk', False, b'clear on-disk caches too'),
1493 ]
1492 ]
1494 + formatteropts,
1493 + formatteropts,
1495 b'REV|NODE',
1494 b'REV|NODE',
1496 )
1495 )
1497 def perfmanifest(ui, repo, rev, manifest_rev=False, clear_disk=False, **opts):
1496 def perfmanifest(ui, repo, rev, manifest_rev=False, clear_disk=False, **opts):
1498 """benchmark the time to read a manifest from disk and return a usable
1497 """benchmark the time to read a manifest from disk and return a usable
1499 dict-like object
1498 dict-like object
1500
1499
1501 Manifest caches are cleared before retrieval."""
1500 Manifest caches are cleared before retrieval."""
1502 opts = _byteskwargs(opts)
1501 opts = _byteskwargs(opts)
1503 timer, fm = gettimer(ui, opts)
1502 timer, fm = gettimer(ui, opts)
1504 if not manifest_rev:
1503 if not manifest_rev:
1505 ctx = scmutil.revsingle(repo, rev, rev)
1504 ctx = scmutil.revsingle(repo, rev, rev)
1506 t = ctx.manifestnode()
1505 t = ctx.manifestnode()
1507 else:
1506 else:
1508 from mercurial.node import bin
1507 from mercurial.node import bin
1509
1508
1510 if len(rev) == 40:
1509 if len(rev) == 40:
1511 t = bin(rev)
1510 t = bin(rev)
1512 else:
1511 else:
1513 try:
1512 try:
1514 rev = int(rev)
1513 rev = int(rev)
1515
1514
1516 if util.safehasattr(repo.manifestlog, b'getstorage'):
1515 if util.safehasattr(repo.manifestlog, b'getstorage'):
1517 t = repo.manifestlog.getstorage(b'').node(rev)
1516 t = repo.manifestlog.getstorage(b'').node(rev)
1518 else:
1517 else:
1519 t = repo.manifestlog._revlog.lookup(rev)
1518 t = repo.manifestlog._revlog.lookup(rev)
1520 except ValueError:
1519 except ValueError:
1521 raise error.Abort(
1520 raise error.Abort(
1522 b'manifest revision must be integer or full node'
1521 b'manifest revision must be integer or full node'
1523 )
1522 )
1524
1523
1525 def d():
1524 def d():
1526 repo.manifestlog.clearcaches(clear_persisted_data=clear_disk)
1525 repo.manifestlog.clearcaches(clear_persisted_data=clear_disk)
1527 repo.manifestlog[t].read()
1526 repo.manifestlog[t].read()
1528
1527
1529 timer(d)
1528 timer(d)
1530 fm.end()
1529 fm.end()
1531
1530
1532
1531
1533 @command(b'perf::changeset|perfchangeset', formatteropts)
1532 @command(b'perf::changeset|perfchangeset', formatteropts)
1534 def perfchangeset(ui, repo, rev, **opts):
1533 def perfchangeset(ui, repo, rev, **opts):
1535 opts = _byteskwargs(opts)
1534 opts = _byteskwargs(opts)
1536 timer, fm = gettimer(ui, opts)
1535 timer, fm = gettimer(ui, opts)
1537 n = scmutil.revsingle(repo, rev).node()
1536 n = scmutil.revsingle(repo, rev).node()
1538
1537
1539 def d():
1538 def d():
1540 repo.changelog.read(n)
1539 repo.changelog.read(n)
1541 # repo.changelog._cache = None
1540 # repo.changelog._cache = None
1542
1541
1543 timer(d)
1542 timer(d)
1544 fm.end()
1543 fm.end()
1545
1544
1546
1545
1547 @command(b'perf::ignore|perfignore', formatteropts)
1546 @command(b'perf::ignore|perfignore', formatteropts)
1548 def perfignore(ui, repo, **opts):
1547 def perfignore(ui, repo, **opts):
1549 """benchmark operation related to computing ignore"""
1548 """benchmark operation related to computing ignore"""
1550 opts = _byteskwargs(opts)
1549 opts = _byteskwargs(opts)
1551 timer, fm = gettimer(ui, opts)
1550 timer, fm = gettimer(ui, opts)
1552 dirstate = repo.dirstate
1551 dirstate = repo.dirstate
1553
1552
1554 def setupone():
1553 def setupone():
1555 dirstate.invalidate()
1554 dirstate.invalidate()
1556 clearfilecache(dirstate, b'_ignore')
1555 clearfilecache(dirstate, b'_ignore')
1557
1556
1558 def runone():
1557 def runone():
1559 dirstate._ignore
1558 dirstate._ignore
1560
1559
1561 timer(runone, setup=setupone, title=b"load")
1560 timer(runone, setup=setupone, title=b"load")
1562 fm.end()
1561 fm.end()
1563
1562
1564
1563
1565 @command(
1564 @command(
1566 b'perf::index|perfindex',
1565 b'perf::index|perfindex',
1567 [
1566 [
1568 (b'', b'rev', [], b'revision to be looked up (default tip)'),
1567 (b'', b'rev', [], b'revision to be looked up (default tip)'),
1569 (b'', b'no-lookup', None, b'do not revision lookup post creation'),
1568 (b'', b'no-lookup', None, b'do not revision lookup post creation'),
1570 ]
1569 ]
1571 + formatteropts,
1570 + formatteropts,
1572 )
1571 )
1573 def perfindex(ui, repo, **opts):
1572 def perfindex(ui, repo, **opts):
1574 """benchmark index creation time followed by a lookup
1573 """benchmark index creation time followed by a lookup
1575
1574
1576 The default is to look `tip` up. Depending on the index implementation,
1575 The default is to look `tip` up. Depending on the index implementation,
1577 the revision looked up can matters. For example, an implementation
1576 the revision looked up can matters. For example, an implementation
1578 scanning the index will have a faster lookup time for `--rev tip` than for
1577 scanning the index will have a faster lookup time for `--rev tip` than for
1579 `--rev 0`. The number of looked up revisions and their order can also
1578 `--rev 0`. The number of looked up revisions and their order can also
1580 matters.
1579 matters.
1581
1580
1582 Example of useful set to test:
1581 Example of useful set to test:
1583
1582
1584 * tip
1583 * tip
1585 * 0
1584 * 0
1586 * -10:
1585 * -10:
1587 * :10
1586 * :10
1588 * -10: + :10
1587 * -10: + :10
1589 * :10: + -10:
1588 * :10: + -10:
1590 * -10000:
1589 * -10000:
1591 * -10000: + 0
1590 * -10000: + 0
1592
1591
1593 It is not currently possible to check for lookup of a missing node. For
1592 It is not currently possible to check for lookup of a missing node. For
1594 deeper lookup benchmarking, checkout the `perfnodemap` command."""
1593 deeper lookup benchmarking, checkout the `perfnodemap` command."""
1595 import mercurial.revlog
1594 import mercurial.revlog
1596
1595
1597 opts = _byteskwargs(opts)
1596 opts = _byteskwargs(opts)
1598 timer, fm = gettimer(ui, opts)
1597 timer, fm = gettimer(ui, opts)
1599 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1598 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1600 if opts[b'no_lookup']:
1599 if opts[b'no_lookup']:
1601 if opts['rev']:
1600 if opts['rev']:
1602 raise error.Abort('--no-lookup and --rev are mutually exclusive')
1601 raise error.Abort('--no-lookup and --rev are mutually exclusive')
1603 nodes = []
1602 nodes = []
1604 elif not opts[b'rev']:
1603 elif not opts[b'rev']:
1605 nodes = [repo[b"tip"].node()]
1604 nodes = [repo[b"tip"].node()]
1606 else:
1605 else:
1607 revs = scmutil.revrange(repo, opts[b'rev'])
1606 revs = scmutil.revrange(repo, opts[b'rev'])
1608 cl = repo.changelog
1607 cl = repo.changelog
1609 nodes = [cl.node(r) for r in revs]
1608 nodes = [cl.node(r) for r in revs]
1610
1609
1611 unfi = repo.unfiltered()
1610 unfi = repo.unfiltered()
1612 # find the filecache func directly
1611 # find the filecache func directly
1613 # This avoid polluting the benchmark with the filecache logic
1612 # This avoid polluting the benchmark with the filecache logic
1614 makecl = unfi.__class__.changelog.func
1613 makecl = unfi.__class__.changelog.func
1615
1614
1616 def setup():
1615 def setup():
1617 # probably not necessary, but for good measure
1616 # probably not necessary, but for good measure
1618 clearchangelog(unfi)
1617 clearchangelog(unfi)
1619
1618
1620 def d():
1619 def d():
1621 cl = makecl(unfi)
1620 cl = makecl(unfi)
1622 for n in nodes:
1621 for n in nodes:
1623 cl.rev(n)
1622 cl.rev(n)
1624
1623
1625 timer(d, setup=setup)
1624 timer(d, setup=setup)
1626 fm.end()
1625 fm.end()
1627
1626
1628
1627
1629 @command(
1628 @command(
1630 b'perf::nodemap|perfnodemap',
1629 b'perf::nodemap|perfnodemap',
1631 [
1630 [
1632 (b'', b'rev', [], b'revision to be looked up (default tip)'),
1631 (b'', b'rev', [], b'revision to be looked up (default tip)'),
1633 (b'', b'clear-caches', True, b'clear revlog cache between calls'),
1632 (b'', b'clear-caches', True, b'clear revlog cache between calls'),
1634 ]
1633 ]
1635 + formatteropts,
1634 + formatteropts,
1636 )
1635 )
1637 def perfnodemap(ui, repo, **opts):
1636 def perfnodemap(ui, repo, **opts):
1638 """benchmark the time necessary to look up revision from a cold nodemap
1637 """benchmark the time necessary to look up revision from a cold nodemap
1639
1638
1640 Depending on the implementation, the amount and order of revision we look
1639 Depending on the implementation, the amount and order of revision we look
1641 up can varies. Example of useful set to test:
1640 up can varies. Example of useful set to test:
1642 * tip
1641 * tip
1643 * 0
1642 * 0
1644 * -10:
1643 * -10:
1645 * :10
1644 * :10
1646 * -10: + :10
1645 * -10: + :10
1647 * :10: + -10:
1646 * :10: + -10:
1648 * -10000:
1647 * -10000:
1649 * -10000: + 0
1648 * -10000: + 0
1650
1649
1651 The command currently focus on valid binary lookup. Benchmarking for
1650 The command currently focus on valid binary lookup. Benchmarking for
1652 hexlookup, prefix lookup and missing lookup would also be valuable.
1651 hexlookup, prefix lookup and missing lookup would also be valuable.
1653 """
1652 """
1654 import mercurial.revlog
1653 import mercurial.revlog
1655
1654
1656 opts = _byteskwargs(opts)
1655 opts = _byteskwargs(opts)
1657 timer, fm = gettimer(ui, opts)
1656 timer, fm = gettimer(ui, opts)
1658 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1657 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1659
1658
1660 unfi = repo.unfiltered()
1659 unfi = repo.unfiltered()
1661 clearcaches = opts[b'clear_caches']
1660 clearcaches = opts[b'clear_caches']
1662 # find the filecache func directly
1661 # find the filecache func directly
1663 # This avoid polluting the benchmark with the filecache logic
1662 # This avoid polluting the benchmark with the filecache logic
1664 makecl = unfi.__class__.changelog.func
1663 makecl = unfi.__class__.changelog.func
1665 if not opts[b'rev']:
1664 if not opts[b'rev']:
1666 raise error.Abort(b'use --rev to specify revisions to look up')
1665 raise error.Abort(b'use --rev to specify revisions to look up')
1667 revs = scmutil.revrange(repo, opts[b'rev'])
1666 revs = scmutil.revrange(repo, opts[b'rev'])
1668 cl = repo.changelog
1667 cl = repo.changelog
1669 nodes = [cl.node(r) for r in revs]
1668 nodes = [cl.node(r) for r in revs]
1670
1669
1671 # use a list to pass reference to a nodemap from one closure to the next
1670 # use a list to pass reference to a nodemap from one closure to the next
1672 nodeget = [None]
1671 nodeget = [None]
1673
1672
1674 def setnodeget():
1673 def setnodeget():
1675 # probably not necessary, but for good measure
1674 # probably not necessary, but for good measure
1676 clearchangelog(unfi)
1675 clearchangelog(unfi)
1677 cl = makecl(unfi)
1676 cl = makecl(unfi)
1678 if util.safehasattr(cl.index, 'get_rev'):
1677 if util.safehasattr(cl.index, 'get_rev'):
1679 nodeget[0] = cl.index.get_rev
1678 nodeget[0] = cl.index.get_rev
1680 else:
1679 else:
1681 nodeget[0] = cl.nodemap.get
1680 nodeget[0] = cl.nodemap.get
1682
1681
1683 def d():
1682 def d():
1684 get = nodeget[0]
1683 get = nodeget[0]
1685 for n in nodes:
1684 for n in nodes:
1686 get(n)
1685 get(n)
1687
1686
1688 setup = None
1687 setup = None
1689 if clearcaches:
1688 if clearcaches:
1690
1689
1691 def setup():
1690 def setup():
1692 setnodeget()
1691 setnodeget()
1693
1692
1694 else:
1693 else:
1695 setnodeget()
1694 setnodeget()
1696 d() # prewarm the data structure
1695 d() # prewarm the data structure
1697 timer(d, setup=setup)
1696 timer(d, setup=setup)
1698 fm.end()
1697 fm.end()
1699
1698
1700
1699
1701 @command(b'perf::startup|perfstartup', formatteropts)
1700 @command(b'perf::startup|perfstartup', formatteropts)
1702 def perfstartup(ui, repo, **opts):
1701 def perfstartup(ui, repo, **opts):
1703 opts = _byteskwargs(opts)
1702 opts = _byteskwargs(opts)
1704 timer, fm = gettimer(ui, opts)
1703 timer, fm = gettimer(ui, opts)
1705
1704
1706 def d():
1705 def d():
1707 if os.name != 'nt':
1706 if os.name != 'nt':
1708 os.system(
1707 os.system(
1709 b"HGRCPATH= %s version -q > /dev/null" % fsencode(sys.argv[0])
1708 b"HGRCPATH= %s version -q > /dev/null" % fsencode(sys.argv[0])
1710 )
1709 )
1711 else:
1710 else:
1712 os.environ['HGRCPATH'] = r' '
1711 os.environ['HGRCPATH'] = r' '
1713 os.system("%s version -q > NUL" % sys.argv[0])
1712 os.system("%s version -q > NUL" % sys.argv[0])
1714
1713
1715 timer(d)
1714 timer(d)
1716 fm.end()
1715 fm.end()
1717
1716
1718
1717
1719 @command(b'perf::parents|perfparents', formatteropts)
1718 @command(b'perf::parents|perfparents', formatteropts)
1720 def perfparents(ui, repo, **opts):
1719 def perfparents(ui, repo, **opts):
1721 """benchmark the time necessary to fetch one changeset's parents.
1720 """benchmark the time necessary to fetch one changeset's parents.
1722
1721
1723 The fetch is done using the `node identifier`, traversing all object layers
1722 The fetch is done using the `node identifier`, traversing all object layers
1724 from the repository object. The first N revisions will be used for this
1723 from the repository object. The first N revisions will be used for this
1725 benchmark. N is controlled by the ``perf.parentscount`` config option
1724 benchmark. N is controlled by the ``perf.parentscount`` config option
1726 (default: 1000).
1725 (default: 1000).
1727 """
1726 """
1728 opts = _byteskwargs(opts)
1727 opts = _byteskwargs(opts)
1729 timer, fm = gettimer(ui, opts)
1728 timer, fm = gettimer(ui, opts)
1730 # control the number of commits perfparents iterates over
1729 # control the number of commits perfparents iterates over
1731 # experimental config: perf.parentscount
1730 # experimental config: perf.parentscount
1732 count = getint(ui, b"perf", b"parentscount", 1000)
1731 count = getint(ui, b"perf", b"parentscount", 1000)
1733 if len(repo.changelog) < count:
1732 if len(repo.changelog) < count:
1734 raise error.Abort(b"repo needs %d commits for this test" % count)
1733 raise error.Abort(b"repo needs %d commits for this test" % count)
1735 repo = repo.unfiltered()
1734 repo = repo.unfiltered()
1736 nl = [repo.changelog.node(i) for i in _xrange(count)]
1735 nl = [repo.changelog.node(i) for i in _xrange(count)]
1737
1736
1738 def d():
1737 def d():
1739 for n in nl:
1738 for n in nl:
1740 repo.changelog.parents(n)
1739 repo.changelog.parents(n)
1741
1740
1742 timer(d)
1741 timer(d)
1743 fm.end()
1742 fm.end()
1744
1743
1745
1744
1746 @command(b'perf::ctxfiles|perfctxfiles', formatteropts)
1745 @command(b'perf::ctxfiles|perfctxfiles', formatteropts)
1747 def perfctxfiles(ui, repo, x, **opts):
1746 def perfctxfiles(ui, repo, x, **opts):
1748 opts = _byteskwargs(opts)
1747 opts = _byteskwargs(opts)
1749 x = int(x)
1748 x = int(x)
1750 timer, fm = gettimer(ui, opts)
1749 timer, fm = gettimer(ui, opts)
1751
1750
1752 def d():
1751 def d():
1753 len(repo[x].files())
1752 len(repo[x].files())
1754
1753
1755 timer(d)
1754 timer(d)
1756 fm.end()
1755 fm.end()
1757
1756
1758
1757
1759 @command(b'perf::rawfiles|perfrawfiles', formatteropts)
1758 @command(b'perf::rawfiles|perfrawfiles', formatteropts)
1760 def perfrawfiles(ui, repo, x, **opts):
1759 def perfrawfiles(ui, repo, x, **opts):
1761 opts = _byteskwargs(opts)
1760 opts = _byteskwargs(opts)
1762 x = int(x)
1761 x = int(x)
1763 timer, fm = gettimer(ui, opts)
1762 timer, fm = gettimer(ui, opts)
1764 cl = repo.changelog
1763 cl = repo.changelog
1765
1764
1766 def d():
1765 def d():
1767 len(cl.read(x)[3])
1766 len(cl.read(x)[3])
1768
1767
1769 timer(d)
1768 timer(d)
1770 fm.end()
1769 fm.end()
1771
1770
1772
1771
1773 @command(b'perf::lookup|perflookup', formatteropts)
1772 @command(b'perf::lookup|perflookup', formatteropts)
1774 def perflookup(ui, repo, rev, **opts):
1773 def perflookup(ui, repo, rev, **opts):
1775 opts = _byteskwargs(opts)
1774 opts = _byteskwargs(opts)
1776 timer, fm = gettimer(ui, opts)
1775 timer, fm = gettimer(ui, opts)
1777 timer(lambda: len(repo.lookup(rev)))
1776 timer(lambda: len(repo.lookup(rev)))
1778 fm.end()
1777 fm.end()
1779
1778
1780
1779
1781 @command(
1780 @command(
1782 b'perf::linelogedits|perflinelogedits',
1781 b'perf::linelogedits|perflinelogedits',
1783 [
1782 [
1784 (b'n', b'edits', 10000, b'number of edits'),
1783 (b'n', b'edits', 10000, b'number of edits'),
1785 (b'', b'max-hunk-lines', 10, b'max lines in a hunk'),
1784 (b'', b'max-hunk-lines', 10, b'max lines in a hunk'),
1786 ],
1785 ],
1787 norepo=True,
1786 norepo=True,
1788 )
1787 )
1789 def perflinelogedits(ui, **opts):
1788 def perflinelogedits(ui, **opts):
1790 from mercurial import linelog
1789 from mercurial import linelog
1791
1790
1792 opts = _byteskwargs(opts)
1791 opts = _byteskwargs(opts)
1793
1792
1794 edits = opts[b'edits']
1793 edits = opts[b'edits']
1795 maxhunklines = opts[b'max_hunk_lines']
1794 maxhunklines = opts[b'max_hunk_lines']
1796
1795
1797 maxb1 = 100000
1796 maxb1 = 100000
1798 random.seed(0)
1797 random.seed(0)
1799 randint = random.randint
1798 randint = random.randint
1800 currentlines = 0
1799 currentlines = 0
1801 arglist = []
1800 arglist = []
1802 for rev in _xrange(edits):
1801 for rev in _xrange(edits):
1803 a1 = randint(0, currentlines)
1802 a1 = randint(0, currentlines)
1804 a2 = randint(a1, min(currentlines, a1 + maxhunklines))
1803 a2 = randint(a1, min(currentlines, a1 + maxhunklines))
1805 b1 = randint(0, maxb1)
1804 b1 = randint(0, maxb1)
1806 b2 = randint(b1, b1 + maxhunklines)
1805 b2 = randint(b1, b1 + maxhunklines)
1807 currentlines += (b2 - b1) - (a2 - a1)
1806 currentlines += (b2 - b1) - (a2 - a1)
1808 arglist.append((rev, a1, a2, b1, b2))
1807 arglist.append((rev, a1, a2, b1, b2))
1809
1808
1810 def d():
1809 def d():
1811 ll = linelog.linelog()
1810 ll = linelog.linelog()
1812 for args in arglist:
1811 for args in arglist:
1813 ll.replacelines(*args)
1812 ll.replacelines(*args)
1814
1813
1815 timer, fm = gettimer(ui, opts)
1814 timer, fm = gettimer(ui, opts)
1816 timer(d)
1815 timer(d)
1817 fm.end()
1816 fm.end()
1818
1817
1819
1818
1820 @command(b'perf::revrange|perfrevrange', formatteropts)
1819 @command(b'perf::revrange|perfrevrange', formatteropts)
1821 def perfrevrange(ui, repo, *specs, **opts):
1820 def perfrevrange(ui, repo, *specs, **opts):
1822 opts = _byteskwargs(opts)
1821 opts = _byteskwargs(opts)
1823 timer, fm = gettimer(ui, opts)
1822 timer, fm = gettimer(ui, opts)
1824 revrange = scmutil.revrange
1823 revrange = scmutil.revrange
1825 timer(lambda: len(revrange(repo, specs)))
1824 timer(lambda: len(revrange(repo, specs)))
1826 fm.end()
1825 fm.end()
1827
1826
1828
1827
1829 @command(b'perf::nodelookup|perfnodelookup', formatteropts)
1828 @command(b'perf::nodelookup|perfnodelookup', formatteropts)
1830 def perfnodelookup(ui, repo, rev, **opts):
1829 def perfnodelookup(ui, repo, rev, **opts):
1831 opts = _byteskwargs(opts)
1830 opts = _byteskwargs(opts)
1832 timer, fm = gettimer(ui, opts)
1831 timer, fm = gettimer(ui, opts)
1833 import mercurial.revlog
1832 import mercurial.revlog
1834
1833
1835 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1834 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1836 n = scmutil.revsingle(repo, rev).node()
1835 n = scmutil.revsingle(repo, rev).node()
1837
1836
1838 try:
1837 try:
1839 cl = revlog(getsvfs(repo), radix=b"00changelog")
1838 cl = revlog(getsvfs(repo), radix=b"00changelog")
1840 except TypeError:
1839 except TypeError:
1841 cl = revlog(getsvfs(repo), indexfile=b"00changelog.i")
1840 cl = revlog(getsvfs(repo), indexfile=b"00changelog.i")
1842
1841
1843 def d():
1842 def d():
1844 cl.rev(n)
1843 cl.rev(n)
1845 clearcaches(cl)
1844 clearcaches(cl)
1846
1845
1847 timer(d)
1846 timer(d)
1848 fm.end()
1847 fm.end()
1849
1848
1850
1849
1851 @command(
1850 @command(
1852 b'perf::log|perflog',
1851 b'perf::log|perflog',
1853 [(b'', b'rename', False, b'ask log to follow renames')] + formatteropts,
1852 [(b'', b'rename', False, b'ask log to follow renames')] + formatteropts,
1854 )
1853 )
1855 def perflog(ui, repo, rev=None, **opts):
1854 def perflog(ui, repo, rev=None, **opts):
1856 opts = _byteskwargs(opts)
1855 opts = _byteskwargs(opts)
1857 if rev is None:
1856 if rev is None:
1858 rev = []
1857 rev = []
1859 timer, fm = gettimer(ui, opts)
1858 timer, fm = gettimer(ui, opts)
1860 ui.pushbuffer()
1859 ui.pushbuffer()
1861 timer(
1860 timer(
1862 lambda: commands.log(
1861 lambda: commands.log(
1863 ui, repo, rev=rev, date=b'', user=b'', copies=opts.get(b'rename')
1862 ui, repo, rev=rev, date=b'', user=b'', copies=opts.get(b'rename')
1864 )
1863 )
1865 )
1864 )
1866 ui.popbuffer()
1865 ui.popbuffer()
1867 fm.end()
1866 fm.end()
1868
1867
1869
1868
1870 @command(b'perf::moonwalk|perfmoonwalk', formatteropts)
1869 @command(b'perf::moonwalk|perfmoonwalk', formatteropts)
1871 def perfmoonwalk(ui, repo, **opts):
1870 def perfmoonwalk(ui, repo, **opts):
1872 """benchmark walking the changelog backwards
1871 """benchmark walking the changelog backwards
1873
1872
1874 This also loads the changelog data for each revision in the changelog.
1873 This also loads the changelog data for each revision in the changelog.
1875 """
1874 """
1876 opts = _byteskwargs(opts)
1875 opts = _byteskwargs(opts)
1877 timer, fm = gettimer(ui, opts)
1876 timer, fm = gettimer(ui, opts)
1878
1877
1879 def moonwalk():
1878 def moonwalk():
1880 for i in repo.changelog.revs(start=(len(repo) - 1), stop=-1):
1879 for i in repo.changelog.revs(start=(len(repo) - 1), stop=-1):
1881 ctx = repo[i]
1880 ctx = repo[i]
1882 ctx.branch() # read changelog data (in addition to the index)
1881 ctx.branch() # read changelog data (in addition to the index)
1883
1882
1884 timer(moonwalk)
1883 timer(moonwalk)
1885 fm.end()
1884 fm.end()
1886
1885
1887
1886
1888 @command(
1887 @command(
1889 b'perf::templating|perftemplating',
1888 b'perf::templating|perftemplating',
1890 [
1889 [
1891 (b'r', b'rev', [], b'revisions to run the template on'),
1890 (b'r', b'rev', [], b'revisions to run the template on'),
1892 ]
1891 ]
1893 + formatteropts,
1892 + formatteropts,
1894 )
1893 )
1895 def perftemplating(ui, repo, testedtemplate=None, **opts):
1894 def perftemplating(ui, repo, testedtemplate=None, **opts):
1896 """test the rendering time of a given template"""
1895 """test the rendering time of a given template"""
1897 if makelogtemplater is None:
1896 if makelogtemplater is None:
1898 raise error.Abort(
1897 raise error.Abort(
1899 b"perftemplating not available with this Mercurial",
1898 b"perftemplating not available with this Mercurial",
1900 hint=b"use 4.3 or later",
1899 hint=b"use 4.3 or later",
1901 )
1900 )
1902
1901
1903 opts = _byteskwargs(opts)
1902 opts = _byteskwargs(opts)
1904
1903
1905 nullui = ui.copy()
1904 nullui = ui.copy()
1906 nullui.fout = open(os.devnull, 'wb')
1905 nullui.fout = open(os.devnull, 'wb')
1907 nullui.disablepager()
1906 nullui.disablepager()
1908 revs = opts.get(b'rev')
1907 revs = opts.get(b'rev')
1909 if not revs:
1908 if not revs:
1910 revs = [b'all()']
1909 revs = [b'all()']
1911 revs = list(scmutil.revrange(repo, revs))
1910 revs = list(scmutil.revrange(repo, revs))
1912
1911
1913 defaulttemplate = (
1912 defaulttemplate = (
1914 b'{date|shortdate} [{rev}:{node|short}]'
1913 b'{date|shortdate} [{rev}:{node|short}]'
1915 b' {author|person}: {desc|firstline}\n'
1914 b' {author|person}: {desc|firstline}\n'
1916 )
1915 )
1917 if testedtemplate is None:
1916 if testedtemplate is None:
1918 testedtemplate = defaulttemplate
1917 testedtemplate = defaulttemplate
1919 displayer = makelogtemplater(nullui, repo, testedtemplate)
1918 displayer = makelogtemplater(nullui, repo, testedtemplate)
1920
1919
1921 def format():
1920 def format():
1922 for r in revs:
1921 for r in revs:
1923 ctx = repo[r]
1922 ctx = repo[r]
1924 displayer.show(ctx)
1923 displayer.show(ctx)
1925 displayer.flush(ctx)
1924 displayer.flush(ctx)
1926
1925
1927 timer, fm = gettimer(ui, opts)
1926 timer, fm = gettimer(ui, opts)
1928 timer(format)
1927 timer(format)
1929 fm.end()
1928 fm.end()
1930
1929
1931
1930
1932 def _displaystats(ui, opts, entries, data):
1931 def _displaystats(ui, opts, entries, data):
1933 # use a second formatter because the data are quite different, not sure
1932 # use a second formatter because the data are quite different, not sure
1934 # how it flies with the templater.
1933 # how it flies with the templater.
1935 fm = ui.formatter(b'perf-stats', opts)
1934 fm = ui.formatter(b'perf-stats', opts)
1936 for key, title in entries:
1935 for key, title in entries:
1937 values = data[key]
1936 values = data[key]
1938 nbvalues = len(data)
1937 nbvalues = len(data)
1939 values.sort()
1938 values.sort()
1940 stats = {
1939 stats = {
1941 'key': key,
1940 'key': key,
1942 'title': title,
1941 'title': title,
1943 'nbitems': len(values),
1942 'nbitems': len(values),
1944 'min': values[0][0],
1943 'min': values[0][0],
1945 '10%': values[(nbvalues * 10) // 100][0],
1944 '10%': values[(nbvalues * 10) // 100][0],
1946 '25%': values[(nbvalues * 25) // 100][0],
1945 '25%': values[(nbvalues * 25) // 100][0],
1947 '50%': values[(nbvalues * 50) // 100][0],
1946 '50%': values[(nbvalues * 50) // 100][0],
1948 '75%': values[(nbvalues * 75) // 100][0],
1947 '75%': values[(nbvalues * 75) // 100][0],
1949 '80%': values[(nbvalues * 80) // 100][0],
1948 '80%': values[(nbvalues * 80) // 100][0],
1950 '85%': values[(nbvalues * 85) // 100][0],
1949 '85%': values[(nbvalues * 85) // 100][0],
1951 '90%': values[(nbvalues * 90) // 100][0],
1950 '90%': values[(nbvalues * 90) // 100][0],
1952 '95%': values[(nbvalues * 95) // 100][0],
1951 '95%': values[(nbvalues * 95) // 100][0],
1953 '99%': values[(nbvalues * 99) // 100][0],
1952 '99%': values[(nbvalues * 99) // 100][0],
1954 'max': values[-1][0],
1953 'max': values[-1][0],
1955 }
1954 }
1956 fm.startitem()
1955 fm.startitem()
1957 fm.data(**stats)
1956 fm.data(**stats)
1958 # make node pretty for the human output
1957 # make node pretty for the human output
1959 fm.plain('### %s (%d items)\n' % (title, len(values)))
1958 fm.plain('### %s (%d items)\n' % (title, len(values)))
1960 lines = [
1959 lines = [
1961 'min',
1960 'min',
1962 '10%',
1961 '10%',
1963 '25%',
1962 '25%',
1964 '50%',
1963 '50%',
1965 '75%',
1964 '75%',
1966 '80%',
1965 '80%',
1967 '85%',
1966 '85%',
1968 '90%',
1967 '90%',
1969 '95%',
1968 '95%',
1970 '99%',
1969 '99%',
1971 'max',
1970 'max',
1972 ]
1971 ]
1973 for l in lines:
1972 for l in lines:
1974 fm.plain('%s: %s\n' % (l, stats[l]))
1973 fm.plain('%s: %s\n' % (l, stats[l]))
1975 fm.end()
1974 fm.end()
1976
1975
1977
1976
1978 @command(
1977 @command(
1979 b'perf::helper-mergecopies|perfhelper-mergecopies',
1978 b'perf::helper-mergecopies|perfhelper-mergecopies',
1980 formatteropts
1979 formatteropts
1981 + [
1980 + [
1982 (b'r', b'revs', [], b'restrict search to these revisions'),
1981 (b'r', b'revs', [], b'restrict search to these revisions'),
1983 (b'', b'timing', False, b'provides extra data (costly)'),
1982 (b'', b'timing', False, b'provides extra data (costly)'),
1984 (b'', b'stats', False, b'provides statistic about the measured data'),
1983 (b'', b'stats', False, b'provides statistic about the measured data'),
1985 ],
1984 ],
1986 )
1985 )
1987 def perfhelpermergecopies(ui, repo, revs=[], **opts):
1986 def perfhelpermergecopies(ui, repo, revs=[], **opts):
1988 """find statistics about potential parameters for `perfmergecopies`
1987 """find statistics about potential parameters for `perfmergecopies`
1989
1988
1990 This command find (base, p1, p2) triplet relevant for copytracing
1989 This command find (base, p1, p2) triplet relevant for copytracing
1991 benchmarking in the context of a merge. It reports values for some of the
1990 benchmarking in the context of a merge. It reports values for some of the
1992 parameters that impact merge copy tracing time during merge.
1991 parameters that impact merge copy tracing time during merge.
1993
1992
1994 If `--timing` is set, rename detection is run and the associated timing
1993 If `--timing` is set, rename detection is run and the associated timing
1995 will be reported. The extra details come at the cost of slower command
1994 will be reported. The extra details come at the cost of slower command
1996 execution.
1995 execution.
1997
1996
1998 Since rename detection is only run once, other factors might easily
1997 Since rename detection is only run once, other factors might easily
1999 affect the precision of the timing. However it should give a good
1998 affect the precision of the timing. However it should give a good
2000 approximation of which revision triplets are very costly.
1999 approximation of which revision triplets are very costly.
2001 """
2000 """
2002 opts = _byteskwargs(opts)
2001 opts = _byteskwargs(opts)
2003 fm = ui.formatter(b'perf', opts)
2002 fm = ui.formatter(b'perf', opts)
2004 dotiming = opts[b'timing']
2003 dotiming = opts[b'timing']
2005 dostats = opts[b'stats']
2004 dostats = opts[b'stats']
2006
2005
2007 output_template = [
2006 output_template = [
2008 ("base", "%(base)12s"),
2007 ("base", "%(base)12s"),
2009 ("p1", "%(p1.node)12s"),
2008 ("p1", "%(p1.node)12s"),
2010 ("p2", "%(p2.node)12s"),
2009 ("p2", "%(p2.node)12s"),
2011 ("p1.nb-revs", "%(p1.nbrevs)12d"),
2010 ("p1.nb-revs", "%(p1.nbrevs)12d"),
2012 ("p1.nb-files", "%(p1.nbmissingfiles)12d"),
2011 ("p1.nb-files", "%(p1.nbmissingfiles)12d"),
2013 ("p1.renames", "%(p1.renamedfiles)12d"),
2012 ("p1.renames", "%(p1.renamedfiles)12d"),
2014 ("p1.time", "%(p1.time)12.3f"),
2013 ("p1.time", "%(p1.time)12.3f"),
2015 ("p2.nb-revs", "%(p2.nbrevs)12d"),
2014 ("p2.nb-revs", "%(p2.nbrevs)12d"),
2016 ("p2.nb-files", "%(p2.nbmissingfiles)12d"),
2015 ("p2.nb-files", "%(p2.nbmissingfiles)12d"),
2017 ("p2.renames", "%(p2.renamedfiles)12d"),
2016 ("p2.renames", "%(p2.renamedfiles)12d"),
2018 ("p2.time", "%(p2.time)12.3f"),
2017 ("p2.time", "%(p2.time)12.3f"),
2019 ("renames", "%(nbrenamedfiles)12d"),
2018 ("renames", "%(nbrenamedfiles)12d"),
2020 ("total.time", "%(time)12.3f"),
2019 ("total.time", "%(time)12.3f"),
2021 ]
2020 ]
2022 if not dotiming:
2021 if not dotiming:
2023 output_template = [
2022 output_template = [
2024 i
2023 i
2025 for i in output_template
2024 for i in output_template
2026 if not ('time' in i[0] or 'renames' in i[0])
2025 if not ('time' in i[0] or 'renames' in i[0])
2027 ]
2026 ]
2028 header_names = [h for (h, v) in output_template]
2027 header_names = [h for (h, v) in output_template]
2029 output = ' '.join([v for (h, v) in output_template]) + '\n'
2028 output = ' '.join([v for (h, v) in output_template]) + '\n'
2030 header = ' '.join(['%12s'] * len(header_names)) + '\n'
2029 header = ' '.join(['%12s'] * len(header_names)) + '\n'
2031 fm.plain(header % tuple(header_names))
2030 fm.plain(header % tuple(header_names))
2032
2031
2033 if not revs:
2032 if not revs:
2034 revs = ['all()']
2033 revs = ['all()']
2035 revs = scmutil.revrange(repo, revs)
2034 revs = scmutil.revrange(repo, revs)
2036
2035
2037 if dostats:
2036 if dostats:
2038 alldata = {
2037 alldata = {
2039 'nbrevs': [],
2038 'nbrevs': [],
2040 'nbmissingfiles': [],
2039 'nbmissingfiles': [],
2041 }
2040 }
2042 if dotiming:
2041 if dotiming:
2043 alldata['parentnbrenames'] = []
2042 alldata['parentnbrenames'] = []
2044 alldata['totalnbrenames'] = []
2043 alldata['totalnbrenames'] = []
2045 alldata['parenttime'] = []
2044 alldata['parenttime'] = []
2046 alldata['totaltime'] = []
2045 alldata['totaltime'] = []
2047
2046
2048 roi = repo.revs('merge() and %ld', revs)
2047 roi = repo.revs('merge() and %ld', revs)
2049 for r in roi:
2048 for r in roi:
2050 ctx = repo[r]
2049 ctx = repo[r]
2051 p1 = ctx.p1()
2050 p1 = ctx.p1()
2052 p2 = ctx.p2()
2051 p2 = ctx.p2()
2053 bases = repo.changelog._commonancestorsheads(p1.rev(), p2.rev())
2052 bases = repo.changelog._commonancestorsheads(p1.rev(), p2.rev())
2054 for b in bases:
2053 for b in bases:
2055 b = repo[b]
2054 b = repo[b]
2056 p1missing = copies._computeforwardmissing(b, p1)
2055 p1missing = copies._computeforwardmissing(b, p1)
2057 p2missing = copies._computeforwardmissing(b, p2)
2056 p2missing = copies._computeforwardmissing(b, p2)
2058 data = {
2057 data = {
2059 b'base': b.hex(),
2058 b'base': b.hex(),
2060 b'p1.node': p1.hex(),
2059 b'p1.node': p1.hex(),
2061 b'p1.nbrevs': len(repo.revs('only(%d, %d)', p1.rev(), b.rev())),
2060 b'p1.nbrevs': len(repo.revs('only(%d, %d)', p1.rev(), b.rev())),
2062 b'p1.nbmissingfiles': len(p1missing),
2061 b'p1.nbmissingfiles': len(p1missing),
2063 b'p2.node': p2.hex(),
2062 b'p2.node': p2.hex(),
2064 b'p2.nbrevs': len(repo.revs('only(%d, %d)', p2.rev(), b.rev())),
2063 b'p2.nbrevs': len(repo.revs('only(%d, %d)', p2.rev(), b.rev())),
2065 b'p2.nbmissingfiles': len(p2missing),
2064 b'p2.nbmissingfiles': len(p2missing),
2066 }
2065 }
2067 if dostats:
2066 if dostats:
2068 if p1missing:
2067 if p1missing:
2069 alldata['nbrevs'].append(
2068 alldata['nbrevs'].append(
2070 (data['p1.nbrevs'], b.hex(), p1.hex())
2069 (data['p1.nbrevs'], b.hex(), p1.hex())
2071 )
2070 )
2072 alldata['nbmissingfiles'].append(
2071 alldata['nbmissingfiles'].append(
2073 (data['p1.nbmissingfiles'], b.hex(), p1.hex())
2072 (data['p1.nbmissingfiles'], b.hex(), p1.hex())
2074 )
2073 )
2075 if p2missing:
2074 if p2missing:
2076 alldata['nbrevs'].append(
2075 alldata['nbrevs'].append(
2077 (data['p2.nbrevs'], b.hex(), p2.hex())
2076 (data['p2.nbrevs'], b.hex(), p2.hex())
2078 )
2077 )
2079 alldata['nbmissingfiles'].append(
2078 alldata['nbmissingfiles'].append(
2080 (data['p2.nbmissingfiles'], b.hex(), p2.hex())
2079 (data['p2.nbmissingfiles'], b.hex(), p2.hex())
2081 )
2080 )
2082 if dotiming:
2081 if dotiming:
2083 begin = util.timer()
2082 begin = util.timer()
2084 mergedata = copies.mergecopies(repo, p1, p2, b)
2083 mergedata = copies.mergecopies(repo, p1, p2, b)
2085 end = util.timer()
2084 end = util.timer()
2086 # not very stable timing since we did only one run
2085 # not very stable timing since we did only one run
2087 data['time'] = end - begin
2086 data['time'] = end - begin
2088 # mergedata contains five dicts: "copy", "movewithdir",
2087 # mergedata contains five dicts: "copy", "movewithdir",
2089 # "diverge", "renamedelete" and "dirmove".
2088 # "diverge", "renamedelete" and "dirmove".
2090 # The first 4 are about renamed file so lets count that.
2089 # The first 4 are about renamed file so lets count that.
2091 renames = len(mergedata[0])
2090 renames = len(mergedata[0])
2092 renames += len(mergedata[1])
2091 renames += len(mergedata[1])
2093 renames += len(mergedata[2])
2092 renames += len(mergedata[2])
2094 renames += len(mergedata[3])
2093 renames += len(mergedata[3])
2095 data['nbrenamedfiles'] = renames
2094 data['nbrenamedfiles'] = renames
2096 begin = util.timer()
2095 begin = util.timer()
2097 p1renames = copies.pathcopies(b, p1)
2096 p1renames = copies.pathcopies(b, p1)
2098 end = util.timer()
2097 end = util.timer()
2099 data['p1.time'] = end - begin
2098 data['p1.time'] = end - begin
2100 begin = util.timer()
2099 begin = util.timer()
2101 p2renames = copies.pathcopies(b, p2)
2100 p2renames = copies.pathcopies(b, p2)
2102 end = util.timer()
2101 end = util.timer()
2103 data['p2.time'] = end - begin
2102 data['p2.time'] = end - begin
2104 data['p1.renamedfiles'] = len(p1renames)
2103 data['p1.renamedfiles'] = len(p1renames)
2105 data['p2.renamedfiles'] = len(p2renames)
2104 data['p2.renamedfiles'] = len(p2renames)
2106
2105
2107 if dostats:
2106 if dostats:
2108 if p1missing:
2107 if p1missing:
2109 alldata['parentnbrenames'].append(
2108 alldata['parentnbrenames'].append(
2110 (data['p1.renamedfiles'], b.hex(), p1.hex())
2109 (data['p1.renamedfiles'], b.hex(), p1.hex())
2111 )
2110 )
2112 alldata['parenttime'].append(
2111 alldata['parenttime'].append(
2113 (data['p1.time'], b.hex(), p1.hex())
2112 (data['p1.time'], b.hex(), p1.hex())
2114 )
2113 )
2115 if p2missing:
2114 if p2missing:
2116 alldata['parentnbrenames'].append(
2115 alldata['parentnbrenames'].append(
2117 (data['p2.renamedfiles'], b.hex(), p2.hex())
2116 (data['p2.renamedfiles'], b.hex(), p2.hex())
2118 )
2117 )
2119 alldata['parenttime'].append(
2118 alldata['parenttime'].append(
2120 (data['p2.time'], b.hex(), p2.hex())
2119 (data['p2.time'], b.hex(), p2.hex())
2121 )
2120 )
2122 if p1missing or p2missing:
2121 if p1missing or p2missing:
2123 alldata['totalnbrenames'].append(
2122 alldata['totalnbrenames'].append(
2124 (
2123 (
2125 data['nbrenamedfiles'],
2124 data['nbrenamedfiles'],
2126 b.hex(),
2125 b.hex(),
2127 p1.hex(),
2126 p1.hex(),
2128 p2.hex(),
2127 p2.hex(),
2129 )
2128 )
2130 )
2129 )
2131 alldata['totaltime'].append(
2130 alldata['totaltime'].append(
2132 (data['time'], b.hex(), p1.hex(), p2.hex())
2131 (data['time'], b.hex(), p1.hex(), p2.hex())
2133 )
2132 )
2134 fm.startitem()
2133 fm.startitem()
2135 fm.data(**data)
2134 fm.data(**data)
2136 # make node pretty for the human output
2135 # make node pretty for the human output
2137 out = data.copy()
2136 out = data.copy()
2138 out['base'] = fm.hexfunc(b.node())
2137 out['base'] = fm.hexfunc(b.node())
2139 out['p1.node'] = fm.hexfunc(p1.node())
2138 out['p1.node'] = fm.hexfunc(p1.node())
2140 out['p2.node'] = fm.hexfunc(p2.node())
2139 out['p2.node'] = fm.hexfunc(p2.node())
2141 fm.plain(output % out)
2140 fm.plain(output % out)
2142
2141
2143 fm.end()
2142 fm.end()
2144 if dostats:
2143 if dostats:
2145 # use a second formatter because the data are quite different, not sure
2144 # use a second formatter because the data are quite different, not sure
2146 # how it flies with the templater.
2145 # how it flies with the templater.
2147 entries = [
2146 entries = [
2148 ('nbrevs', 'number of revision covered'),
2147 ('nbrevs', 'number of revision covered'),
2149 ('nbmissingfiles', 'number of missing files at head'),
2148 ('nbmissingfiles', 'number of missing files at head'),
2150 ]
2149 ]
2151 if dotiming:
2150 if dotiming:
2152 entries.append(
2151 entries.append(
2153 ('parentnbrenames', 'rename from one parent to base')
2152 ('parentnbrenames', 'rename from one parent to base')
2154 )
2153 )
2155 entries.append(('totalnbrenames', 'total number of renames'))
2154 entries.append(('totalnbrenames', 'total number of renames'))
2156 entries.append(('parenttime', 'time for one parent'))
2155 entries.append(('parenttime', 'time for one parent'))
2157 entries.append(('totaltime', 'time for both parents'))
2156 entries.append(('totaltime', 'time for both parents'))
2158 _displaystats(ui, opts, entries, alldata)
2157 _displaystats(ui, opts, entries, alldata)
2159
2158
2160
2159
2161 @command(
2160 @command(
2162 b'perf::helper-pathcopies|perfhelper-pathcopies',
2161 b'perf::helper-pathcopies|perfhelper-pathcopies',
2163 formatteropts
2162 formatteropts
2164 + [
2163 + [
2165 (b'r', b'revs', [], b'restrict search to these revisions'),
2164 (b'r', b'revs', [], b'restrict search to these revisions'),
2166 (b'', b'timing', False, b'provides extra data (costly)'),
2165 (b'', b'timing', False, b'provides extra data (costly)'),
2167 (b'', b'stats', False, b'provides statistic about the measured data'),
2166 (b'', b'stats', False, b'provides statistic about the measured data'),
2168 ],
2167 ],
2169 )
2168 )
2170 def perfhelperpathcopies(ui, repo, revs=[], **opts):
2169 def perfhelperpathcopies(ui, repo, revs=[], **opts):
2171 """find statistic about potential parameters for the `perftracecopies`
2170 """find statistic about potential parameters for the `perftracecopies`
2172
2171
2173 This command find source-destination pair relevant for copytracing testing.
2172 This command find source-destination pair relevant for copytracing testing.
2174 It report value for some of the parameters that impact copy tracing time.
2173 It report value for some of the parameters that impact copy tracing time.
2175
2174
2176 If `--timing` is set, rename detection is run and the associated timing
2175 If `--timing` is set, rename detection is run and the associated timing
2177 will be reported. The extra details comes at the cost of a slower command
2176 will be reported. The extra details comes at the cost of a slower command
2178 execution.
2177 execution.
2179
2178
2180 Since the rename detection is only run once, other factors might easily
2179 Since the rename detection is only run once, other factors might easily
2181 affect the precision of the timing. However it should give a good
2180 affect the precision of the timing. However it should give a good
2182 approximation of which revision pairs are very costly.
2181 approximation of which revision pairs are very costly.
2183 """
2182 """
2184 opts = _byteskwargs(opts)
2183 opts = _byteskwargs(opts)
2185 fm = ui.formatter(b'perf', opts)
2184 fm = ui.formatter(b'perf', opts)
2186 dotiming = opts[b'timing']
2185 dotiming = opts[b'timing']
2187 dostats = opts[b'stats']
2186 dostats = opts[b'stats']
2188
2187
2189 if dotiming:
2188 if dotiming:
2190 header = '%12s %12s %12s %12s %12s %12s\n'
2189 header = '%12s %12s %12s %12s %12s %12s\n'
2191 output = (
2190 output = (
2192 "%(source)12s %(destination)12s "
2191 "%(source)12s %(destination)12s "
2193 "%(nbrevs)12d %(nbmissingfiles)12d "
2192 "%(nbrevs)12d %(nbmissingfiles)12d "
2194 "%(nbrenamedfiles)12d %(time)18.5f\n"
2193 "%(nbrenamedfiles)12d %(time)18.5f\n"
2195 )
2194 )
2196 header_names = (
2195 header_names = (
2197 "source",
2196 "source",
2198 "destination",
2197 "destination",
2199 "nb-revs",
2198 "nb-revs",
2200 "nb-files",
2199 "nb-files",
2201 "nb-renames",
2200 "nb-renames",
2202 "time",
2201 "time",
2203 )
2202 )
2204 fm.plain(header % header_names)
2203 fm.plain(header % header_names)
2205 else:
2204 else:
2206 header = '%12s %12s %12s %12s\n'
2205 header = '%12s %12s %12s %12s\n'
2207 output = (
2206 output = (
2208 "%(source)12s %(destination)12s "
2207 "%(source)12s %(destination)12s "
2209 "%(nbrevs)12d %(nbmissingfiles)12d\n"
2208 "%(nbrevs)12d %(nbmissingfiles)12d\n"
2210 )
2209 )
2211 fm.plain(header % ("source", "destination", "nb-revs", "nb-files"))
2210 fm.plain(header % ("source", "destination", "nb-revs", "nb-files"))
2212
2211
2213 if not revs:
2212 if not revs:
2214 revs = ['all()']
2213 revs = ['all()']
2215 revs = scmutil.revrange(repo, revs)
2214 revs = scmutil.revrange(repo, revs)
2216
2215
2217 if dostats:
2216 if dostats:
2218 alldata = {
2217 alldata = {
2219 'nbrevs': [],
2218 'nbrevs': [],
2220 'nbmissingfiles': [],
2219 'nbmissingfiles': [],
2221 }
2220 }
2222 if dotiming:
2221 if dotiming:
2223 alldata['nbrenames'] = []
2222 alldata['nbrenames'] = []
2224 alldata['time'] = []
2223 alldata['time'] = []
2225
2224
2226 roi = repo.revs('merge() and %ld', revs)
2225 roi = repo.revs('merge() and %ld', revs)
2227 for r in roi:
2226 for r in roi:
2228 ctx = repo[r]
2227 ctx = repo[r]
2229 p1 = ctx.p1().rev()
2228 p1 = ctx.p1().rev()
2230 p2 = ctx.p2().rev()
2229 p2 = ctx.p2().rev()
2231 bases = repo.changelog._commonancestorsheads(p1, p2)
2230 bases = repo.changelog._commonancestorsheads(p1, p2)
2232 for p in (p1, p2):
2231 for p in (p1, p2):
2233 for b in bases:
2232 for b in bases:
2234 base = repo[b]
2233 base = repo[b]
2235 parent = repo[p]
2234 parent = repo[p]
2236 missing = copies._computeforwardmissing(base, parent)
2235 missing = copies._computeforwardmissing(base, parent)
2237 if not missing:
2236 if not missing:
2238 continue
2237 continue
2239 data = {
2238 data = {
2240 b'source': base.hex(),
2239 b'source': base.hex(),
2241 b'destination': parent.hex(),
2240 b'destination': parent.hex(),
2242 b'nbrevs': len(repo.revs('only(%d, %d)', p, b)),
2241 b'nbrevs': len(repo.revs('only(%d, %d)', p, b)),
2243 b'nbmissingfiles': len(missing),
2242 b'nbmissingfiles': len(missing),
2244 }
2243 }
2245 if dostats:
2244 if dostats:
2246 alldata['nbrevs'].append(
2245 alldata['nbrevs'].append(
2247 (
2246 (
2248 data['nbrevs'],
2247 data['nbrevs'],
2249 base.hex(),
2248 base.hex(),
2250 parent.hex(),
2249 parent.hex(),
2251 )
2250 )
2252 )
2251 )
2253 alldata['nbmissingfiles'].append(
2252 alldata['nbmissingfiles'].append(
2254 (
2253 (
2255 data['nbmissingfiles'],
2254 data['nbmissingfiles'],
2256 base.hex(),
2255 base.hex(),
2257 parent.hex(),
2256 parent.hex(),
2258 )
2257 )
2259 )
2258 )
2260 if dotiming:
2259 if dotiming:
2261 begin = util.timer()
2260 begin = util.timer()
2262 renames = copies.pathcopies(base, parent)
2261 renames = copies.pathcopies(base, parent)
2263 end = util.timer()
2262 end = util.timer()
2264 # not very stable timing since we did only one run
2263 # not very stable timing since we did only one run
2265 data['time'] = end - begin
2264 data['time'] = end - begin
2266 data['nbrenamedfiles'] = len(renames)
2265 data['nbrenamedfiles'] = len(renames)
2267 if dostats:
2266 if dostats:
2268 alldata['time'].append(
2267 alldata['time'].append(
2269 (
2268 (
2270 data['time'],
2269 data['time'],
2271 base.hex(),
2270 base.hex(),
2272 parent.hex(),
2271 parent.hex(),
2273 )
2272 )
2274 )
2273 )
2275 alldata['nbrenames'].append(
2274 alldata['nbrenames'].append(
2276 (
2275 (
2277 data['nbrenamedfiles'],
2276 data['nbrenamedfiles'],
2278 base.hex(),
2277 base.hex(),
2279 parent.hex(),
2278 parent.hex(),
2280 )
2279 )
2281 )
2280 )
2282 fm.startitem()
2281 fm.startitem()
2283 fm.data(**data)
2282 fm.data(**data)
2284 out = data.copy()
2283 out = data.copy()
2285 out['source'] = fm.hexfunc(base.node())
2284 out['source'] = fm.hexfunc(base.node())
2286 out['destination'] = fm.hexfunc(parent.node())
2285 out['destination'] = fm.hexfunc(parent.node())
2287 fm.plain(output % out)
2286 fm.plain(output % out)
2288
2287
2289 fm.end()
2288 fm.end()
2290 if dostats:
2289 if dostats:
2291 entries = [
2290 entries = [
2292 ('nbrevs', 'number of revision covered'),
2291 ('nbrevs', 'number of revision covered'),
2293 ('nbmissingfiles', 'number of missing files at head'),
2292 ('nbmissingfiles', 'number of missing files at head'),
2294 ]
2293 ]
2295 if dotiming:
2294 if dotiming:
2296 entries.append(('nbrenames', 'renamed files'))
2295 entries.append(('nbrenames', 'renamed files'))
2297 entries.append(('time', 'time'))
2296 entries.append(('time', 'time'))
2298 _displaystats(ui, opts, entries, alldata)
2297 _displaystats(ui, opts, entries, alldata)
2299
2298
2300
2299
2301 @command(b'perf::cca|perfcca', formatteropts)
2300 @command(b'perf::cca|perfcca', formatteropts)
2302 def perfcca(ui, repo, **opts):
2301 def perfcca(ui, repo, **opts):
2303 opts = _byteskwargs(opts)
2302 opts = _byteskwargs(opts)
2304 timer, fm = gettimer(ui, opts)
2303 timer, fm = gettimer(ui, opts)
2305 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
2304 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
2306 fm.end()
2305 fm.end()
2307
2306
2308
2307
2309 @command(b'perf::fncacheload|perffncacheload', formatteropts)
2308 @command(b'perf::fncacheload|perffncacheload', formatteropts)
2310 def perffncacheload(ui, repo, **opts):
2309 def perffncacheload(ui, repo, **opts):
2311 opts = _byteskwargs(opts)
2310 opts = _byteskwargs(opts)
2312 timer, fm = gettimer(ui, opts)
2311 timer, fm = gettimer(ui, opts)
2313 s = repo.store
2312 s = repo.store
2314
2313
2315 def d():
2314 def d():
2316 s.fncache._load()
2315 s.fncache._load()
2317
2316
2318 timer(d)
2317 timer(d)
2319 fm.end()
2318 fm.end()
2320
2319
2321
2320
2322 @command(b'perf::fncachewrite|perffncachewrite', formatteropts)
2321 @command(b'perf::fncachewrite|perffncachewrite', formatteropts)
2323 def perffncachewrite(ui, repo, **opts):
2322 def perffncachewrite(ui, repo, **opts):
2324 opts = _byteskwargs(opts)
2323 opts = _byteskwargs(opts)
2325 timer, fm = gettimer(ui, opts)
2324 timer, fm = gettimer(ui, opts)
2326 s = repo.store
2325 s = repo.store
2327 lock = repo.lock()
2326 lock = repo.lock()
2328 s.fncache._load()
2327 s.fncache._load()
2329 tr = repo.transaction(b'perffncachewrite')
2328 tr = repo.transaction(b'perffncachewrite')
2330 tr.addbackup(b'fncache')
2329 tr.addbackup(b'fncache')
2331
2330
2332 def d():
2331 def d():
2333 s.fncache._dirty = True
2332 s.fncache._dirty = True
2334 s.fncache.write(tr)
2333 s.fncache.write(tr)
2335
2334
2336 timer(d)
2335 timer(d)
2337 tr.close()
2336 tr.close()
2338 lock.release()
2337 lock.release()
2339 fm.end()
2338 fm.end()
2340
2339
2341
2340
2342 @command(b'perf::fncacheencode|perffncacheencode', formatteropts)
2341 @command(b'perf::fncacheencode|perffncacheencode', formatteropts)
2343 def perffncacheencode(ui, repo, **opts):
2342 def perffncacheencode(ui, repo, **opts):
2344 opts = _byteskwargs(opts)
2343 opts = _byteskwargs(opts)
2345 timer, fm = gettimer(ui, opts)
2344 timer, fm = gettimer(ui, opts)
2346 s = repo.store
2345 s = repo.store
2347 s.fncache._load()
2346 s.fncache._load()
2348
2347
2349 def d():
2348 def d():
2350 for p in s.fncache.entries:
2349 for p in s.fncache.entries:
2351 s.encode(p)
2350 s.encode(p)
2352
2351
2353 timer(d)
2352 timer(d)
2354 fm.end()
2353 fm.end()
2355
2354
2356
2355
2357 def _bdiffworker(q, blocks, xdiff, ready, done):
2356 def _bdiffworker(q, blocks, xdiff, ready, done):
2358 while not done.is_set():
2357 while not done.is_set():
2359 pair = q.get()
2358 pair = q.get()
2360 while pair is not None:
2359 while pair is not None:
2361 if xdiff:
2360 if xdiff:
2362 mdiff.bdiff.xdiffblocks(*pair)
2361 mdiff.bdiff.xdiffblocks(*pair)
2363 elif blocks:
2362 elif blocks:
2364 mdiff.bdiff.blocks(*pair)
2363 mdiff.bdiff.blocks(*pair)
2365 else:
2364 else:
2366 mdiff.textdiff(*pair)
2365 mdiff.textdiff(*pair)
2367 q.task_done()
2366 q.task_done()
2368 pair = q.get()
2367 pair = q.get()
2369 q.task_done() # for the None one
2368 q.task_done() # for the None one
2370 with ready:
2369 with ready:
2371 ready.wait()
2370 ready.wait()
2372
2371
2373
2372
2374 def _manifestrevision(repo, mnode):
2373 def _manifestrevision(repo, mnode):
2375 ml = repo.manifestlog
2374 ml = repo.manifestlog
2376
2375
2377 if util.safehasattr(ml, b'getstorage'):
2376 if util.safehasattr(ml, b'getstorage'):
2378 store = ml.getstorage(b'')
2377 store = ml.getstorage(b'')
2379 else:
2378 else:
2380 store = ml._revlog
2379 store = ml._revlog
2381
2380
2382 return store.revision(mnode)
2381 return store.revision(mnode)
2383
2382
2384
2383
2385 @command(
2384 @command(
2386 b'perf::bdiff|perfbdiff',
2385 b'perf::bdiff|perfbdiff',
2387 revlogopts
2386 revlogopts
2388 + formatteropts
2387 + formatteropts
2389 + [
2388 + [
2390 (
2389 (
2391 b'',
2390 b'',
2392 b'count',
2391 b'count',
2393 1,
2392 1,
2394 b'number of revisions to test (when using --startrev)',
2393 b'number of revisions to test (when using --startrev)',
2395 ),
2394 ),
2396 (b'', b'alldata', False, b'test bdiffs for all associated revisions'),
2395 (b'', b'alldata', False, b'test bdiffs for all associated revisions'),
2397 (b'', b'threads', 0, b'number of thread to use (disable with 0)'),
2396 (b'', b'threads', 0, b'number of thread to use (disable with 0)'),
2398 (b'', b'blocks', False, b'test computing diffs into blocks'),
2397 (b'', b'blocks', False, b'test computing diffs into blocks'),
2399 (b'', b'xdiff', False, b'use xdiff algorithm'),
2398 (b'', b'xdiff', False, b'use xdiff algorithm'),
2400 ],
2399 ],
2401 b'-c|-m|FILE REV',
2400 b'-c|-m|FILE REV',
2402 )
2401 )
2403 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
2402 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
2404 """benchmark a bdiff between revisions
2403 """benchmark a bdiff between revisions
2405
2404
2406 By default, benchmark a bdiff between its delta parent and itself.
2405 By default, benchmark a bdiff between its delta parent and itself.
2407
2406
2408 With ``--count``, benchmark bdiffs between delta parents and self for N
2407 With ``--count``, benchmark bdiffs between delta parents and self for N
2409 revisions starting at the specified revision.
2408 revisions starting at the specified revision.
2410
2409
2411 With ``--alldata``, assume the requested revision is a changeset and
2410 With ``--alldata``, assume the requested revision is a changeset and
2412 measure bdiffs for all changes related to that changeset (manifest
2411 measure bdiffs for all changes related to that changeset (manifest
2413 and filelogs).
2412 and filelogs).
2414 """
2413 """
2415 opts = _byteskwargs(opts)
2414 opts = _byteskwargs(opts)
2416
2415
2417 if opts[b'xdiff'] and not opts[b'blocks']:
2416 if opts[b'xdiff'] and not opts[b'blocks']:
2418 raise error.CommandError(b'perfbdiff', b'--xdiff requires --blocks')
2417 raise error.CommandError(b'perfbdiff', b'--xdiff requires --blocks')
2419
2418
2420 if opts[b'alldata']:
2419 if opts[b'alldata']:
2421 opts[b'changelog'] = True
2420 opts[b'changelog'] = True
2422
2421
2423 if opts.get(b'changelog') or opts.get(b'manifest'):
2422 if opts.get(b'changelog') or opts.get(b'manifest'):
2424 file_, rev = None, file_
2423 file_, rev = None, file_
2425 elif rev is None:
2424 elif rev is None:
2426 raise error.CommandError(b'perfbdiff', b'invalid arguments')
2425 raise error.CommandError(b'perfbdiff', b'invalid arguments')
2427
2426
2428 blocks = opts[b'blocks']
2427 blocks = opts[b'blocks']
2429 xdiff = opts[b'xdiff']
2428 xdiff = opts[b'xdiff']
2430 textpairs = []
2429 textpairs = []
2431
2430
2432 r = cmdutil.openrevlog(repo, b'perfbdiff', file_, opts)
2431 r = cmdutil.openrevlog(repo, b'perfbdiff', file_, opts)
2433
2432
2434 startrev = r.rev(r.lookup(rev))
2433 startrev = r.rev(r.lookup(rev))
2435 for rev in range(startrev, min(startrev + count, len(r) - 1)):
2434 for rev in range(startrev, min(startrev + count, len(r) - 1)):
2436 if opts[b'alldata']:
2435 if opts[b'alldata']:
2437 # Load revisions associated with changeset.
2436 # Load revisions associated with changeset.
2438 ctx = repo[rev]
2437 ctx = repo[rev]
2439 mtext = _manifestrevision(repo, ctx.manifestnode())
2438 mtext = _manifestrevision(repo, ctx.manifestnode())
2440 for pctx in ctx.parents():
2439 for pctx in ctx.parents():
2441 pman = _manifestrevision(repo, pctx.manifestnode())
2440 pman = _manifestrevision(repo, pctx.manifestnode())
2442 textpairs.append((pman, mtext))
2441 textpairs.append((pman, mtext))
2443
2442
2444 # Load filelog revisions by iterating manifest delta.
2443 # Load filelog revisions by iterating manifest delta.
2445 man = ctx.manifest()
2444 man = ctx.manifest()
2446 pman = ctx.p1().manifest()
2445 pman = ctx.p1().manifest()
2447 for filename, change in pman.diff(man).items():
2446 for filename, change in pman.diff(man).items():
2448 fctx = repo.file(filename)
2447 fctx = repo.file(filename)
2449 f1 = fctx.revision(change[0][0] or -1)
2448 f1 = fctx.revision(change[0][0] or -1)
2450 f2 = fctx.revision(change[1][0] or -1)
2449 f2 = fctx.revision(change[1][0] or -1)
2451 textpairs.append((f1, f2))
2450 textpairs.append((f1, f2))
2452 else:
2451 else:
2453 dp = r.deltaparent(rev)
2452 dp = r.deltaparent(rev)
2454 textpairs.append((r.revision(dp), r.revision(rev)))
2453 textpairs.append((r.revision(dp), r.revision(rev)))
2455
2454
2456 withthreads = threads > 0
2455 withthreads = threads > 0
2457 if not withthreads:
2456 if not withthreads:
2458
2457
2459 def d():
2458 def d():
2460 for pair in textpairs:
2459 for pair in textpairs:
2461 if xdiff:
2460 if xdiff:
2462 mdiff.bdiff.xdiffblocks(*pair)
2461 mdiff.bdiff.xdiffblocks(*pair)
2463 elif blocks:
2462 elif blocks:
2464 mdiff.bdiff.blocks(*pair)
2463 mdiff.bdiff.blocks(*pair)
2465 else:
2464 else:
2466 mdiff.textdiff(*pair)
2465 mdiff.textdiff(*pair)
2467
2466
2468 else:
2467 else:
2469 q = queue()
2468 q = queue()
2470 for i in _xrange(threads):
2469 for i in _xrange(threads):
2471 q.put(None)
2470 q.put(None)
2472 ready = threading.Condition()
2471 ready = threading.Condition()
2473 done = threading.Event()
2472 done = threading.Event()
2474 for i in _xrange(threads):
2473 for i in _xrange(threads):
2475 threading.Thread(
2474 threading.Thread(
2476 target=_bdiffworker, args=(q, blocks, xdiff, ready, done)
2475 target=_bdiffworker, args=(q, blocks, xdiff, ready, done)
2477 ).start()
2476 ).start()
2478 q.join()
2477 q.join()
2479
2478
2480 def d():
2479 def d():
2481 for pair in textpairs:
2480 for pair in textpairs:
2482 q.put(pair)
2481 q.put(pair)
2483 for i in _xrange(threads):
2482 for i in _xrange(threads):
2484 q.put(None)
2483 q.put(None)
2485 with ready:
2484 with ready:
2486 ready.notify_all()
2485 ready.notify_all()
2487 q.join()
2486 q.join()
2488
2487
2489 timer, fm = gettimer(ui, opts)
2488 timer, fm = gettimer(ui, opts)
2490 timer(d)
2489 timer(d)
2491 fm.end()
2490 fm.end()
2492
2491
2493 if withthreads:
2492 if withthreads:
2494 done.set()
2493 done.set()
2495 for i in _xrange(threads):
2494 for i in _xrange(threads):
2496 q.put(None)
2495 q.put(None)
2497 with ready:
2496 with ready:
2498 ready.notify_all()
2497 ready.notify_all()
2499
2498
2500
2499
2501 @command(
2500 @command(
2502 b'perf::unidiff|perfunidiff',
2501 b'perf::unidiff|perfunidiff',
2503 revlogopts
2502 revlogopts
2504 + formatteropts
2503 + formatteropts
2505 + [
2504 + [
2506 (
2505 (
2507 b'',
2506 b'',
2508 b'count',
2507 b'count',
2509 1,
2508 1,
2510 b'number of revisions to test (when using --startrev)',
2509 b'number of revisions to test (when using --startrev)',
2511 ),
2510 ),
2512 (b'', b'alldata', False, b'test unidiffs for all associated revisions'),
2511 (b'', b'alldata', False, b'test unidiffs for all associated revisions'),
2513 ],
2512 ],
2514 b'-c|-m|FILE REV',
2513 b'-c|-m|FILE REV',
2515 )
2514 )
2516 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
2515 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
2517 """benchmark a unified diff between revisions
2516 """benchmark a unified diff between revisions
2518
2517
2519 This doesn't include any copy tracing - it's just a unified diff
2518 This doesn't include any copy tracing - it's just a unified diff
2520 of the texts.
2519 of the texts.
2521
2520
2522 By default, benchmark a diff between its delta parent and itself.
2521 By default, benchmark a diff between its delta parent and itself.
2523
2522
2524 With ``--count``, benchmark diffs between delta parents and self for N
2523 With ``--count``, benchmark diffs between delta parents and self for N
2525 revisions starting at the specified revision.
2524 revisions starting at the specified revision.
2526
2525
2527 With ``--alldata``, assume the requested revision is a changeset and
2526 With ``--alldata``, assume the requested revision is a changeset and
2528 measure diffs for all changes related to that changeset (manifest
2527 measure diffs for all changes related to that changeset (manifest
2529 and filelogs).
2528 and filelogs).
2530 """
2529 """
2531 opts = _byteskwargs(opts)
2530 opts = _byteskwargs(opts)
2532 if opts[b'alldata']:
2531 if opts[b'alldata']:
2533 opts[b'changelog'] = True
2532 opts[b'changelog'] = True
2534
2533
2535 if opts.get(b'changelog') or opts.get(b'manifest'):
2534 if opts.get(b'changelog') or opts.get(b'manifest'):
2536 file_, rev = None, file_
2535 file_, rev = None, file_
2537 elif rev is None:
2536 elif rev is None:
2538 raise error.CommandError(b'perfunidiff', b'invalid arguments')
2537 raise error.CommandError(b'perfunidiff', b'invalid arguments')
2539
2538
2540 textpairs = []
2539 textpairs = []
2541
2540
2542 r = cmdutil.openrevlog(repo, b'perfunidiff', file_, opts)
2541 r = cmdutil.openrevlog(repo, b'perfunidiff', file_, opts)
2543
2542
2544 startrev = r.rev(r.lookup(rev))
2543 startrev = r.rev(r.lookup(rev))
2545 for rev in range(startrev, min(startrev + count, len(r) - 1)):
2544 for rev in range(startrev, min(startrev + count, len(r) - 1)):
2546 if opts[b'alldata']:
2545 if opts[b'alldata']:
2547 # Load revisions associated with changeset.
2546 # Load revisions associated with changeset.
2548 ctx = repo[rev]
2547 ctx = repo[rev]
2549 mtext = _manifestrevision(repo, ctx.manifestnode())
2548 mtext = _manifestrevision(repo, ctx.manifestnode())
2550 for pctx in ctx.parents():
2549 for pctx in ctx.parents():
2551 pman = _manifestrevision(repo, pctx.manifestnode())
2550 pman = _manifestrevision(repo, pctx.manifestnode())
2552 textpairs.append((pman, mtext))
2551 textpairs.append((pman, mtext))
2553
2552
2554 # Load filelog revisions by iterating manifest delta.
2553 # Load filelog revisions by iterating manifest delta.
2555 man = ctx.manifest()
2554 man = ctx.manifest()
2556 pman = ctx.p1().manifest()
2555 pman = ctx.p1().manifest()
2557 for filename, change in pman.diff(man).items():
2556 for filename, change in pman.diff(man).items():
2558 fctx = repo.file(filename)
2557 fctx = repo.file(filename)
2559 f1 = fctx.revision(change[0][0] or -1)
2558 f1 = fctx.revision(change[0][0] or -1)
2560 f2 = fctx.revision(change[1][0] or -1)
2559 f2 = fctx.revision(change[1][0] or -1)
2561 textpairs.append((f1, f2))
2560 textpairs.append((f1, f2))
2562 else:
2561 else:
2563 dp = r.deltaparent(rev)
2562 dp = r.deltaparent(rev)
2564 textpairs.append((r.revision(dp), r.revision(rev)))
2563 textpairs.append((r.revision(dp), r.revision(rev)))
2565
2564
2566 def d():
2565 def d():
2567 for left, right in textpairs:
2566 for left, right in textpairs:
2568 # The date strings don't matter, so we pass empty strings.
2567 # The date strings don't matter, so we pass empty strings.
2569 headerlines, hunks = mdiff.unidiff(
2568 headerlines, hunks = mdiff.unidiff(
2570 left, b'', right, b'', b'left', b'right', binary=False
2569 left, b'', right, b'', b'left', b'right', binary=False
2571 )
2570 )
2572 # consume iterators in roughly the way patch.py does
2571 # consume iterators in roughly the way patch.py does
2573 b'\n'.join(headerlines)
2572 b'\n'.join(headerlines)
2574 b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
2573 b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
2575
2574
2576 timer, fm = gettimer(ui, opts)
2575 timer, fm = gettimer(ui, opts)
2577 timer(d)
2576 timer(d)
2578 fm.end()
2577 fm.end()
2579
2578
2580
2579
2581 @command(b'perf::diffwd|perfdiffwd', formatteropts)
2580 @command(b'perf::diffwd|perfdiffwd', formatteropts)
2582 def perfdiffwd(ui, repo, **opts):
2581 def perfdiffwd(ui, repo, **opts):
2583 """Profile diff of working directory changes"""
2582 """Profile diff of working directory changes"""
2584 opts = _byteskwargs(opts)
2583 opts = _byteskwargs(opts)
2585 timer, fm = gettimer(ui, opts)
2584 timer, fm = gettimer(ui, opts)
2586 options = {
2585 options = {
2587 'w': 'ignore_all_space',
2586 'w': 'ignore_all_space',
2588 'b': 'ignore_space_change',
2587 'b': 'ignore_space_change',
2589 'B': 'ignore_blank_lines',
2588 'B': 'ignore_blank_lines',
2590 }
2589 }
2591
2590
2592 for diffopt in ('', 'w', 'b', 'B', 'wB'):
2591 for diffopt in ('', 'w', 'b', 'B', 'wB'):
2593 opts = {options[c]: b'1' for c in diffopt}
2592 opts = {options[c]: b'1' for c in diffopt}
2594
2593
2595 def d():
2594 def d():
2596 ui.pushbuffer()
2595 ui.pushbuffer()
2597 commands.diff(ui, repo, **opts)
2596 commands.diff(ui, repo, **opts)
2598 ui.popbuffer()
2597 ui.popbuffer()
2599
2598
2600 diffopt = diffopt.encode('ascii')
2599 diffopt = diffopt.encode('ascii')
2601 title = b'diffopts: %s' % (diffopt and (b'-' + diffopt) or b'none')
2600 title = b'diffopts: %s' % (diffopt and (b'-' + diffopt) or b'none')
2602 timer(d, title=title)
2601 timer(d, title=title)
2603 fm.end()
2602 fm.end()
2604
2603
2605
2604
2606 @command(
2605 @command(
2607 b'perf::revlogindex|perfrevlogindex',
2606 b'perf::revlogindex|perfrevlogindex',
2608 revlogopts + formatteropts,
2607 revlogopts + formatteropts,
2609 b'-c|-m|FILE',
2608 b'-c|-m|FILE',
2610 )
2609 )
2611 def perfrevlogindex(ui, repo, file_=None, **opts):
2610 def perfrevlogindex(ui, repo, file_=None, **opts):
2612 """Benchmark operations against a revlog index.
2611 """Benchmark operations against a revlog index.
2613
2612
2614 This tests constructing a revlog instance, reading index data,
2613 This tests constructing a revlog instance, reading index data,
2615 parsing index data, and performing various operations related to
2614 parsing index data, and performing various operations related to
2616 index data.
2615 index data.
2617 """
2616 """
2618
2617
2619 opts = _byteskwargs(opts)
2618 opts = _byteskwargs(opts)
2620
2619
2621 rl = cmdutil.openrevlog(repo, b'perfrevlogindex', file_, opts)
2620 rl = cmdutil.openrevlog(repo, b'perfrevlogindex', file_, opts)
2622
2621
2623 opener = getattr(rl, 'opener') # trick linter
2622 opener = getattr(rl, 'opener') # trick linter
2624 # compat with hg <= 5.8
2623 # compat with hg <= 5.8
2625 radix = getattr(rl, 'radix', None)
2624 radix = getattr(rl, 'radix', None)
2626 indexfile = getattr(rl, '_indexfile', None)
2625 indexfile = getattr(rl, '_indexfile', None)
2627 if indexfile is None:
2626 if indexfile is None:
2628 # compatibility with <= hg-5.8
2627 # compatibility with <= hg-5.8
2629 indexfile = getattr(rl, 'indexfile')
2628 indexfile = getattr(rl, 'indexfile')
2630 data = opener.read(indexfile)
2629 data = opener.read(indexfile)
2631
2630
2632 header = struct.unpack(b'>I', data[0:4])[0]
2631 header = struct.unpack(b'>I', data[0:4])[0]
2633 version = header & 0xFFFF
2632 version = header & 0xFFFF
2634 if version == 1:
2633 if version == 1:
2635 inline = header & (1 << 16)
2634 inline = header & (1 << 16)
2636 else:
2635 else:
2637 raise error.Abort(b'unsupported revlog version: %d' % version)
2636 raise error.Abort(b'unsupported revlog version: %d' % version)
2638
2637
2639 parse_index_v1 = getattr(mercurial.revlog, 'parse_index_v1', None)
2638 parse_index_v1 = getattr(mercurial.revlog, 'parse_index_v1', None)
2640 if parse_index_v1 is None:
2639 if parse_index_v1 is None:
2641 parse_index_v1 = mercurial.revlog.revlogio().parseindex
2640 parse_index_v1 = mercurial.revlog.revlogio().parseindex
2642
2641
2643 rllen = len(rl)
2642 rllen = len(rl)
2644
2643
2645 node0 = rl.node(0)
2644 node0 = rl.node(0)
2646 node25 = rl.node(rllen // 4)
2645 node25 = rl.node(rllen // 4)
2647 node50 = rl.node(rllen // 2)
2646 node50 = rl.node(rllen // 2)
2648 node75 = rl.node(rllen // 4 * 3)
2647 node75 = rl.node(rllen // 4 * 3)
2649 node100 = rl.node(rllen - 1)
2648 node100 = rl.node(rllen - 1)
2650
2649
2651 allrevs = range(rllen)
2650 allrevs = range(rllen)
2652 allrevsrev = list(reversed(allrevs))
2651 allrevsrev = list(reversed(allrevs))
2653 allnodes = [rl.node(rev) for rev in range(rllen)]
2652 allnodes = [rl.node(rev) for rev in range(rllen)]
2654 allnodesrev = list(reversed(allnodes))
2653 allnodesrev = list(reversed(allnodes))
2655
2654
2656 def constructor():
2655 def constructor():
2657 if radix is not None:
2656 if radix is not None:
2658 revlog(opener, radix=radix)
2657 revlog(opener, radix=radix)
2659 else:
2658 else:
2660 # hg <= 5.8
2659 # hg <= 5.8
2661 revlog(opener, indexfile=indexfile)
2660 revlog(opener, indexfile=indexfile)
2662
2661
2663 def read():
2662 def read():
2664 with opener(indexfile) as fh:
2663 with opener(indexfile) as fh:
2665 fh.read()
2664 fh.read()
2666
2665
2667 def parseindex():
2666 def parseindex():
2668 parse_index_v1(data, inline)
2667 parse_index_v1(data, inline)
2669
2668
2670 def getentry(revornode):
2669 def getentry(revornode):
2671 index = parse_index_v1(data, inline)[0]
2670 index = parse_index_v1(data, inline)[0]
2672 index[revornode]
2671 index[revornode]
2673
2672
2674 def getentries(revs, count=1):
2673 def getentries(revs, count=1):
2675 index = parse_index_v1(data, inline)[0]
2674 index = parse_index_v1(data, inline)[0]
2676
2675
2677 for i in range(count):
2676 for i in range(count):
2678 for rev in revs:
2677 for rev in revs:
2679 index[rev]
2678 index[rev]
2680
2679
2681 def resolvenode(node):
2680 def resolvenode(node):
2682 index = parse_index_v1(data, inline)[0]
2681 index = parse_index_v1(data, inline)[0]
2683 rev = getattr(index, 'rev', None)
2682 rev = getattr(index, 'rev', None)
2684 if rev is None:
2683 if rev is None:
2685 nodemap = getattr(parse_index_v1(data, inline)[0], 'nodemap', None)
2684 nodemap = getattr(parse_index_v1(data, inline)[0], 'nodemap', None)
2686 # This only works for the C code.
2685 # This only works for the C code.
2687 if nodemap is None:
2686 if nodemap is None:
2688 return
2687 return
2689 rev = nodemap.__getitem__
2688 rev = nodemap.__getitem__
2690
2689
2691 try:
2690 try:
2692 rev(node)
2691 rev(node)
2693 except error.RevlogError:
2692 except error.RevlogError:
2694 pass
2693 pass
2695
2694
2696 def resolvenodes(nodes, count=1):
2695 def resolvenodes(nodes, count=1):
2697 index = parse_index_v1(data, inline)[0]
2696 index = parse_index_v1(data, inline)[0]
2698 rev = getattr(index, 'rev', None)
2697 rev = getattr(index, 'rev', None)
2699 if rev is None:
2698 if rev is None:
2700 nodemap = getattr(parse_index_v1(data, inline)[0], 'nodemap', None)
2699 nodemap = getattr(parse_index_v1(data, inline)[0], 'nodemap', None)
2701 # This only works for the C code.
2700 # This only works for the C code.
2702 if nodemap is None:
2701 if nodemap is None:
2703 return
2702 return
2704 rev = nodemap.__getitem__
2703 rev = nodemap.__getitem__
2705
2704
2706 for i in range(count):
2705 for i in range(count):
2707 for node in nodes:
2706 for node in nodes:
2708 try:
2707 try:
2709 rev(node)
2708 rev(node)
2710 except error.RevlogError:
2709 except error.RevlogError:
2711 pass
2710 pass
2712
2711
2713 benches = [
2712 benches = [
2714 (constructor, b'revlog constructor'),
2713 (constructor, b'revlog constructor'),
2715 (read, b'read'),
2714 (read, b'read'),
2716 (parseindex, b'create index object'),
2715 (parseindex, b'create index object'),
2717 (lambda: getentry(0), b'retrieve index entry for rev 0'),
2716 (lambda: getentry(0), b'retrieve index entry for rev 0'),
2718 (lambda: resolvenode(b'a' * 20), b'look up missing node'),
2717 (lambda: resolvenode(b'a' * 20), b'look up missing node'),
2719 (lambda: resolvenode(node0), b'look up node at rev 0'),
2718 (lambda: resolvenode(node0), b'look up node at rev 0'),
2720 (lambda: resolvenode(node25), b'look up node at 1/4 len'),
2719 (lambda: resolvenode(node25), b'look up node at 1/4 len'),
2721 (lambda: resolvenode(node50), b'look up node at 1/2 len'),
2720 (lambda: resolvenode(node50), b'look up node at 1/2 len'),
2722 (lambda: resolvenode(node75), b'look up node at 3/4 len'),
2721 (lambda: resolvenode(node75), b'look up node at 3/4 len'),
2723 (lambda: resolvenode(node100), b'look up node at tip'),
2722 (lambda: resolvenode(node100), b'look up node at tip'),
2724 # 2x variation is to measure caching impact.
2723 # 2x variation is to measure caching impact.
2725 (lambda: resolvenodes(allnodes), b'look up all nodes (forward)'),
2724 (lambda: resolvenodes(allnodes), b'look up all nodes (forward)'),
2726 (lambda: resolvenodes(allnodes, 2), b'look up all nodes 2x (forward)'),
2725 (lambda: resolvenodes(allnodes, 2), b'look up all nodes 2x (forward)'),
2727 (lambda: resolvenodes(allnodesrev), b'look up all nodes (reverse)'),
2726 (lambda: resolvenodes(allnodesrev), b'look up all nodes (reverse)'),
2728 (
2727 (
2729 lambda: resolvenodes(allnodesrev, 2),
2728 lambda: resolvenodes(allnodesrev, 2),
2730 b'look up all nodes 2x (reverse)',
2729 b'look up all nodes 2x (reverse)',
2731 ),
2730 ),
2732 (lambda: getentries(allrevs), b'retrieve all index entries (forward)'),
2731 (lambda: getentries(allrevs), b'retrieve all index entries (forward)'),
2733 (
2732 (
2734 lambda: getentries(allrevs, 2),
2733 lambda: getentries(allrevs, 2),
2735 b'retrieve all index entries 2x (forward)',
2734 b'retrieve all index entries 2x (forward)',
2736 ),
2735 ),
2737 (
2736 (
2738 lambda: getentries(allrevsrev),
2737 lambda: getentries(allrevsrev),
2739 b'retrieve all index entries (reverse)',
2738 b'retrieve all index entries (reverse)',
2740 ),
2739 ),
2741 (
2740 (
2742 lambda: getentries(allrevsrev, 2),
2741 lambda: getentries(allrevsrev, 2),
2743 b'retrieve all index entries 2x (reverse)',
2742 b'retrieve all index entries 2x (reverse)',
2744 ),
2743 ),
2745 ]
2744 ]
2746
2745
2747 for fn, title in benches:
2746 for fn, title in benches:
2748 timer, fm = gettimer(ui, opts)
2747 timer, fm = gettimer(ui, opts)
2749 timer(fn, title=title)
2748 timer(fn, title=title)
2750 fm.end()
2749 fm.end()
2751
2750
2752
2751
2753 @command(
2752 @command(
2754 b'perf::revlogrevisions|perfrevlogrevisions',
2753 b'perf::revlogrevisions|perfrevlogrevisions',
2755 revlogopts
2754 revlogopts
2756 + formatteropts
2755 + formatteropts
2757 + [
2756 + [
2758 (b'd', b'dist', 100, b'distance between the revisions'),
2757 (b'd', b'dist', 100, b'distance between the revisions'),
2759 (b's', b'startrev', 0, b'revision to start reading at'),
2758 (b's', b'startrev', 0, b'revision to start reading at'),
2760 (b'', b'reverse', False, b'read in reverse'),
2759 (b'', b'reverse', False, b'read in reverse'),
2761 ],
2760 ],
2762 b'-c|-m|FILE',
2761 b'-c|-m|FILE',
2763 )
2762 )
2764 def perfrevlogrevisions(
2763 def perfrevlogrevisions(
2765 ui, repo, file_=None, startrev=0, reverse=False, **opts
2764 ui, repo, file_=None, startrev=0, reverse=False, **opts
2766 ):
2765 ):
2767 """Benchmark reading a series of revisions from a revlog.
2766 """Benchmark reading a series of revisions from a revlog.
2768
2767
2769 By default, we read every ``-d/--dist`` revision from 0 to tip of
2768 By default, we read every ``-d/--dist`` revision from 0 to tip of
2770 the specified revlog.
2769 the specified revlog.
2771
2770
2772 The start revision can be defined via ``-s/--startrev``.
2771 The start revision can be defined via ``-s/--startrev``.
2773 """
2772 """
2774 opts = _byteskwargs(opts)
2773 opts = _byteskwargs(opts)
2775
2774
2776 rl = cmdutil.openrevlog(repo, b'perfrevlogrevisions', file_, opts)
2775 rl = cmdutil.openrevlog(repo, b'perfrevlogrevisions', file_, opts)
2777 rllen = getlen(ui)(rl)
2776 rllen = getlen(ui)(rl)
2778
2777
2779 if startrev < 0:
2778 if startrev < 0:
2780 startrev = rllen + startrev
2779 startrev = rllen + startrev
2781
2780
2782 def d():
2781 def d():
2783 rl.clearcaches()
2782 rl.clearcaches()
2784
2783
2785 beginrev = startrev
2784 beginrev = startrev
2786 endrev = rllen
2785 endrev = rllen
2787 dist = opts[b'dist']
2786 dist = opts[b'dist']
2788
2787
2789 if reverse:
2788 if reverse:
2790 beginrev, endrev = endrev - 1, beginrev - 1
2789 beginrev, endrev = endrev - 1, beginrev - 1
2791 dist = -1 * dist
2790 dist = -1 * dist
2792
2791
2793 for x in _xrange(beginrev, endrev, dist):
2792 for x in _xrange(beginrev, endrev, dist):
2794 # Old revisions don't support passing int.
2793 # Old revisions don't support passing int.
2795 n = rl.node(x)
2794 n = rl.node(x)
2796 rl.revision(n)
2795 rl.revision(n)
2797
2796
2798 timer, fm = gettimer(ui, opts)
2797 timer, fm = gettimer(ui, opts)
2799 timer(d)
2798 timer(d)
2800 fm.end()
2799 fm.end()
2801
2800
2802
2801
2803 @command(
2802 @command(
2804 b'perf::revlogwrite|perfrevlogwrite',
2803 b'perf::revlogwrite|perfrevlogwrite',
2805 revlogopts
2804 revlogopts
2806 + formatteropts
2805 + formatteropts
2807 + [
2806 + [
2808 (b's', b'startrev', 1000, b'revision to start writing at'),
2807 (b's', b'startrev', 1000, b'revision to start writing at'),
2809 (b'', b'stoprev', -1, b'last revision to write'),
2808 (b'', b'stoprev', -1, b'last revision to write'),
2810 (b'', b'count', 3, b'number of passes to perform'),
2809 (b'', b'count', 3, b'number of passes to perform'),
2811 (b'', b'details', False, b'print timing for every revisions tested'),
2810 (b'', b'details', False, b'print timing for every revisions tested'),
2812 (b'', b'source', b'full', b'the kind of data feed in the revlog'),
2811 (b'', b'source', b'full', b'the kind of data feed in the revlog'),
2813 (b'', b'lazydeltabase', True, b'try the provided delta first'),
2812 (b'', b'lazydeltabase', True, b'try the provided delta first'),
2814 (b'', b'clear-caches', True, b'clear revlog cache between calls'),
2813 (b'', b'clear-caches', True, b'clear revlog cache between calls'),
2815 ],
2814 ],
2816 b'-c|-m|FILE',
2815 b'-c|-m|FILE',
2817 )
2816 )
2818 def perfrevlogwrite(ui, repo, file_=None, startrev=1000, stoprev=-1, **opts):
2817 def perfrevlogwrite(ui, repo, file_=None, startrev=1000, stoprev=-1, **opts):
2819 """Benchmark writing a series of revisions to a revlog.
2818 """Benchmark writing a series of revisions to a revlog.
2820
2819
2821 Possible source values are:
2820 Possible source values are:
2822 * `full`: add from a full text (default).
2821 * `full`: add from a full text (default).
2823 * `parent-1`: add from a delta to the first parent
2822 * `parent-1`: add from a delta to the first parent
2824 * `parent-2`: add from a delta to the second parent if it exists
2823 * `parent-2`: add from a delta to the second parent if it exists
2825 (use a delta from the first parent otherwise)
2824 (use a delta from the first parent otherwise)
2826 * `parent-smallest`: add from the smallest delta (either p1 or p2)
2825 * `parent-smallest`: add from the smallest delta (either p1 or p2)
2827 * `storage`: add from the existing precomputed deltas
2826 * `storage`: add from the existing precomputed deltas
2828
2827
2829 Note: This performance command measures performance in a custom way. As a
2828 Note: This performance command measures performance in a custom way. As a
2830 result some of the global configuration of the 'perf' command does not
2829 result some of the global configuration of the 'perf' command does not
2831 apply to it:
2830 apply to it:
2832
2831
2833 * ``pre-run``: disabled
2832 * ``pre-run``: disabled
2834
2833
2835 * ``profile-benchmark``: disabled
2834 * ``profile-benchmark``: disabled
2836
2835
2837 * ``run-limits``: disabled use --count instead
2836 * ``run-limits``: disabled use --count instead
2838 """
2837 """
2839 opts = _byteskwargs(opts)
2838 opts = _byteskwargs(opts)
2840
2839
2841 rl = cmdutil.openrevlog(repo, b'perfrevlogwrite', file_, opts)
2840 rl = cmdutil.openrevlog(repo, b'perfrevlogwrite', file_, opts)
2842 rllen = getlen(ui)(rl)
2841 rllen = getlen(ui)(rl)
2843 if startrev < 0:
2842 if startrev < 0:
2844 startrev = rllen + startrev
2843 startrev = rllen + startrev
2845 if stoprev < 0:
2844 if stoprev < 0:
2846 stoprev = rllen + stoprev
2845 stoprev = rllen + stoprev
2847
2846
2848 lazydeltabase = opts['lazydeltabase']
2847 lazydeltabase = opts['lazydeltabase']
2849 source = opts['source']
2848 source = opts['source']
2850 clearcaches = opts['clear_caches']
2849 clearcaches = opts['clear_caches']
2851 validsource = (
2850 validsource = (
2852 b'full',
2851 b'full',
2853 b'parent-1',
2852 b'parent-1',
2854 b'parent-2',
2853 b'parent-2',
2855 b'parent-smallest',
2854 b'parent-smallest',
2856 b'storage',
2855 b'storage',
2857 )
2856 )
2858 if source not in validsource:
2857 if source not in validsource:
2859 raise error.Abort('invalid source type: %s' % source)
2858 raise error.Abort('invalid source type: %s' % source)
2860
2859
2861 ### actually gather results
2860 ### actually gather results
2862 count = opts['count']
2861 count = opts['count']
2863 if count <= 0:
2862 if count <= 0:
2864 raise error.Abort('invalide run count: %d' % count)
2863 raise error.Abort('invalide run count: %d' % count)
2865 allresults = []
2864 allresults = []
2866 for c in range(count):
2865 for c in range(count):
2867 timing = _timeonewrite(
2866 timing = _timeonewrite(
2868 ui,
2867 ui,
2869 rl,
2868 rl,
2870 source,
2869 source,
2871 startrev,
2870 startrev,
2872 stoprev,
2871 stoprev,
2873 c + 1,
2872 c + 1,
2874 lazydeltabase=lazydeltabase,
2873 lazydeltabase=lazydeltabase,
2875 clearcaches=clearcaches,
2874 clearcaches=clearcaches,
2876 )
2875 )
2877 allresults.append(timing)
2876 allresults.append(timing)
2878
2877
2879 ### consolidate the results in a single list
2878 ### consolidate the results in a single list
2880 results = []
2879 results = []
2881 for idx, (rev, t) in enumerate(allresults[0]):
2880 for idx, (rev, t) in enumerate(allresults[0]):
2882 ts = [t]
2881 ts = [t]
2883 for other in allresults[1:]:
2882 for other in allresults[1:]:
2884 orev, ot = other[idx]
2883 orev, ot = other[idx]
2885 assert orev == rev
2884 assert orev == rev
2886 ts.append(ot)
2885 ts.append(ot)
2887 results.append((rev, ts))
2886 results.append((rev, ts))
2888 resultcount = len(results)
2887 resultcount = len(results)
2889
2888
2890 ### Compute and display relevant statistics
2889 ### Compute and display relevant statistics
2891
2890
2892 # get a formatter
2891 # get a formatter
2893 fm = ui.formatter(b'perf', opts)
2892 fm = ui.formatter(b'perf', opts)
2894 displayall = ui.configbool(b"perf", b"all-timing", False)
2893 displayall = ui.configbool(b"perf", b"all-timing", False)
2895
2894
2896 # print individual details if requested
2895 # print individual details if requested
2897 if opts['details']:
2896 if opts['details']:
2898 for idx, item in enumerate(results, 1):
2897 for idx, item in enumerate(results, 1):
2899 rev, data = item
2898 rev, data = item
2900 title = 'revisions #%d of %d, rev %d' % (idx, resultcount, rev)
2899 title = 'revisions #%d of %d, rev %d' % (idx, resultcount, rev)
2901 formatone(fm, data, title=title, displayall=displayall)
2900 formatone(fm, data, title=title, displayall=displayall)
2902
2901
2903 # sorts results by median time
2902 # sorts results by median time
2904 results.sort(key=lambda x: sorted(x[1])[len(x[1]) // 2])
2903 results.sort(key=lambda x: sorted(x[1])[len(x[1]) // 2])
2905 # list of (name, index) to display)
2904 # list of (name, index) to display)
2906 relevants = [
2905 relevants = [
2907 ("min", 0),
2906 ("min", 0),
2908 ("10%", resultcount * 10 // 100),
2907 ("10%", resultcount * 10 // 100),
2909 ("25%", resultcount * 25 // 100),
2908 ("25%", resultcount * 25 // 100),
2910 ("50%", resultcount * 70 // 100),
2909 ("50%", resultcount * 70 // 100),
2911 ("75%", resultcount * 75 // 100),
2910 ("75%", resultcount * 75 // 100),
2912 ("90%", resultcount * 90 // 100),
2911 ("90%", resultcount * 90 // 100),
2913 ("95%", resultcount * 95 // 100),
2912 ("95%", resultcount * 95 // 100),
2914 ("99%", resultcount * 99 // 100),
2913 ("99%", resultcount * 99 // 100),
2915 ("99.9%", resultcount * 999 // 1000),
2914 ("99.9%", resultcount * 999 // 1000),
2916 ("99.99%", resultcount * 9999 // 10000),
2915 ("99.99%", resultcount * 9999 // 10000),
2917 ("99.999%", resultcount * 99999 // 100000),
2916 ("99.999%", resultcount * 99999 // 100000),
2918 ("max", -1),
2917 ("max", -1),
2919 ]
2918 ]
2920 if not ui.quiet:
2919 if not ui.quiet:
2921 for name, idx in relevants:
2920 for name, idx in relevants:
2922 data = results[idx]
2921 data = results[idx]
2923 title = '%s of %d, rev %d' % (name, resultcount, data[0])
2922 title = '%s of %d, rev %d' % (name, resultcount, data[0])
2924 formatone(fm, data[1], title=title, displayall=displayall)
2923 formatone(fm, data[1], title=title, displayall=displayall)
2925
2924
2926 # XXX summing that many float will not be very precise, we ignore this fact
2925 # XXX summing that many float will not be very precise, we ignore this fact
2927 # for now
2926 # for now
2928 totaltime = []
2927 totaltime = []
2929 for item in allresults:
2928 for item in allresults:
2930 totaltime.append(
2929 totaltime.append(
2931 (
2930 (
2932 sum(x[1][0] for x in item),
2931 sum(x[1][0] for x in item),
2933 sum(x[1][1] for x in item),
2932 sum(x[1][1] for x in item),
2934 sum(x[1][2] for x in item),
2933 sum(x[1][2] for x in item),
2935 )
2934 )
2936 )
2935 )
2937 formatone(
2936 formatone(
2938 fm,
2937 fm,
2939 totaltime,
2938 totaltime,
2940 title="total time (%d revs)" % resultcount,
2939 title="total time (%d revs)" % resultcount,
2941 displayall=displayall,
2940 displayall=displayall,
2942 )
2941 )
2943 fm.end()
2942 fm.end()
2944
2943
2945
2944
2946 class _faketr(object):
2945 class _faketr(object):
2947 def add(s, x, y, z=None):
2946 def add(s, x, y, z=None):
2948 return None
2947 return None
2949
2948
2950
2949
2951 def _timeonewrite(
2950 def _timeonewrite(
2952 ui,
2951 ui,
2953 orig,
2952 orig,
2954 source,
2953 source,
2955 startrev,
2954 startrev,
2956 stoprev,
2955 stoprev,
2957 runidx=None,
2956 runidx=None,
2958 lazydeltabase=True,
2957 lazydeltabase=True,
2959 clearcaches=True,
2958 clearcaches=True,
2960 ):
2959 ):
2961 timings = []
2960 timings = []
2962 tr = _faketr()
2961 tr = _faketr()
2963 with _temprevlog(ui, orig, startrev) as dest:
2962 with _temprevlog(ui, orig, startrev) as dest:
2964 dest._lazydeltabase = lazydeltabase
2963 dest._lazydeltabase = lazydeltabase
2965 revs = list(orig.revs(startrev, stoprev))
2964 revs = list(orig.revs(startrev, stoprev))
2966 total = len(revs)
2965 total = len(revs)
2967 topic = 'adding'
2966 topic = 'adding'
2968 if runidx is not None:
2967 if runidx is not None:
2969 topic += ' (run #%d)' % runidx
2968 topic += ' (run #%d)' % runidx
2970 # Support both old and new progress API
2969 # Support both old and new progress API
2971 if util.safehasattr(ui, 'makeprogress'):
2970 if util.safehasattr(ui, 'makeprogress'):
2972 progress = ui.makeprogress(topic, unit='revs', total=total)
2971 progress = ui.makeprogress(topic, unit='revs', total=total)
2973
2972
2974 def updateprogress(pos):
2973 def updateprogress(pos):
2975 progress.update(pos)
2974 progress.update(pos)
2976
2975
2977 def completeprogress():
2976 def completeprogress():
2978 progress.complete()
2977 progress.complete()
2979
2978
2980 else:
2979 else:
2981
2980
2982 def updateprogress(pos):
2981 def updateprogress(pos):
2983 ui.progress(topic, pos, unit='revs', total=total)
2982 ui.progress(topic, pos, unit='revs', total=total)
2984
2983
2985 def completeprogress():
2984 def completeprogress():
2986 ui.progress(topic, None, unit='revs', total=total)
2985 ui.progress(topic, None, unit='revs', total=total)
2987
2986
2988 for idx, rev in enumerate(revs):
2987 for idx, rev in enumerate(revs):
2989 updateprogress(idx)
2988 updateprogress(idx)
2990 addargs, addkwargs = _getrevisionseed(orig, rev, tr, source)
2989 addargs, addkwargs = _getrevisionseed(orig, rev, tr, source)
2991 if clearcaches:
2990 if clearcaches:
2992 dest.index.clearcaches()
2991 dest.index.clearcaches()
2993 dest.clearcaches()
2992 dest.clearcaches()
2994 with timeone() as r:
2993 with timeone() as r:
2995 dest.addrawrevision(*addargs, **addkwargs)
2994 dest.addrawrevision(*addargs, **addkwargs)
2996 timings.append((rev, r[0]))
2995 timings.append((rev, r[0]))
2997 updateprogress(total)
2996 updateprogress(total)
2998 completeprogress()
2997 completeprogress()
2999 return timings
2998 return timings
3000
2999
3001
3000
3002 def _getrevisionseed(orig, rev, tr, source):
3001 def _getrevisionseed(orig, rev, tr, source):
3003 from mercurial.node import nullid
3002 from mercurial.node import nullid
3004
3003
3005 linkrev = orig.linkrev(rev)
3004 linkrev = orig.linkrev(rev)
3006 node = orig.node(rev)
3005 node = orig.node(rev)
3007 p1, p2 = orig.parents(node)
3006 p1, p2 = orig.parents(node)
3008 flags = orig.flags(rev)
3007 flags = orig.flags(rev)
3009 cachedelta = None
3008 cachedelta = None
3010 text = None
3009 text = None
3011
3010
3012 if source == b'full':
3011 if source == b'full':
3013 text = orig.revision(rev)
3012 text = orig.revision(rev)
3014 elif source == b'parent-1':
3013 elif source == b'parent-1':
3015 baserev = orig.rev(p1)
3014 baserev = orig.rev(p1)
3016 cachedelta = (baserev, orig.revdiff(p1, rev))
3015 cachedelta = (baserev, orig.revdiff(p1, rev))
3017 elif source == b'parent-2':
3016 elif source == b'parent-2':
3018 parent = p2
3017 parent = p2
3019 if p2 == nullid:
3018 if p2 == nullid:
3020 parent = p1
3019 parent = p1
3021 baserev = orig.rev(parent)
3020 baserev = orig.rev(parent)
3022 cachedelta = (baserev, orig.revdiff(parent, rev))
3021 cachedelta = (baserev, orig.revdiff(parent, rev))
3023 elif source == b'parent-smallest':
3022 elif source == b'parent-smallest':
3024 p1diff = orig.revdiff(p1, rev)
3023 p1diff = orig.revdiff(p1, rev)
3025 parent = p1
3024 parent = p1
3026 diff = p1diff
3025 diff = p1diff
3027 if p2 != nullid:
3026 if p2 != nullid:
3028 p2diff = orig.revdiff(p2, rev)
3027 p2diff = orig.revdiff(p2, rev)
3029 if len(p1diff) > len(p2diff):
3028 if len(p1diff) > len(p2diff):
3030 parent = p2
3029 parent = p2
3031 diff = p2diff
3030 diff = p2diff
3032 baserev = orig.rev(parent)
3031 baserev = orig.rev(parent)
3033 cachedelta = (baserev, diff)
3032 cachedelta = (baserev, diff)
3034 elif source == b'storage':
3033 elif source == b'storage':
3035 baserev = orig.deltaparent(rev)
3034 baserev = orig.deltaparent(rev)
3036 cachedelta = (baserev, orig.revdiff(orig.node(baserev), rev))
3035 cachedelta = (baserev, orig.revdiff(orig.node(baserev), rev))
3037
3036
3038 return (
3037 return (
3039 (text, tr, linkrev, p1, p2),
3038 (text, tr, linkrev, p1, p2),
3040 {'node': node, 'flags': flags, 'cachedelta': cachedelta},
3039 {'node': node, 'flags': flags, 'cachedelta': cachedelta},
3041 )
3040 )
3042
3041
3043
3042
3044 @contextlib.contextmanager
3043 @contextlib.contextmanager
3045 def _temprevlog(ui, orig, truncaterev):
3044 def _temprevlog(ui, orig, truncaterev):
3046 from mercurial import vfs as vfsmod
3045 from mercurial import vfs as vfsmod
3047
3046
3048 if orig._inline:
3047 if orig._inline:
3049 raise error.Abort('not supporting inline revlog (yet)')
3048 raise error.Abort('not supporting inline revlog (yet)')
3050 revlogkwargs = {}
3049 revlogkwargs = {}
3051 k = 'upperboundcomp'
3050 k = 'upperboundcomp'
3052 if util.safehasattr(orig, k):
3051 if util.safehasattr(orig, k):
3053 revlogkwargs[k] = getattr(orig, k)
3052 revlogkwargs[k] = getattr(orig, k)
3054
3053
3055 indexfile = getattr(orig, '_indexfile', None)
3054 indexfile = getattr(orig, '_indexfile', None)
3056 if indexfile is None:
3055 if indexfile is None:
3057 # compatibility with <= hg-5.8
3056 # compatibility with <= hg-5.8
3058 indexfile = getattr(orig, 'indexfile')
3057 indexfile = getattr(orig, 'indexfile')
3059 origindexpath = orig.opener.join(indexfile)
3058 origindexpath = orig.opener.join(indexfile)
3060
3059
3061 datafile = getattr(orig, '_datafile', getattr(orig, 'datafile'))
3060 datafile = getattr(orig, '_datafile', getattr(orig, 'datafile'))
3062 origdatapath = orig.opener.join(datafile)
3061 origdatapath = orig.opener.join(datafile)
3063 radix = b'revlog'
3062 radix = b'revlog'
3064 indexname = b'revlog.i'
3063 indexname = b'revlog.i'
3065 dataname = b'revlog.d'
3064 dataname = b'revlog.d'
3066
3065
3067 tmpdir = tempfile.mkdtemp(prefix='tmp-hgperf-')
3066 tmpdir = tempfile.mkdtemp(prefix='tmp-hgperf-')
3068 try:
3067 try:
3069 # copy the data file in a temporary directory
3068 # copy the data file in a temporary directory
3070 ui.debug('copying data in %s\n' % tmpdir)
3069 ui.debug('copying data in %s\n' % tmpdir)
3071 destindexpath = os.path.join(tmpdir, 'revlog.i')
3070 destindexpath = os.path.join(tmpdir, 'revlog.i')
3072 destdatapath = os.path.join(tmpdir, 'revlog.d')
3071 destdatapath = os.path.join(tmpdir, 'revlog.d')
3073 shutil.copyfile(origindexpath, destindexpath)
3072 shutil.copyfile(origindexpath, destindexpath)
3074 shutil.copyfile(origdatapath, destdatapath)
3073 shutil.copyfile(origdatapath, destdatapath)
3075
3074
3076 # remove the data we want to add again
3075 # remove the data we want to add again
3077 ui.debug('truncating data to be rewritten\n')
3076 ui.debug('truncating data to be rewritten\n')
3078 with open(destindexpath, 'ab') as index:
3077 with open(destindexpath, 'ab') as index:
3079 index.seek(0)
3078 index.seek(0)
3080 index.truncate(truncaterev * orig._io.size)
3079 index.truncate(truncaterev * orig._io.size)
3081 with open(destdatapath, 'ab') as data:
3080 with open(destdatapath, 'ab') as data:
3082 data.seek(0)
3081 data.seek(0)
3083 data.truncate(orig.start(truncaterev))
3082 data.truncate(orig.start(truncaterev))
3084
3083
3085 # instantiate a new revlog from the temporary copy
3084 # instantiate a new revlog from the temporary copy
3086 ui.debug('truncating adding to be rewritten\n')
3085 ui.debug('truncating adding to be rewritten\n')
3087 vfs = vfsmod.vfs(tmpdir)
3086 vfs = vfsmod.vfs(tmpdir)
3088 vfs.options = getattr(orig.opener, 'options', None)
3087 vfs.options = getattr(orig.opener, 'options', None)
3089
3088
3090 try:
3089 try:
3091 dest = revlog(vfs, radix=radix, **revlogkwargs)
3090 dest = revlog(vfs, radix=radix, **revlogkwargs)
3092 except TypeError:
3091 except TypeError:
3093 dest = revlog(
3092 dest = revlog(
3094 vfs, indexfile=indexname, datafile=dataname, **revlogkwargs
3093 vfs, indexfile=indexname, datafile=dataname, **revlogkwargs
3095 )
3094 )
3096 if dest._inline:
3095 if dest._inline:
3097 raise error.Abort('not supporting inline revlog (yet)')
3096 raise error.Abort('not supporting inline revlog (yet)')
3098 # make sure internals are initialized
3097 # make sure internals are initialized
3099 dest.revision(len(dest) - 1)
3098 dest.revision(len(dest) - 1)
3100 yield dest
3099 yield dest
3101 del dest, vfs
3100 del dest, vfs
3102 finally:
3101 finally:
3103 shutil.rmtree(tmpdir, True)
3102 shutil.rmtree(tmpdir, True)
3104
3103
3105
3104
3106 @command(
3105 @command(
3107 b'perf::revlogchunks|perfrevlogchunks',
3106 b'perf::revlogchunks|perfrevlogchunks',
3108 revlogopts
3107 revlogopts
3109 + formatteropts
3108 + formatteropts
3110 + [
3109 + [
3111 (b'e', b'engines', b'', b'compression engines to use'),
3110 (b'e', b'engines', b'', b'compression engines to use'),
3112 (b's', b'startrev', 0, b'revision to start at'),
3111 (b's', b'startrev', 0, b'revision to start at'),
3113 ],
3112 ],
3114 b'-c|-m|FILE',
3113 b'-c|-m|FILE',
3115 )
3114 )
3116 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
3115 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
3117 """Benchmark operations on revlog chunks.
3116 """Benchmark operations on revlog chunks.
3118
3117
3119 Logically, each revlog is a collection of fulltext revisions. However,
3118 Logically, each revlog is a collection of fulltext revisions. However,
3120 stored within each revlog are "chunks" of possibly compressed data. This
3119 stored within each revlog are "chunks" of possibly compressed data. This
3121 data needs to be read and decompressed or compressed and written.
3120 data needs to be read and decompressed or compressed and written.
3122
3121
3123 This command measures the time it takes to read+decompress and recompress
3122 This command measures the time it takes to read+decompress and recompress
3124 chunks in a revlog. It effectively isolates I/O and compression performance.
3123 chunks in a revlog. It effectively isolates I/O and compression performance.
3125 For measurements of higher-level operations like resolving revisions,
3124 For measurements of higher-level operations like resolving revisions,
3126 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
3125 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
3127 """
3126 """
3128 opts = _byteskwargs(opts)
3127 opts = _byteskwargs(opts)
3129
3128
3130 rl = cmdutil.openrevlog(repo, b'perfrevlogchunks', file_, opts)
3129 rl = cmdutil.openrevlog(repo, b'perfrevlogchunks', file_, opts)
3131
3130
3132 # _chunkraw was renamed to _getsegmentforrevs.
3131 # _chunkraw was renamed to _getsegmentforrevs.
3133 try:
3132 try:
3134 segmentforrevs = rl._getsegmentforrevs
3133 segmentforrevs = rl._getsegmentforrevs
3135 except AttributeError:
3134 except AttributeError:
3136 segmentforrevs = rl._chunkraw
3135 segmentforrevs = rl._chunkraw
3137
3136
3138 # Verify engines argument.
3137 # Verify engines argument.
3139 if engines:
3138 if engines:
3140 engines = {e.strip() for e in engines.split(b',')}
3139 engines = {e.strip() for e in engines.split(b',')}
3141 for engine in engines:
3140 for engine in engines:
3142 try:
3141 try:
3143 util.compressionengines[engine]
3142 util.compressionengines[engine]
3144 except KeyError:
3143 except KeyError:
3145 raise error.Abort(b'unknown compression engine: %s' % engine)
3144 raise error.Abort(b'unknown compression engine: %s' % engine)
3146 else:
3145 else:
3147 engines = []
3146 engines = []
3148 for e in util.compengines:
3147 for e in util.compengines:
3149 engine = util.compengines[e]
3148 engine = util.compengines[e]
3150 try:
3149 try:
3151 if engine.available():
3150 if engine.available():
3152 engine.revlogcompressor().compress(b'dummy')
3151 engine.revlogcompressor().compress(b'dummy')
3153 engines.append(e)
3152 engines.append(e)
3154 except NotImplementedError:
3153 except NotImplementedError:
3155 pass
3154 pass
3156
3155
3157 revs = list(rl.revs(startrev, len(rl) - 1))
3156 revs = list(rl.revs(startrev, len(rl) - 1))
3158
3157
3159 def rlfh(rl):
3158 def rlfh(rl):
3160 if rl._inline:
3159 if rl._inline:
3161 indexfile = getattr(rl, '_indexfile', None)
3160 indexfile = getattr(rl, '_indexfile', None)
3162 if indexfile is None:
3161 if indexfile is None:
3163 # compatibility with <= hg-5.8
3162 # compatibility with <= hg-5.8
3164 indexfile = getattr(rl, 'indexfile')
3163 indexfile = getattr(rl, 'indexfile')
3165 return getsvfs(repo)(indexfile)
3164 return getsvfs(repo)(indexfile)
3166 else:
3165 else:
3167 datafile = getattr(rl, 'datafile', getattr(rl, 'datafile'))
3166 datafile = getattr(rl, 'datafile', getattr(rl, 'datafile'))
3168 return getsvfs(repo)(datafile)
3167 return getsvfs(repo)(datafile)
3169
3168
3170 def doread():
3169 def doread():
3171 rl.clearcaches()
3170 rl.clearcaches()
3172 for rev in revs:
3171 for rev in revs:
3173 segmentforrevs(rev, rev)
3172 segmentforrevs(rev, rev)
3174
3173
3175 def doreadcachedfh():
3174 def doreadcachedfh():
3176 rl.clearcaches()
3175 rl.clearcaches()
3177 fh = rlfh(rl)
3176 fh = rlfh(rl)
3178 for rev in revs:
3177 for rev in revs:
3179 segmentforrevs(rev, rev, df=fh)
3178 segmentforrevs(rev, rev, df=fh)
3180
3179
3181 def doreadbatch():
3180 def doreadbatch():
3182 rl.clearcaches()
3181 rl.clearcaches()
3183 segmentforrevs(revs[0], revs[-1])
3182 segmentforrevs(revs[0], revs[-1])
3184
3183
3185 def doreadbatchcachedfh():
3184 def doreadbatchcachedfh():
3186 rl.clearcaches()
3185 rl.clearcaches()
3187 fh = rlfh(rl)
3186 fh = rlfh(rl)
3188 segmentforrevs(revs[0], revs[-1], df=fh)
3187 segmentforrevs(revs[0], revs[-1], df=fh)
3189
3188
3190 def dochunk():
3189 def dochunk():
3191 rl.clearcaches()
3190 rl.clearcaches()
3192 fh = rlfh(rl)
3191 fh = rlfh(rl)
3193 for rev in revs:
3192 for rev in revs:
3194 rl._chunk(rev, df=fh)
3193 rl._chunk(rev, df=fh)
3195
3194
3196 chunks = [None]
3195 chunks = [None]
3197
3196
3198 def dochunkbatch():
3197 def dochunkbatch():
3199 rl.clearcaches()
3198 rl.clearcaches()
3200 fh = rlfh(rl)
3199 fh = rlfh(rl)
3201 # Save chunks as a side-effect.
3200 # Save chunks as a side-effect.
3202 chunks[0] = rl._chunks(revs, df=fh)
3201 chunks[0] = rl._chunks(revs, df=fh)
3203
3202
3204 def docompress(compressor):
3203 def docompress(compressor):
3205 rl.clearcaches()
3204 rl.clearcaches()
3206
3205
3207 try:
3206 try:
3208 # Swap in the requested compression engine.
3207 # Swap in the requested compression engine.
3209 oldcompressor = rl._compressor
3208 oldcompressor = rl._compressor
3210 rl._compressor = compressor
3209 rl._compressor = compressor
3211 for chunk in chunks[0]:
3210 for chunk in chunks[0]:
3212 rl.compress(chunk)
3211 rl.compress(chunk)
3213 finally:
3212 finally:
3214 rl._compressor = oldcompressor
3213 rl._compressor = oldcompressor
3215
3214
3216 benches = [
3215 benches = [
3217 (lambda: doread(), b'read'),
3216 (lambda: doread(), b'read'),
3218 (lambda: doreadcachedfh(), b'read w/ reused fd'),
3217 (lambda: doreadcachedfh(), b'read w/ reused fd'),
3219 (lambda: doreadbatch(), b'read batch'),
3218 (lambda: doreadbatch(), b'read batch'),
3220 (lambda: doreadbatchcachedfh(), b'read batch w/ reused fd'),
3219 (lambda: doreadbatchcachedfh(), b'read batch w/ reused fd'),
3221 (lambda: dochunk(), b'chunk'),
3220 (lambda: dochunk(), b'chunk'),
3222 (lambda: dochunkbatch(), b'chunk batch'),
3221 (lambda: dochunkbatch(), b'chunk batch'),
3223 ]
3222 ]
3224
3223
3225 for engine in sorted(engines):
3224 for engine in sorted(engines):
3226 compressor = util.compengines[engine].revlogcompressor()
3225 compressor = util.compengines[engine].revlogcompressor()
3227 benches.append(
3226 benches.append(
3228 (
3227 (
3229 functools.partial(docompress, compressor),
3228 functools.partial(docompress, compressor),
3230 b'compress w/ %s' % engine,
3229 b'compress w/ %s' % engine,
3231 )
3230 )
3232 )
3231 )
3233
3232
3234 for fn, title in benches:
3233 for fn, title in benches:
3235 timer, fm = gettimer(ui, opts)
3234 timer, fm = gettimer(ui, opts)
3236 timer(fn, title=title)
3235 timer(fn, title=title)
3237 fm.end()
3236 fm.end()
3238
3237
3239
3238
3240 @command(
3239 @command(
3241 b'perf::revlogrevision|perfrevlogrevision',
3240 b'perf::revlogrevision|perfrevlogrevision',
3242 revlogopts
3241 revlogopts
3243 + formatteropts
3242 + formatteropts
3244 + [(b'', b'cache', False, b'use caches instead of clearing')],
3243 + [(b'', b'cache', False, b'use caches instead of clearing')],
3245 b'-c|-m|FILE REV',
3244 b'-c|-m|FILE REV',
3246 )
3245 )
3247 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
3246 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
3248 """Benchmark obtaining a revlog revision.
3247 """Benchmark obtaining a revlog revision.
3249
3248
3250 Obtaining a revlog revision consists of roughly the following steps:
3249 Obtaining a revlog revision consists of roughly the following steps:
3251
3250
3252 1. Compute the delta chain
3251 1. Compute the delta chain
3253 2. Slice the delta chain if applicable
3252 2. Slice the delta chain if applicable
3254 3. Obtain the raw chunks for that delta chain
3253 3. Obtain the raw chunks for that delta chain
3255 4. Decompress each raw chunk
3254 4. Decompress each raw chunk
3256 5. Apply binary patches to obtain fulltext
3255 5. Apply binary patches to obtain fulltext
3257 6. Verify hash of fulltext
3256 6. Verify hash of fulltext
3258
3257
3259 This command measures the time spent in each of these phases.
3258 This command measures the time spent in each of these phases.
3260 """
3259 """
3261 opts = _byteskwargs(opts)
3260 opts = _byteskwargs(opts)
3262
3261
3263 if opts.get(b'changelog') or opts.get(b'manifest'):
3262 if opts.get(b'changelog') or opts.get(b'manifest'):
3264 file_, rev = None, file_
3263 file_, rev = None, file_
3265 elif rev is None:
3264 elif rev is None:
3266 raise error.CommandError(b'perfrevlogrevision', b'invalid arguments')
3265 raise error.CommandError(b'perfrevlogrevision', b'invalid arguments')
3267
3266
3268 r = cmdutil.openrevlog(repo, b'perfrevlogrevision', file_, opts)
3267 r = cmdutil.openrevlog(repo, b'perfrevlogrevision', file_, opts)
3269
3268
3270 # _chunkraw was renamed to _getsegmentforrevs.
3269 # _chunkraw was renamed to _getsegmentforrevs.
3271 try:
3270 try:
3272 segmentforrevs = r._getsegmentforrevs
3271 segmentforrevs = r._getsegmentforrevs
3273 except AttributeError:
3272 except AttributeError:
3274 segmentforrevs = r._chunkraw
3273 segmentforrevs = r._chunkraw
3275
3274
3276 node = r.lookup(rev)
3275 node = r.lookup(rev)
3277 rev = r.rev(node)
3276 rev = r.rev(node)
3278
3277
3279 def getrawchunks(data, chain):
3278 def getrawchunks(data, chain):
3280 start = r.start
3279 start = r.start
3281 length = r.length
3280 length = r.length
3282 inline = r._inline
3281 inline = r._inline
3283 try:
3282 try:
3284 iosize = r.index.entry_size
3283 iosize = r.index.entry_size
3285 except AttributeError:
3284 except AttributeError:
3286 iosize = r._io.size
3285 iosize = r._io.size
3287 buffer = util.buffer
3286 buffer = util.buffer
3288
3287
3289 chunks = []
3288 chunks = []
3290 ladd = chunks.append
3289 ladd = chunks.append
3291 for idx, item in enumerate(chain):
3290 for idx, item in enumerate(chain):
3292 offset = start(item[0])
3291 offset = start(item[0])
3293 bits = data[idx]
3292 bits = data[idx]
3294 for rev in item:
3293 for rev in item:
3295 chunkstart = start(rev)
3294 chunkstart = start(rev)
3296 if inline:
3295 if inline:
3297 chunkstart += (rev + 1) * iosize
3296 chunkstart += (rev + 1) * iosize
3298 chunklength = length(rev)
3297 chunklength = length(rev)
3299 ladd(buffer(bits, chunkstart - offset, chunklength))
3298 ladd(buffer(bits, chunkstart - offset, chunklength))
3300
3299
3301 return chunks
3300 return chunks
3302
3301
3303 def dodeltachain(rev):
3302 def dodeltachain(rev):
3304 if not cache:
3303 if not cache:
3305 r.clearcaches()
3304 r.clearcaches()
3306 r._deltachain(rev)
3305 r._deltachain(rev)
3307
3306
3308 def doread(chain):
3307 def doread(chain):
3309 if not cache:
3308 if not cache:
3310 r.clearcaches()
3309 r.clearcaches()
3311 for item in slicedchain:
3310 for item in slicedchain:
3312 segmentforrevs(item[0], item[-1])
3311 segmentforrevs(item[0], item[-1])
3313
3312
3314 def doslice(r, chain, size):
3313 def doslice(r, chain, size):
3315 for s in slicechunk(r, chain, targetsize=size):
3314 for s in slicechunk(r, chain, targetsize=size):
3316 pass
3315 pass
3317
3316
3318 def dorawchunks(data, chain):
3317 def dorawchunks(data, chain):
3319 if not cache:
3318 if not cache:
3320 r.clearcaches()
3319 r.clearcaches()
3321 getrawchunks(data, chain)
3320 getrawchunks(data, chain)
3322
3321
3323 def dodecompress(chunks):
3322 def dodecompress(chunks):
3324 decomp = r.decompress
3323 decomp = r.decompress
3325 for chunk in chunks:
3324 for chunk in chunks:
3326 decomp(chunk)
3325 decomp(chunk)
3327
3326
3328 def dopatch(text, bins):
3327 def dopatch(text, bins):
3329 if not cache:
3328 if not cache:
3330 r.clearcaches()
3329 r.clearcaches()
3331 mdiff.patches(text, bins)
3330 mdiff.patches(text, bins)
3332
3331
3333 def dohash(text):
3332 def dohash(text):
3334 if not cache:
3333 if not cache:
3335 r.clearcaches()
3334 r.clearcaches()
3336 r.checkhash(text, node, rev=rev)
3335 r.checkhash(text, node, rev=rev)
3337
3336
3338 def dorevision():
3337 def dorevision():
3339 if not cache:
3338 if not cache:
3340 r.clearcaches()
3339 r.clearcaches()
3341 r.revision(node)
3340 r.revision(node)
3342
3341
3343 try:
3342 try:
3344 from mercurial.revlogutils.deltas import slicechunk
3343 from mercurial.revlogutils.deltas import slicechunk
3345 except ImportError:
3344 except ImportError:
3346 slicechunk = getattr(revlog, '_slicechunk', None)
3345 slicechunk = getattr(revlog, '_slicechunk', None)
3347
3346
3348 size = r.length(rev)
3347 size = r.length(rev)
3349 chain = r._deltachain(rev)[0]
3348 chain = r._deltachain(rev)[0]
3350 if not getattr(r, '_withsparseread', False):
3349 if not getattr(r, '_withsparseread', False):
3351 slicedchain = (chain,)
3350 slicedchain = (chain,)
3352 else:
3351 else:
3353 slicedchain = tuple(slicechunk(r, chain, targetsize=size))
3352 slicedchain = tuple(slicechunk(r, chain, targetsize=size))
3354 data = [segmentforrevs(seg[0], seg[-1])[1] for seg in slicedchain]
3353 data = [segmentforrevs(seg[0], seg[-1])[1] for seg in slicedchain]
3355 rawchunks = getrawchunks(data, slicedchain)
3354 rawchunks = getrawchunks(data, slicedchain)
3356 bins = r._chunks(chain)
3355 bins = r._chunks(chain)
3357 text = bytes(bins[0])
3356 text = bytes(bins[0])
3358 bins = bins[1:]
3357 bins = bins[1:]
3359 text = mdiff.patches(text, bins)
3358 text = mdiff.patches(text, bins)
3360
3359
3361 benches = [
3360 benches = [
3362 (lambda: dorevision(), b'full'),
3361 (lambda: dorevision(), b'full'),
3363 (lambda: dodeltachain(rev), b'deltachain'),
3362 (lambda: dodeltachain(rev), b'deltachain'),
3364 (lambda: doread(chain), b'read'),
3363 (lambda: doread(chain), b'read'),
3365 ]
3364 ]
3366
3365
3367 if getattr(r, '_withsparseread', False):
3366 if getattr(r, '_withsparseread', False):
3368 slicing = (lambda: doslice(r, chain, size), b'slice-sparse-chain')
3367 slicing = (lambda: doslice(r, chain, size), b'slice-sparse-chain')
3369 benches.append(slicing)
3368 benches.append(slicing)
3370
3369
3371 benches.extend(
3370 benches.extend(
3372 [
3371 [
3373 (lambda: dorawchunks(data, slicedchain), b'rawchunks'),
3372 (lambda: dorawchunks(data, slicedchain), b'rawchunks'),
3374 (lambda: dodecompress(rawchunks), b'decompress'),
3373 (lambda: dodecompress(rawchunks), b'decompress'),
3375 (lambda: dopatch(text, bins), b'patch'),
3374 (lambda: dopatch(text, bins), b'patch'),
3376 (lambda: dohash(text), b'hash'),
3375 (lambda: dohash(text), b'hash'),
3377 ]
3376 ]
3378 )
3377 )
3379
3378
3380 timer, fm = gettimer(ui, opts)
3379 timer, fm = gettimer(ui, opts)
3381 for fn, title in benches:
3380 for fn, title in benches:
3382 timer(fn, title=title)
3381 timer(fn, title=title)
3383 fm.end()
3382 fm.end()
3384
3383
3385
3384
3386 @command(
3385 @command(
3387 b'perf::revset|perfrevset',
3386 b'perf::revset|perfrevset',
3388 [
3387 [
3389 (b'C', b'clear', False, b'clear volatile cache between each call.'),
3388 (b'C', b'clear', False, b'clear volatile cache between each call.'),
3390 (b'', b'contexts', False, b'obtain changectx for each revision'),
3389 (b'', b'contexts', False, b'obtain changectx for each revision'),
3391 ]
3390 ]
3392 + formatteropts,
3391 + formatteropts,
3393 b"REVSET",
3392 b"REVSET",
3394 )
3393 )
3395 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
3394 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
3396 """benchmark the execution time of a revset
3395 """benchmark the execution time of a revset
3397
3396
3398 Use the --clean option if need to evaluate the impact of build volatile
3397 Use the --clean option if need to evaluate the impact of build volatile
3399 revisions set cache on the revset execution. Volatile cache hold filtered
3398 revisions set cache on the revset execution. Volatile cache hold filtered
3400 and obsolete related cache."""
3399 and obsolete related cache."""
3401 opts = _byteskwargs(opts)
3400 opts = _byteskwargs(opts)
3402
3401
3403 timer, fm = gettimer(ui, opts)
3402 timer, fm = gettimer(ui, opts)
3404
3403
3405 def d():
3404 def d():
3406 if clear:
3405 if clear:
3407 repo.invalidatevolatilesets()
3406 repo.invalidatevolatilesets()
3408 if contexts:
3407 if contexts:
3409 for ctx in repo.set(expr):
3408 for ctx in repo.set(expr):
3410 pass
3409 pass
3411 else:
3410 else:
3412 for r in repo.revs(expr):
3411 for r in repo.revs(expr):
3413 pass
3412 pass
3414
3413
3415 timer(d)
3414 timer(d)
3416 fm.end()
3415 fm.end()
3417
3416
3418
3417
3419 @command(
3418 @command(
3420 b'perf::volatilesets|perfvolatilesets',
3419 b'perf::volatilesets|perfvolatilesets',
3421 [
3420 [
3422 (b'', b'clear-obsstore', False, b'drop obsstore between each call.'),
3421 (b'', b'clear-obsstore', False, b'drop obsstore between each call.'),
3423 ]
3422 ]
3424 + formatteropts,
3423 + formatteropts,
3425 )
3424 )
3426 def perfvolatilesets(ui, repo, *names, **opts):
3425 def perfvolatilesets(ui, repo, *names, **opts):
3427 """benchmark the computation of various volatile set
3426 """benchmark the computation of various volatile set
3428
3427
3429 Volatile set computes element related to filtering and obsolescence."""
3428 Volatile set computes element related to filtering and obsolescence."""
3430 opts = _byteskwargs(opts)
3429 opts = _byteskwargs(opts)
3431 timer, fm = gettimer(ui, opts)
3430 timer, fm = gettimer(ui, opts)
3432 repo = repo.unfiltered()
3431 repo = repo.unfiltered()
3433
3432
3434 def getobs(name):
3433 def getobs(name):
3435 def d():
3434 def d():
3436 repo.invalidatevolatilesets()
3435 repo.invalidatevolatilesets()
3437 if opts[b'clear_obsstore']:
3436 if opts[b'clear_obsstore']:
3438 clearfilecache(repo, b'obsstore')
3437 clearfilecache(repo, b'obsstore')
3439 obsolete.getrevs(repo, name)
3438 obsolete.getrevs(repo, name)
3440
3439
3441 return d
3440 return d
3442
3441
3443 allobs = sorted(obsolete.cachefuncs)
3442 allobs = sorted(obsolete.cachefuncs)
3444 if names:
3443 if names:
3445 allobs = [n for n in allobs if n in names]
3444 allobs = [n for n in allobs if n in names]
3446
3445
3447 for name in allobs:
3446 for name in allobs:
3448 timer(getobs(name), title=name)
3447 timer(getobs(name), title=name)
3449
3448
3450 def getfiltered(name):
3449 def getfiltered(name):
3451 def d():
3450 def d():
3452 repo.invalidatevolatilesets()
3451 repo.invalidatevolatilesets()
3453 if opts[b'clear_obsstore']:
3452 if opts[b'clear_obsstore']:
3454 clearfilecache(repo, b'obsstore')
3453 clearfilecache(repo, b'obsstore')
3455 repoview.filterrevs(repo, name)
3454 repoview.filterrevs(repo, name)
3456
3455
3457 return d
3456 return d
3458
3457
3459 allfilter = sorted(repoview.filtertable)
3458 allfilter = sorted(repoview.filtertable)
3460 if names:
3459 if names:
3461 allfilter = [n for n in allfilter if n in names]
3460 allfilter = [n for n in allfilter if n in names]
3462
3461
3463 for name in allfilter:
3462 for name in allfilter:
3464 timer(getfiltered(name), title=name)
3463 timer(getfiltered(name), title=name)
3465 fm.end()
3464 fm.end()
3466
3465
3467
3466
3468 @command(
3467 @command(
3469 b'perf::branchmap|perfbranchmap',
3468 b'perf::branchmap|perfbranchmap',
3470 [
3469 [
3471 (b'f', b'full', False, b'Includes build time of subset'),
3470 (b'f', b'full', False, b'Includes build time of subset'),
3472 (
3471 (
3473 b'',
3472 b'',
3474 b'clear-revbranch',
3473 b'clear-revbranch',
3475 False,
3474 False,
3476 b'purge the revbranch cache between computation',
3475 b'purge the revbranch cache between computation',
3477 ),
3476 ),
3478 ]
3477 ]
3479 + formatteropts,
3478 + formatteropts,
3480 )
3479 )
3481 def perfbranchmap(ui, repo, *filternames, **opts):
3480 def perfbranchmap(ui, repo, *filternames, **opts):
3482 """benchmark the update of a branchmap
3481 """benchmark the update of a branchmap
3483
3482
3484 This benchmarks the full repo.branchmap() call with read and write disabled
3483 This benchmarks the full repo.branchmap() call with read and write disabled
3485 """
3484 """
3486 opts = _byteskwargs(opts)
3485 opts = _byteskwargs(opts)
3487 full = opts.get(b"full", False)
3486 full = opts.get(b"full", False)
3488 clear_revbranch = opts.get(b"clear_revbranch", False)
3487 clear_revbranch = opts.get(b"clear_revbranch", False)
3489 timer, fm = gettimer(ui, opts)
3488 timer, fm = gettimer(ui, opts)
3490
3489
3491 def getbranchmap(filtername):
3490 def getbranchmap(filtername):
3492 """generate a benchmark function for the filtername"""
3491 """generate a benchmark function for the filtername"""
3493 if filtername is None:
3492 if filtername is None:
3494 view = repo
3493 view = repo
3495 else:
3494 else:
3496 view = repo.filtered(filtername)
3495 view = repo.filtered(filtername)
3497 if util.safehasattr(view._branchcaches, '_per_filter'):
3496 if util.safehasattr(view._branchcaches, '_per_filter'):
3498 filtered = view._branchcaches._per_filter
3497 filtered = view._branchcaches._per_filter
3499 else:
3498 else:
3500 # older versions
3499 # older versions
3501 filtered = view._branchcaches
3500 filtered = view._branchcaches
3502
3501
3503 def d():
3502 def d():
3504 if clear_revbranch:
3503 if clear_revbranch:
3505 repo.revbranchcache()._clear()
3504 repo.revbranchcache()._clear()
3506 if full:
3505 if full:
3507 view._branchcaches.clear()
3506 view._branchcaches.clear()
3508 else:
3507 else:
3509 filtered.pop(filtername, None)
3508 filtered.pop(filtername, None)
3510 view.branchmap()
3509 view.branchmap()
3511
3510
3512 return d
3511 return d
3513
3512
3514 # add filter in smaller subset to bigger subset
3513 # add filter in smaller subset to bigger subset
3515 possiblefilters = set(repoview.filtertable)
3514 possiblefilters = set(repoview.filtertable)
3516 if filternames:
3515 if filternames:
3517 possiblefilters &= set(filternames)
3516 possiblefilters &= set(filternames)
3518 subsettable = getbranchmapsubsettable()
3517 subsettable = getbranchmapsubsettable()
3519 allfilters = []
3518 allfilters = []
3520 while possiblefilters:
3519 while possiblefilters:
3521 for name in possiblefilters:
3520 for name in possiblefilters:
3522 subset = subsettable.get(name)
3521 subset = subsettable.get(name)
3523 if subset not in possiblefilters:
3522 if subset not in possiblefilters:
3524 break
3523 break
3525 else:
3524 else:
3526 assert False, b'subset cycle %s!' % possiblefilters
3525 assert False, b'subset cycle %s!' % possiblefilters
3527 allfilters.append(name)
3526 allfilters.append(name)
3528 possiblefilters.remove(name)
3527 possiblefilters.remove(name)
3529
3528
3530 # warm the cache
3529 # warm the cache
3531 if not full:
3530 if not full:
3532 for name in allfilters:
3531 for name in allfilters:
3533 repo.filtered(name).branchmap()
3532 repo.filtered(name).branchmap()
3534 if not filternames or b'unfiltered' in filternames:
3533 if not filternames or b'unfiltered' in filternames:
3535 # add unfiltered
3534 # add unfiltered
3536 allfilters.append(None)
3535 allfilters.append(None)
3537
3536
3538 if util.safehasattr(branchmap.branchcache, 'fromfile'):
3537 if util.safehasattr(branchmap.branchcache, 'fromfile'):
3539 branchcacheread = safeattrsetter(branchmap.branchcache, b'fromfile')
3538 branchcacheread = safeattrsetter(branchmap.branchcache, b'fromfile')
3540 branchcacheread.set(classmethod(lambda *args: None))
3539 branchcacheread.set(classmethod(lambda *args: None))
3541 else:
3540 else:
3542 # older versions
3541 # older versions
3543 branchcacheread = safeattrsetter(branchmap, b'read')
3542 branchcacheread = safeattrsetter(branchmap, b'read')
3544 branchcacheread.set(lambda *args: None)
3543 branchcacheread.set(lambda *args: None)
3545 branchcachewrite = safeattrsetter(branchmap.branchcache, b'write')
3544 branchcachewrite = safeattrsetter(branchmap.branchcache, b'write')
3546 branchcachewrite.set(lambda *args: None)
3545 branchcachewrite.set(lambda *args: None)
3547 try:
3546 try:
3548 for name in allfilters:
3547 for name in allfilters:
3549 printname = name
3548 printname = name
3550 if name is None:
3549 if name is None:
3551 printname = b'unfiltered'
3550 printname = b'unfiltered'
3552 timer(getbranchmap(name), title=printname)
3551 timer(getbranchmap(name), title=printname)
3553 finally:
3552 finally:
3554 branchcacheread.restore()
3553 branchcacheread.restore()
3555 branchcachewrite.restore()
3554 branchcachewrite.restore()
3556 fm.end()
3555 fm.end()
3557
3556
3558
3557
3559 @command(
3558 @command(
3560 b'perf::branchmapupdate|perfbranchmapupdate',
3559 b'perf::branchmapupdate|perfbranchmapupdate',
3561 [
3560 [
3562 (b'', b'base', [], b'subset of revision to start from'),
3561 (b'', b'base', [], b'subset of revision to start from'),
3563 (b'', b'target', [], b'subset of revision to end with'),
3562 (b'', b'target', [], b'subset of revision to end with'),
3564 (b'', b'clear-caches', False, b'clear cache between each runs'),
3563 (b'', b'clear-caches', False, b'clear cache between each runs'),
3565 ]
3564 ]
3566 + formatteropts,
3565 + formatteropts,
3567 )
3566 )
3568 def perfbranchmapupdate(ui, repo, base=(), target=(), **opts):
3567 def perfbranchmapupdate(ui, repo, base=(), target=(), **opts):
3569 """benchmark branchmap update from for <base> revs to <target> revs
3568 """benchmark branchmap update from for <base> revs to <target> revs
3570
3569
3571 If `--clear-caches` is passed, the following items will be reset before
3570 If `--clear-caches` is passed, the following items will be reset before
3572 each update:
3571 each update:
3573 * the changelog instance and associated indexes
3572 * the changelog instance and associated indexes
3574 * the rev-branch-cache instance
3573 * the rev-branch-cache instance
3575
3574
3576 Examples:
3575 Examples:
3577
3576
3578 # update for the one last revision
3577 # update for the one last revision
3579 $ hg perfbranchmapupdate --base 'not tip' --target 'tip'
3578 $ hg perfbranchmapupdate --base 'not tip' --target 'tip'
3580
3579
3581 $ update for change coming with a new branch
3580 $ update for change coming with a new branch
3582 $ hg perfbranchmapupdate --base 'stable' --target 'default'
3581 $ hg perfbranchmapupdate --base 'stable' --target 'default'
3583 """
3582 """
3584 from mercurial import branchmap
3583 from mercurial import branchmap
3585 from mercurial import repoview
3584 from mercurial import repoview
3586
3585
3587 opts = _byteskwargs(opts)
3586 opts = _byteskwargs(opts)
3588 timer, fm = gettimer(ui, opts)
3587 timer, fm = gettimer(ui, opts)
3589 clearcaches = opts[b'clear_caches']
3588 clearcaches = opts[b'clear_caches']
3590 unfi = repo.unfiltered()
3589 unfi = repo.unfiltered()
3591 x = [None] # used to pass data between closure
3590 x = [None] # used to pass data between closure
3592
3591
3593 # we use a `list` here to avoid possible side effect from smartset
3592 # we use a `list` here to avoid possible side effect from smartset
3594 baserevs = list(scmutil.revrange(repo, base))
3593 baserevs = list(scmutil.revrange(repo, base))
3595 targetrevs = list(scmutil.revrange(repo, target))
3594 targetrevs = list(scmutil.revrange(repo, target))
3596 if not baserevs:
3595 if not baserevs:
3597 raise error.Abort(b'no revisions selected for --base')
3596 raise error.Abort(b'no revisions selected for --base')
3598 if not targetrevs:
3597 if not targetrevs:
3599 raise error.Abort(b'no revisions selected for --target')
3598 raise error.Abort(b'no revisions selected for --target')
3600
3599
3601 # make sure the target branchmap also contains the one in the base
3600 # make sure the target branchmap also contains the one in the base
3602 targetrevs = list(set(baserevs) | set(targetrevs))
3601 targetrevs = list(set(baserevs) | set(targetrevs))
3603 targetrevs.sort()
3602 targetrevs.sort()
3604
3603
3605 cl = repo.changelog
3604 cl = repo.changelog
3606 allbaserevs = list(cl.ancestors(baserevs, inclusive=True))
3605 allbaserevs = list(cl.ancestors(baserevs, inclusive=True))
3607 allbaserevs.sort()
3606 allbaserevs.sort()
3608 alltargetrevs = frozenset(cl.ancestors(targetrevs, inclusive=True))
3607 alltargetrevs = frozenset(cl.ancestors(targetrevs, inclusive=True))
3609
3608
3610 newrevs = list(alltargetrevs.difference(allbaserevs))
3609 newrevs = list(alltargetrevs.difference(allbaserevs))
3611 newrevs.sort()
3610 newrevs.sort()
3612
3611
3613 allrevs = frozenset(unfi.changelog.revs())
3612 allrevs = frozenset(unfi.changelog.revs())
3614 basefilterrevs = frozenset(allrevs.difference(allbaserevs))
3613 basefilterrevs = frozenset(allrevs.difference(allbaserevs))
3615 targetfilterrevs = frozenset(allrevs.difference(alltargetrevs))
3614 targetfilterrevs = frozenset(allrevs.difference(alltargetrevs))
3616
3615
3617 def basefilter(repo, visibilityexceptions=None):
3616 def basefilter(repo, visibilityexceptions=None):
3618 return basefilterrevs
3617 return basefilterrevs
3619
3618
3620 def targetfilter(repo, visibilityexceptions=None):
3619 def targetfilter(repo, visibilityexceptions=None):
3621 return targetfilterrevs
3620 return targetfilterrevs
3622
3621
3623 msg = b'benchmark of branchmap with %d revisions with %d new ones\n'
3622 msg = b'benchmark of branchmap with %d revisions with %d new ones\n'
3624 ui.status(msg % (len(allbaserevs), len(newrevs)))
3623 ui.status(msg % (len(allbaserevs), len(newrevs)))
3625 if targetfilterrevs:
3624 if targetfilterrevs:
3626 msg = b'(%d revisions still filtered)\n'
3625 msg = b'(%d revisions still filtered)\n'
3627 ui.status(msg % len(targetfilterrevs))
3626 ui.status(msg % len(targetfilterrevs))
3628
3627
3629 try:
3628 try:
3630 repoview.filtertable[b'__perf_branchmap_update_base'] = basefilter
3629 repoview.filtertable[b'__perf_branchmap_update_base'] = basefilter
3631 repoview.filtertable[b'__perf_branchmap_update_target'] = targetfilter
3630 repoview.filtertable[b'__perf_branchmap_update_target'] = targetfilter
3632
3631
3633 baserepo = repo.filtered(b'__perf_branchmap_update_base')
3632 baserepo = repo.filtered(b'__perf_branchmap_update_base')
3634 targetrepo = repo.filtered(b'__perf_branchmap_update_target')
3633 targetrepo = repo.filtered(b'__perf_branchmap_update_target')
3635
3634
3636 # try to find an existing branchmap to reuse
3635 # try to find an existing branchmap to reuse
3637 subsettable = getbranchmapsubsettable()
3636 subsettable = getbranchmapsubsettable()
3638 candidatefilter = subsettable.get(None)
3637 candidatefilter = subsettable.get(None)
3639 while candidatefilter is not None:
3638 while candidatefilter is not None:
3640 candidatebm = repo.filtered(candidatefilter).branchmap()
3639 candidatebm = repo.filtered(candidatefilter).branchmap()
3641 if candidatebm.validfor(baserepo):
3640 if candidatebm.validfor(baserepo):
3642 filtered = repoview.filterrevs(repo, candidatefilter)
3641 filtered = repoview.filterrevs(repo, candidatefilter)
3643 missing = [r for r in allbaserevs if r in filtered]
3642 missing = [r for r in allbaserevs if r in filtered]
3644 base = candidatebm.copy()
3643 base = candidatebm.copy()
3645 base.update(baserepo, missing)
3644 base.update(baserepo, missing)
3646 break
3645 break
3647 candidatefilter = subsettable.get(candidatefilter)
3646 candidatefilter = subsettable.get(candidatefilter)
3648 else:
3647 else:
3649 # no suitable subset where found
3648 # no suitable subset where found
3650 base = branchmap.branchcache()
3649 base = branchmap.branchcache()
3651 base.update(baserepo, allbaserevs)
3650 base.update(baserepo, allbaserevs)
3652
3651
3653 def setup():
3652 def setup():
3654 x[0] = base.copy()
3653 x[0] = base.copy()
3655 if clearcaches:
3654 if clearcaches:
3656 unfi._revbranchcache = None
3655 unfi._revbranchcache = None
3657 clearchangelog(repo)
3656 clearchangelog(repo)
3658
3657
3659 def bench():
3658 def bench():
3660 x[0].update(targetrepo, newrevs)
3659 x[0].update(targetrepo, newrevs)
3661
3660
3662 timer(bench, setup=setup)
3661 timer(bench, setup=setup)
3663 fm.end()
3662 fm.end()
3664 finally:
3663 finally:
3665 repoview.filtertable.pop(b'__perf_branchmap_update_base', None)
3664 repoview.filtertable.pop(b'__perf_branchmap_update_base', None)
3666 repoview.filtertable.pop(b'__perf_branchmap_update_target', None)
3665 repoview.filtertable.pop(b'__perf_branchmap_update_target', None)
3667
3666
3668
3667
3669 @command(
3668 @command(
3670 b'perf::branchmapload|perfbranchmapload',
3669 b'perf::branchmapload|perfbranchmapload',
3671 [
3670 [
3672 (b'f', b'filter', b'', b'Specify repoview filter'),
3671 (b'f', b'filter', b'', b'Specify repoview filter'),
3673 (b'', b'list', False, b'List brachmap filter caches'),
3672 (b'', b'list', False, b'List brachmap filter caches'),
3674 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
3673 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
3675 ]
3674 ]
3676 + formatteropts,
3675 + formatteropts,
3677 )
3676 )
3678 def perfbranchmapload(ui, repo, filter=b'', list=False, **opts):
3677 def perfbranchmapload(ui, repo, filter=b'', list=False, **opts):
3679 """benchmark reading the branchmap"""
3678 """benchmark reading the branchmap"""
3680 opts = _byteskwargs(opts)
3679 opts = _byteskwargs(opts)
3681 clearrevlogs = opts[b'clear_revlogs']
3680 clearrevlogs = opts[b'clear_revlogs']
3682
3681
3683 if list:
3682 if list:
3684 for name, kind, st in repo.cachevfs.readdir(stat=True):
3683 for name, kind, st in repo.cachevfs.readdir(stat=True):
3685 if name.startswith(b'branch2'):
3684 if name.startswith(b'branch2'):
3686 filtername = name.partition(b'-')[2] or b'unfiltered'
3685 filtername = name.partition(b'-')[2] or b'unfiltered'
3687 ui.status(
3686 ui.status(
3688 b'%s - %s\n' % (filtername, util.bytecount(st.st_size))
3687 b'%s - %s\n' % (filtername, util.bytecount(st.st_size))
3689 )
3688 )
3690 return
3689 return
3691 if not filter:
3690 if not filter:
3692 filter = None
3691 filter = None
3693 subsettable = getbranchmapsubsettable()
3692 subsettable = getbranchmapsubsettable()
3694 if filter is None:
3693 if filter is None:
3695 repo = repo.unfiltered()
3694 repo = repo.unfiltered()
3696 else:
3695 else:
3697 repo = repoview.repoview(repo, filter)
3696 repo = repoview.repoview(repo, filter)
3698
3697
3699 repo.branchmap() # make sure we have a relevant, up to date branchmap
3698 repo.branchmap() # make sure we have a relevant, up to date branchmap
3700
3699
3701 try:
3700 try:
3702 fromfile = branchmap.branchcache.fromfile
3701 fromfile = branchmap.branchcache.fromfile
3703 except AttributeError:
3702 except AttributeError:
3704 # older versions
3703 # older versions
3705 fromfile = branchmap.read
3704 fromfile = branchmap.read
3706
3705
3707 currentfilter = filter
3706 currentfilter = filter
3708 # try once without timer, the filter may not be cached
3707 # try once without timer, the filter may not be cached
3709 while fromfile(repo) is None:
3708 while fromfile(repo) is None:
3710 currentfilter = subsettable.get(currentfilter)
3709 currentfilter = subsettable.get(currentfilter)
3711 if currentfilter is None:
3710 if currentfilter is None:
3712 raise error.Abort(
3711 raise error.Abort(
3713 b'No branchmap cached for %s repo' % (filter or b'unfiltered')
3712 b'No branchmap cached for %s repo' % (filter or b'unfiltered')
3714 )
3713 )
3715 repo = repo.filtered(currentfilter)
3714 repo = repo.filtered(currentfilter)
3716 timer, fm = gettimer(ui, opts)
3715 timer, fm = gettimer(ui, opts)
3717
3716
3718 def setup():
3717 def setup():
3719 if clearrevlogs:
3718 if clearrevlogs:
3720 clearchangelog(repo)
3719 clearchangelog(repo)
3721
3720
3722 def bench():
3721 def bench():
3723 fromfile(repo)
3722 fromfile(repo)
3724
3723
3725 timer(bench, setup=setup)
3724 timer(bench, setup=setup)
3726 fm.end()
3725 fm.end()
3727
3726
3728
3727
3729 @command(b'perf::loadmarkers|perfloadmarkers')
3728 @command(b'perf::loadmarkers|perfloadmarkers')
3730 def perfloadmarkers(ui, repo):
3729 def perfloadmarkers(ui, repo):
3731 """benchmark the time to parse the on-disk markers for a repo
3730 """benchmark the time to parse the on-disk markers for a repo
3732
3731
3733 Result is the number of markers in the repo."""
3732 Result is the number of markers in the repo."""
3734 timer, fm = gettimer(ui)
3733 timer, fm = gettimer(ui)
3735 svfs = getsvfs(repo)
3734 svfs = getsvfs(repo)
3736 timer(lambda: len(obsolete.obsstore(repo, svfs)))
3735 timer(lambda: len(obsolete.obsstore(repo, svfs)))
3737 fm.end()
3736 fm.end()
3738
3737
3739
3738
3740 @command(
3739 @command(
3741 b'perf::lrucachedict|perflrucachedict',
3740 b'perf::lrucachedict|perflrucachedict',
3742 formatteropts
3741 formatteropts
3743 + [
3742 + [
3744 (b'', b'costlimit', 0, b'maximum total cost of items in cache'),
3743 (b'', b'costlimit', 0, b'maximum total cost of items in cache'),
3745 (b'', b'mincost', 0, b'smallest cost of items in cache'),
3744 (b'', b'mincost', 0, b'smallest cost of items in cache'),
3746 (b'', b'maxcost', 100, b'maximum cost of items in cache'),
3745 (b'', b'maxcost', 100, b'maximum cost of items in cache'),
3747 (b'', b'size', 4, b'size of cache'),
3746 (b'', b'size', 4, b'size of cache'),
3748 (b'', b'gets', 10000, b'number of key lookups'),
3747 (b'', b'gets', 10000, b'number of key lookups'),
3749 (b'', b'sets', 10000, b'number of key sets'),
3748 (b'', b'sets', 10000, b'number of key sets'),
3750 (b'', b'mixed', 10000, b'number of mixed mode operations'),
3749 (b'', b'mixed', 10000, b'number of mixed mode operations'),
3751 (
3750 (
3752 b'',
3751 b'',
3753 b'mixedgetfreq',
3752 b'mixedgetfreq',
3754 50,
3753 50,
3755 b'frequency of get vs set ops in mixed mode',
3754 b'frequency of get vs set ops in mixed mode',
3756 ),
3755 ),
3757 ],
3756 ],
3758 norepo=True,
3757 norepo=True,
3759 )
3758 )
3760 def perflrucache(
3759 def perflrucache(
3761 ui,
3760 ui,
3762 mincost=0,
3761 mincost=0,
3763 maxcost=100,
3762 maxcost=100,
3764 costlimit=0,
3763 costlimit=0,
3765 size=4,
3764 size=4,
3766 gets=10000,
3765 gets=10000,
3767 sets=10000,
3766 sets=10000,
3768 mixed=10000,
3767 mixed=10000,
3769 mixedgetfreq=50,
3768 mixedgetfreq=50,
3770 **opts
3769 **opts
3771 ):
3770 ):
3772 opts = _byteskwargs(opts)
3771 opts = _byteskwargs(opts)
3773
3772
3774 def doinit():
3773 def doinit():
3775 for i in _xrange(10000):
3774 for i in _xrange(10000):
3776 util.lrucachedict(size)
3775 util.lrucachedict(size)
3777
3776
3778 costrange = list(range(mincost, maxcost + 1))
3777 costrange = list(range(mincost, maxcost + 1))
3779
3778
3780 values = []
3779 values = []
3781 for i in _xrange(size):
3780 for i in _xrange(size):
3782 values.append(random.randint(0, _maxint))
3781 values.append(random.randint(0, _maxint))
3783
3782
3784 # Get mode fills the cache and tests raw lookup performance with no
3783 # Get mode fills the cache and tests raw lookup performance with no
3785 # eviction.
3784 # eviction.
3786 getseq = []
3785 getseq = []
3787 for i in _xrange(gets):
3786 for i in _xrange(gets):
3788 getseq.append(random.choice(values))
3787 getseq.append(random.choice(values))
3789
3788
3790 def dogets():
3789 def dogets():
3791 d = util.lrucachedict(size)
3790 d = util.lrucachedict(size)
3792 for v in values:
3791 for v in values:
3793 d[v] = v
3792 d[v] = v
3794 for key in getseq:
3793 for key in getseq:
3795 value = d[key]
3794 value = d[key]
3796 value # silence pyflakes warning
3795 value # silence pyflakes warning
3797
3796
3798 def dogetscost():
3797 def dogetscost():
3799 d = util.lrucachedict(size, maxcost=costlimit)
3798 d = util.lrucachedict(size, maxcost=costlimit)
3800 for i, v in enumerate(values):
3799 for i, v in enumerate(values):
3801 d.insert(v, v, cost=costs[i])
3800 d.insert(v, v, cost=costs[i])
3802 for key in getseq:
3801 for key in getseq:
3803 try:
3802 try:
3804 value = d[key]
3803 value = d[key]
3805 value # silence pyflakes warning
3804 value # silence pyflakes warning
3806 except KeyError:
3805 except KeyError:
3807 pass
3806 pass
3808
3807
3809 # Set mode tests insertion speed with cache eviction.
3808 # Set mode tests insertion speed with cache eviction.
3810 setseq = []
3809 setseq = []
3811 costs = []
3810 costs = []
3812 for i in _xrange(sets):
3811 for i in _xrange(sets):
3813 setseq.append(random.randint(0, _maxint))
3812 setseq.append(random.randint(0, _maxint))
3814 costs.append(random.choice(costrange))
3813 costs.append(random.choice(costrange))
3815
3814
3816 def doinserts():
3815 def doinserts():
3817 d = util.lrucachedict(size)
3816 d = util.lrucachedict(size)
3818 for v in setseq:
3817 for v in setseq:
3819 d.insert(v, v)
3818 d.insert(v, v)
3820
3819
3821 def doinsertscost():
3820 def doinsertscost():
3822 d = util.lrucachedict(size, maxcost=costlimit)
3821 d = util.lrucachedict(size, maxcost=costlimit)
3823 for i, v in enumerate(setseq):
3822 for i, v in enumerate(setseq):
3824 d.insert(v, v, cost=costs[i])
3823 d.insert(v, v, cost=costs[i])
3825
3824
3826 def dosets():
3825 def dosets():
3827 d = util.lrucachedict(size)
3826 d = util.lrucachedict(size)
3828 for v in setseq:
3827 for v in setseq:
3829 d[v] = v
3828 d[v] = v
3830
3829
3831 # Mixed mode randomly performs gets and sets with eviction.
3830 # Mixed mode randomly performs gets and sets with eviction.
3832 mixedops = []
3831 mixedops = []
3833 for i in _xrange(mixed):
3832 for i in _xrange(mixed):
3834 r = random.randint(0, 100)
3833 r = random.randint(0, 100)
3835 if r < mixedgetfreq:
3834 if r < mixedgetfreq:
3836 op = 0
3835 op = 0
3837 else:
3836 else:
3838 op = 1
3837 op = 1
3839
3838
3840 mixedops.append(
3839 mixedops.append(
3841 (op, random.randint(0, size * 2), random.choice(costrange))
3840 (op, random.randint(0, size * 2), random.choice(costrange))
3842 )
3841 )
3843
3842
3844 def domixed():
3843 def domixed():
3845 d = util.lrucachedict(size)
3844 d = util.lrucachedict(size)
3846
3845
3847 for op, v, cost in mixedops:
3846 for op, v, cost in mixedops:
3848 if op == 0:
3847 if op == 0:
3849 try:
3848 try:
3850 d[v]
3849 d[v]
3851 except KeyError:
3850 except KeyError:
3852 pass
3851 pass
3853 else:
3852 else:
3854 d[v] = v
3853 d[v] = v
3855
3854
3856 def domixedcost():
3855 def domixedcost():
3857 d = util.lrucachedict(size, maxcost=costlimit)
3856 d = util.lrucachedict(size, maxcost=costlimit)
3858
3857
3859 for op, v, cost in mixedops:
3858 for op, v, cost in mixedops:
3860 if op == 0:
3859 if op == 0:
3861 try:
3860 try:
3862 d[v]
3861 d[v]
3863 except KeyError:
3862 except KeyError:
3864 pass
3863 pass
3865 else:
3864 else:
3866 d.insert(v, v, cost=cost)
3865 d.insert(v, v, cost=cost)
3867
3866
3868 benches = [
3867 benches = [
3869 (doinit, b'init'),
3868 (doinit, b'init'),
3870 ]
3869 ]
3871
3870
3872 if costlimit:
3871 if costlimit:
3873 benches.extend(
3872 benches.extend(
3874 [
3873 [
3875 (dogetscost, b'gets w/ cost limit'),
3874 (dogetscost, b'gets w/ cost limit'),
3876 (doinsertscost, b'inserts w/ cost limit'),
3875 (doinsertscost, b'inserts w/ cost limit'),
3877 (domixedcost, b'mixed w/ cost limit'),
3876 (domixedcost, b'mixed w/ cost limit'),
3878 ]
3877 ]
3879 )
3878 )
3880 else:
3879 else:
3881 benches.extend(
3880 benches.extend(
3882 [
3881 [
3883 (dogets, b'gets'),
3882 (dogets, b'gets'),
3884 (doinserts, b'inserts'),
3883 (doinserts, b'inserts'),
3885 (dosets, b'sets'),
3884 (dosets, b'sets'),
3886 (domixed, b'mixed'),
3885 (domixed, b'mixed'),
3887 ]
3886 ]
3888 )
3887 )
3889
3888
3890 for fn, title in benches:
3889 for fn, title in benches:
3891 timer, fm = gettimer(ui, opts)
3890 timer, fm = gettimer(ui, opts)
3892 timer(fn, title=title)
3891 timer(fn, title=title)
3893 fm.end()
3892 fm.end()
3894
3893
3895
3894
3896 @command(
3895 @command(
3897 b'perf::write|perfwrite',
3896 b'perf::write|perfwrite',
3898 formatteropts
3897 formatteropts
3899 + [
3898 + [
3900 (b'', b'write-method', b'write', b'ui write method'),
3899 (b'', b'write-method', b'write', b'ui write method'),
3901 (b'', b'nlines', 100, b'number of lines'),
3900 (b'', b'nlines', 100, b'number of lines'),
3902 (b'', b'nitems', 100, b'number of items (per line)'),
3901 (b'', b'nitems', 100, b'number of items (per line)'),
3903 (b'', b'item', b'x', b'item that is written'),
3902 (b'', b'item', b'x', b'item that is written'),
3904 (b'', b'batch-line', None, b'pass whole line to write method at once'),
3903 (b'', b'batch-line', None, b'pass whole line to write method at once'),
3905 (b'', b'flush-line', None, b'flush after each line'),
3904 (b'', b'flush-line', None, b'flush after each line'),
3906 ],
3905 ],
3907 )
3906 )
3908 def perfwrite(ui, repo, **opts):
3907 def perfwrite(ui, repo, **opts):
3909 """microbenchmark ui.write (and others)"""
3908 """microbenchmark ui.write (and others)"""
3910 opts = _byteskwargs(opts)
3909 opts = _byteskwargs(opts)
3911
3910
3912 write = getattr(ui, _sysstr(opts[b'write_method']))
3911 write = getattr(ui, _sysstr(opts[b'write_method']))
3913 nlines = int(opts[b'nlines'])
3912 nlines = int(opts[b'nlines'])
3914 nitems = int(opts[b'nitems'])
3913 nitems = int(opts[b'nitems'])
3915 item = opts[b'item']
3914 item = opts[b'item']
3916 batch_line = opts.get(b'batch_line')
3915 batch_line = opts.get(b'batch_line')
3917 flush_line = opts.get(b'flush_line')
3916 flush_line = opts.get(b'flush_line')
3918
3917
3919 if batch_line:
3918 if batch_line:
3920 line = item * nitems + b'\n'
3919 line = item * nitems + b'\n'
3921
3920
3922 def benchmark():
3921 def benchmark():
3923 for i in pycompat.xrange(nlines):
3922 for i in pycompat.xrange(nlines):
3924 if batch_line:
3923 if batch_line:
3925 write(line)
3924 write(line)
3926 else:
3925 else:
3927 for i in pycompat.xrange(nitems):
3926 for i in pycompat.xrange(nitems):
3928 write(item)
3927 write(item)
3929 write(b'\n')
3928 write(b'\n')
3930 if flush_line:
3929 if flush_line:
3931 ui.flush()
3930 ui.flush()
3932 ui.flush()
3931 ui.flush()
3933
3932
3934 timer, fm = gettimer(ui, opts)
3933 timer, fm = gettimer(ui, opts)
3935 timer(benchmark)
3934 timer(benchmark)
3936 fm.end()
3935 fm.end()
3937
3936
3938
3937
3939 def uisetup(ui):
3938 def uisetup(ui):
3940 if util.safehasattr(cmdutil, b'openrevlog') and not util.safehasattr(
3939 if util.safehasattr(cmdutil, b'openrevlog') and not util.safehasattr(
3941 commands, b'debugrevlogopts'
3940 commands, b'debugrevlogopts'
3942 ):
3941 ):
3943 # for "historical portability":
3942 # for "historical portability":
3944 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
3943 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
3945 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
3944 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
3946 # openrevlog() should cause failure, because it has been
3945 # openrevlog() should cause failure, because it has been
3947 # available since 3.5 (or 49c583ca48c4).
3946 # available since 3.5 (or 49c583ca48c4).
3948 def openrevlog(orig, repo, cmd, file_, opts):
3947 def openrevlog(orig, repo, cmd, file_, opts):
3949 if opts.get(b'dir') and not util.safehasattr(repo, b'dirlog'):
3948 if opts.get(b'dir') and not util.safehasattr(repo, b'dirlog'):
3950 raise error.Abort(
3949 raise error.Abort(
3951 b"This version doesn't support --dir option",
3950 b"This version doesn't support --dir option",
3952 hint=b"use 3.5 or later",
3951 hint=b"use 3.5 or later",
3953 )
3952 )
3954 return orig(repo, cmd, file_, opts)
3953 return orig(repo, cmd, file_, opts)
3955
3954
3956 extensions.wrapfunction(cmdutil, b'openrevlog', openrevlog)
3955 extensions.wrapfunction(cmdutil, b'openrevlog', openrevlog)
3957
3956
3958
3957
3959 @command(
3958 @command(
3960 b'perf::progress|perfprogress',
3959 b'perf::progress|perfprogress',
3961 formatteropts
3960 formatteropts
3962 + [
3961 + [
3963 (b'', b'topic', b'topic', b'topic for progress messages'),
3962 (b'', b'topic', b'topic', b'topic for progress messages'),
3964 (b'c', b'total', 1000000, b'total value we are progressing to'),
3963 (b'c', b'total', 1000000, b'total value we are progressing to'),
3965 ],
3964 ],
3966 norepo=True,
3965 norepo=True,
3967 )
3966 )
3968 def perfprogress(ui, topic=None, total=None, **opts):
3967 def perfprogress(ui, topic=None, total=None, **opts):
3969 """printing of progress bars"""
3968 """printing of progress bars"""
3970 opts = _byteskwargs(opts)
3969 opts = _byteskwargs(opts)
3971
3970
3972 timer, fm = gettimer(ui, opts)
3971 timer, fm = gettimer(ui, opts)
3973
3972
3974 def doprogress():
3973 def doprogress():
3975 with ui.makeprogress(topic, total=total) as progress:
3974 with ui.makeprogress(topic, total=total) as progress:
3976 for i in _xrange(total):
3975 for i in _xrange(total):
3977 progress.increment()
3976 progress.increment()
3978
3977
3979 timer(doprogress)
3978 timer(doprogress)
3980 fm.end()
3979 fm.end()
@@ -1,93 +1,92 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 #
2 #
3 # A small script to automatically reject idle Diffs
3 # A small script to automatically reject idle Diffs
4 #
4 #
5 # you need to set the PHABBOT_USER and PHABBOT_TOKEN environment variable for authentication
5 # you need to set the PHABBOT_USER and PHABBOT_TOKEN environment variable for authentication
6 from __future__ import absolute_import, print_function
7
6
8 import datetime
7 import datetime
9 import os
8 import os
10 import sys
9 import sys
11
10
12 import phabricator
11 import phabricator
13
12
14 MESSAGE = """There seems to have been no activities on this Diff for the past 3 Months.
13 MESSAGE = """There seems to have been no activities on this Diff for the past 3 Months.
15
14
16 By policy, we are automatically moving it out of the `need-review` state.
15 By policy, we are automatically moving it out of the `need-review` state.
17
16
18 Please, move it back to `need-review` without hesitation if this diff should still be discussed.
17 Please, move it back to `need-review` without hesitation if this diff should still be discussed.
19
18
20 :baymax:need-review-idle:
19 :baymax:need-review-idle:
21 """
20 """
22
21
23
22
24 PHAB_URL = "https://phab.mercurial-scm.org/api/"
23 PHAB_URL = "https://phab.mercurial-scm.org/api/"
25 USER = os.environ.get("PHABBOT_USER", "baymax")
24 USER = os.environ.get("PHABBOT_USER", "baymax")
26 TOKEN = os.environ.get("PHABBOT_TOKEN")
25 TOKEN = os.environ.get("PHABBOT_TOKEN")
27
26
28
27
29 NOW = datetime.datetime.now()
28 NOW = datetime.datetime.now()
30
29
31 # 3 months in seconds
30 # 3 months in seconds
32 DELAY = 60 * 60 * 24 * 30 * 3
31 DELAY = 60 * 60 * 24 * 30 * 3
33
32
34
33
35 def get_all_diff(phab):
34 def get_all_diff(phab):
36 """Fetch all the diff that the need review"""
35 """Fetch all the diff that the need review"""
37 return phab.differential.query(
36 return phab.differential.query(
38 status="status-needs-review",
37 status="status-needs-review",
39 order="order-modified",
38 order="order-modified",
40 paths=[('HG', None)],
39 paths=[('HG', None)],
41 )
40 )
42
41
43
42
44 def filter_diffs(diffs, older_than):
43 def filter_diffs(diffs, older_than):
45 """filter diffs to only keep the one unmodified sin <older_than> seconds"""
44 """filter diffs to only keep the one unmodified sin <older_than> seconds"""
46 olds = []
45 olds = []
47 for d in diffs:
46 for d in diffs:
48 modified = int(d['dateModified'])
47 modified = int(d['dateModified'])
49 modified = datetime.datetime.fromtimestamp(modified)
48 modified = datetime.datetime.fromtimestamp(modified)
50 d["idleFor"] = idle_for = NOW - modified
49 d["idleFor"] = idle_for = NOW - modified
51 if idle_for.total_seconds() > older_than:
50 if idle_for.total_seconds() > older_than:
52 olds.append(d)
51 olds.append(d)
53 return olds
52 return olds
54
53
55
54
56 def nudge_diff(phab, diff):
55 def nudge_diff(phab, diff):
57 """Comment on the idle diff and reject it"""
56 """Comment on the idle diff and reject it"""
58 diff_id = int(d['id'])
57 diff_id = int(d['id'])
59 phab.differential.createcomment(
58 phab.differential.createcomment(
60 revision_id=diff_id, message=MESSAGE, action="reject"
59 revision_id=diff_id, message=MESSAGE, action="reject"
61 )
60 )
62
61
63
62
64 if not USER:
63 if not USER:
65 print(
64 print(
66 "not user specified please set PHABBOT_USER and PHABBOT_TOKEN",
65 "not user specified please set PHABBOT_USER and PHABBOT_TOKEN",
67 file=sys.stderr,
66 file=sys.stderr,
68 )
67 )
69 elif not TOKEN:
68 elif not TOKEN:
70 print(
69 print(
71 "not api-token specified please set PHABBOT_USER and PHABBOT_TOKEN",
70 "not api-token specified please set PHABBOT_USER and PHABBOT_TOKEN",
72 file=sys.stderr,
71 file=sys.stderr,
73 )
72 )
74 sys.exit(1)
73 sys.exit(1)
75
74
76 phab = phabricator.Phabricator(USER, host=PHAB_URL, token=TOKEN)
75 phab = phabricator.Phabricator(USER, host=PHAB_URL, token=TOKEN)
77 phab.connect()
76 phab.connect()
78 phab.update_interfaces()
77 phab.update_interfaces()
79 print('Hello "%s".' % phab.user.whoami()['realName'])
78 print('Hello "%s".' % phab.user.whoami()['realName'])
80
79
81 diffs = get_all_diff(phab)
80 diffs = get_all_diff(phab)
82 print("Found %d Diffs" % len(diffs))
81 print("Found %d Diffs" % len(diffs))
83 olds = filter_diffs(diffs, DELAY)
82 olds = filter_diffs(diffs, DELAY)
84 print("Found %d old Diffs" % len(olds))
83 print("Found %d old Diffs" % len(olds))
85 for d in olds:
84 for d in olds:
86 diff_id = d['id']
85 diff_id = d['id']
87 status = d['statusName']
86 status = d['statusName']
88 modified = int(d['dateModified'])
87 modified = int(d['dateModified'])
89 idle_for = d["idleFor"]
88 idle_for = d["idleFor"]
90 msg = 'nudging D%s in "%s" state for %s'
89 msg = 'nudging D%s in "%s" state for %s'
91 print(msg % (diff_id, status, idle_for))
90 print(msg % (diff_id, status, idle_for))
92 # uncomment to actually affect phab
91 # uncomment to actually affect phab
93 nudge_diff(phab, d)
92 nudge_diff(phab, d)
@@ -1,27 +1,26 b''
1 '''
1 '''
2 Examples of useful python hooks for Mercurial.
2 Examples of useful python hooks for Mercurial.
3 '''
3 '''
4 from __future__ import absolute_import
5 from mercurial import (
4 from mercurial import (
6 patch,
5 patch,
7 util,
6 util,
8 )
7 )
9
8
10
9
11 def diffstat(ui, repo, **kwargs):
10 def diffstat(ui, repo, **kwargs):
12 """Example usage:
11 """Example usage:
13
12
14 [hooks]
13 [hooks]
15 commit.diffstat = python:/path/to/this/file.py:diffstat
14 commit.diffstat = python:/path/to/this/file.py:diffstat
16 changegroup.diffstat = python:/path/to/this/file.py:diffstat
15 changegroup.diffstat = python:/path/to/this/file.py:diffstat
17 """
16 """
18 if kwargs.get('parent2'):
17 if kwargs.get('parent2'):
19 return
18 return
20 node = kwargs['node']
19 node = kwargs['node']
21 first = repo[node].p1().node()
20 first = repo[node].p1().node()
22 if 'url' in kwargs:
21 if 'url' in kwargs:
23 last = repo.changelog.tip()
22 last = repo.changelog.tip()
24 else:
23 else:
25 last = node
24 last = node
26 diff = patch.diff(repo, first, last)
25 diff = patch.diff(repo, first, last)
27 ui.write(patch.diffstat(util.iterlines(diff)))
26 ui.write(patch.diffstat(util.iterlines(diff)))
@@ -1,239 +1,238 b''
1 # Copyright (c) 2016-present, Gregory Szorc
1 # Copyright (c) 2016-present, Gregory Szorc
2 # All rights reserved.
2 # All rights reserved.
3 #
3 #
4 # This software may be modified and distributed under the terms
4 # This software may be modified and distributed under the terms
5 # of the BSD license. See the LICENSE file for details.
5 # of the BSD license. See the LICENSE file for details.
6
6
7 from __future__ import absolute_import
8
7
9 import cffi
8 import cffi
10 import distutils.ccompiler
9 import distutils.ccompiler
11 import os
10 import os
12 import re
11 import re
13 import subprocess
12 import subprocess
14 import tempfile
13 import tempfile
15
14
16
15
17 HERE = os.path.abspath(os.path.dirname(__file__))
16 HERE = os.path.abspath(os.path.dirname(__file__))
18
17
19 SOURCES = [
18 SOURCES = [
20 "zstd/%s" % p
19 "zstd/%s" % p
21 for p in (
20 for p in (
22 "common/debug.c",
21 "common/debug.c",
23 "common/entropy_common.c",
22 "common/entropy_common.c",
24 "common/error_private.c",
23 "common/error_private.c",
25 "common/fse_decompress.c",
24 "common/fse_decompress.c",
26 "common/pool.c",
25 "common/pool.c",
27 "common/threading.c",
26 "common/threading.c",
28 "common/xxhash.c",
27 "common/xxhash.c",
29 "common/zstd_common.c",
28 "common/zstd_common.c",
30 "compress/fse_compress.c",
29 "compress/fse_compress.c",
31 "compress/hist.c",
30 "compress/hist.c",
32 "compress/huf_compress.c",
31 "compress/huf_compress.c",
33 "compress/zstd_compress.c",
32 "compress/zstd_compress.c",
34 "compress/zstd_compress_literals.c",
33 "compress/zstd_compress_literals.c",
35 "compress/zstd_compress_sequences.c",
34 "compress/zstd_compress_sequences.c",
36 "compress/zstd_double_fast.c",
35 "compress/zstd_double_fast.c",
37 "compress/zstd_fast.c",
36 "compress/zstd_fast.c",
38 "compress/zstd_lazy.c",
37 "compress/zstd_lazy.c",
39 "compress/zstd_ldm.c",
38 "compress/zstd_ldm.c",
40 "compress/zstd_opt.c",
39 "compress/zstd_opt.c",
41 "compress/zstdmt_compress.c",
40 "compress/zstdmt_compress.c",
42 "decompress/huf_decompress.c",
41 "decompress/huf_decompress.c",
43 "decompress/zstd_ddict.c",
42 "decompress/zstd_ddict.c",
44 "decompress/zstd_decompress.c",
43 "decompress/zstd_decompress.c",
45 "decompress/zstd_decompress_block.c",
44 "decompress/zstd_decompress_block.c",
46 "dictBuilder/cover.c",
45 "dictBuilder/cover.c",
47 "dictBuilder/fastcover.c",
46 "dictBuilder/fastcover.c",
48 "dictBuilder/divsufsort.c",
47 "dictBuilder/divsufsort.c",
49 "dictBuilder/zdict.c",
48 "dictBuilder/zdict.c",
50 )
49 )
51 ]
50 ]
52
51
53 # Headers whose preprocessed output will be fed into cdef().
52 # Headers whose preprocessed output will be fed into cdef().
54 HEADERS = [
53 HEADERS = [
55 os.path.join(HERE, "zstd", *p)
54 os.path.join(HERE, "zstd", *p)
56 for p in (
55 for p in (
57 ("zstd.h",),
56 ("zstd.h",),
58 ("dictBuilder", "zdict.h"),
57 ("dictBuilder", "zdict.h"),
59 )
58 )
60 ]
59 ]
61
60
62 INCLUDE_DIRS = [
61 INCLUDE_DIRS = [
63 os.path.join(HERE, d)
62 os.path.join(HERE, d)
64 for d in (
63 for d in (
65 "zstd",
64 "zstd",
66 "zstd/common",
65 "zstd/common",
67 "zstd/compress",
66 "zstd/compress",
68 "zstd/decompress",
67 "zstd/decompress",
69 "zstd/dictBuilder",
68 "zstd/dictBuilder",
70 )
69 )
71 ]
70 ]
72
71
73 # cffi can't parse some of the primitives in zstd.h. So we invoke the
72 # cffi can't parse some of the primitives in zstd.h. So we invoke the
74 # preprocessor and feed its output into cffi.
73 # preprocessor and feed its output into cffi.
75 compiler = distutils.ccompiler.new_compiler()
74 compiler = distutils.ccompiler.new_compiler()
76
75
77 # Needed for MSVC.
76 # Needed for MSVC.
78 if hasattr(compiler, "initialize"):
77 if hasattr(compiler, "initialize"):
79 compiler.initialize()
78 compiler.initialize()
80
79
81 # Distutils doesn't set compiler.preprocessor, so invoke the preprocessor
80 # Distutils doesn't set compiler.preprocessor, so invoke the preprocessor
82 # manually.
81 # manually.
83 if compiler.compiler_type == "unix":
82 if compiler.compiler_type == "unix":
84 args = list(compiler.executables["compiler"])
83 args = list(compiler.executables["compiler"])
85 args.extend(
84 args.extend(
86 [
85 [
87 "-E",
86 "-E",
88 "-DZSTD_STATIC_LINKING_ONLY",
87 "-DZSTD_STATIC_LINKING_ONLY",
89 "-DZDICT_STATIC_LINKING_ONLY",
88 "-DZDICT_STATIC_LINKING_ONLY",
90 ]
89 ]
91 )
90 )
92 elif compiler.compiler_type == "msvc":
91 elif compiler.compiler_type == "msvc":
93 args = [compiler.cc]
92 args = [compiler.cc]
94 args.extend(
93 args.extend(
95 [
94 [
96 "/EP",
95 "/EP",
97 "/DZSTD_STATIC_LINKING_ONLY",
96 "/DZSTD_STATIC_LINKING_ONLY",
98 "/DZDICT_STATIC_LINKING_ONLY",
97 "/DZDICT_STATIC_LINKING_ONLY",
99 ]
98 ]
100 )
99 )
101 else:
100 else:
102 raise Exception("unsupported compiler type: %s" % compiler.compiler_type)
101 raise Exception("unsupported compiler type: %s" % compiler.compiler_type)
103
102
104
103
105 def preprocess(path):
104 def preprocess(path):
106 with open(path, "rb") as fh:
105 with open(path, "rb") as fh:
107 lines = []
106 lines = []
108 it = iter(fh)
107 it = iter(fh)
109
108
110 for l in it:
109 for l in it:
111 # zstd.h includes <stddef.h>, which is also included by cffi's
110 # zstd.h includes <stddef.h>, which is also included by cffi's
112 # boilerplate. This can lead to duplicate declarations. So we strip
111 # boilerplate. This can lead to duplicate declarations. So we strip
113 # this include from the preprocessor invocation.
112 # this include from the preprocessor invocation.
114 #
113 #
115 # The same things happens for including zstd.h, so give it the same
114 # The same things happens for including zstd.h, so give it the same
116 # treatment.
115 # treatment.
117 #
116 #
118 # We define ZSTD_STATIC_LINKING_ONLY, which is redundant with the inline
117 # We define ZSTD_STATIC_LINKING_ONLY, which is redundant with the inline
119 # #define in zstdmt_compress.h and results in a compiler warning. So drop
118 # #define in zstdmt_compress.h and results in a compiler warning. So drop
120 # the inline #define.
119 # the inline #define.
121 if l.startswith(
120 if l.startswith(
122 (
121 (
123 b"#include <stddef.h>",
122 b"#include <stddef.h>",
124 b'#include "zstd.h"',
123 b'#include "zstd.h"',
125 b"#define ZSTD_STATIC_LINKING_ONLY",
124 b"#define ZSTD_STATIC_LINKING_ONLY",
126 )
125 )
127 ):
126 ):
128 continue
127 continue
129
128
130 # The preprocessor environment on Windows doesn't define include
129 # The preprocessor environment on Windows doesn't define include
131 # paths, so the #include of limits.h fails. We work around this
130 # paths, so the #include of limits.h fails. We work around this
132 # by removing that import and defining INT_MAX ourselves. This is
131 # by removing that import and defining INT_MAX ourselves. This is
133 # a bit hacky. But it gets the job done.
132 # a bit hacky. But it gets the job done.
134 # TODO make limits.h work on Windows so we ensure INT_MAX is
133 # TODO make limits.h work on Windows so we ensure INT_MAX is
135 # correct.
134 # correct.
136 if l.startswith(b"#include <limits.h>"):
135 if l.startswith(b"#include <limits.h>"):
137 l = b"#define INT_MAX 2147483647\n"
136 l = b"#define INT_MAX 2147483647\n"
138
137
139 # ZSTDLIB_API may not be defined if we dropped zstd.h. It isn't
138 # ZSTDLIB_API may not be defined if we dropped zstd.h. It isn't
140 # important so just filter it out.
139 # important so just filter it out.
141 if l.startswith(b"ZSTDLIB_API"):
140 if l.startswith(b"ZSTDLIB_API"):
142 l = l[len(b"ZSTDLIB_API ") :]
141 l = l[len(b"ZSTDLIB_API ") :]
143
142
144 lines.append(l)
143 lines.append(l)
145
144
146 fd, input_file = tempfile.mkstemp(suffix=".h")
145 fd, input_file = tempfile.mkstemp(suffix=".h")
147 os.write(fd, b"".join(lines))
146 os.write(fd, b"".join(lines))
148 os.close(fd)
147 os.close(fd)
149
148
150 try:
149 try:
151 env = dict(os.environ)
150 env = dict(os.environ)
152 if getattr(compiler, "_paths", None):
151 if getattr(compiler, "_paths", None):
153 env["PATH"] = compiler._paths
152 env["PATH"] = compiler._paths
154 process = subprocess.Popen(
153 process = subprocess.Popen(
155 args + [input_file], stdout=subprocess.PIPE, env=env
154 args + [input_file], stdout=subprocess.PIPE, env=env
156 )
155 )
157 output = process.communicate()[0]
156 output = process.communicate()[0]
158 ret = process.poll()
157 ret = process.poll()
159 if ret:
158 if ret:
160 raise Exception("preprocessor exited with error")
159 raise Exception("preprocessor exited with error")
161
160
162 return output
161 return output
163 finally:
162 finally:
164 os.unlink(input_file)
163 os.unlink(input_file)
165
164
166
165
167 def normalize_output(output):
166 def normalize_output(output):
168 lines = []
167 lines = []
169 for line in output.splitlines():
168 for line in output.splitlines():
170 # CFFI's parser doesn't like __attribute__ on UNIX compilers.
169 # CFFI's parser doesn't like __attribute__ on UNIX compilers.
171 if line.startswith(b'__attribute__ ((visibility ("default"))) '):
170 if line.startswith(b'__attribute__ ((visibility ("default"))) '):
172 line = line[len(b'__attribute__ ((visibility ("default"))) ') :]
171 line = line[len(b'__attribute__ ((visibility ("default"))) ') :]
173
172
174 if line.startswith(b"__attribute__((deprecated("):
173 if line.startswith(b"__attribute__((deprecated("):
175 continue
174 continue
176 elif b"__declspec(deprecated(" in line:
175 elif b"__declspec(deprecated(" in line:
177 continue
176 continue
178
177
179 lines.append(line)
178 lines.append(line)
180
179
181 return b"\n".join(lines)
180 return b"\n".join(lines)
182
181
183
182
184 ffi = cffi.FFI()
183 ffi = cffi.FFI()
185 # zstd.h uses a possible undefined MIN(). Define it until
184 # zstd.h uses a possible undefined MIN(). Define it until
186 # https://github.com/facebook/zstd/issues/976 is fixed.
185 # https://github.com/facebook/zstd/issues/976 is fixed.
187 # *_DISABLE_DEPRECATE_WARNINGS prevents the compiler from emitting a warning
186 # *_DISABLE_DEPRECATE_WARNINGS prevents the compiler from emitting a warning
188 # when cffi uses the function. Since we statically link against zstd, even
187 # when cffi uses the function. Since we statically link against zstd, even
189 # if we use the deprecated functions it shouldn't be a huge problem.
188 # if we use the deprecated functions it shouldn't be a huge problem.
190 ffi.set_source(
189 ffi.set_source(
191 "_zstd_cffi",
190 "_zstd_cffi",
192 """
191 """
193 #define MIN(a,b) ((a)<(b) ? (a) : (b))
192 #define MIN(a,b) ((a)<(b) ? (a) : (b))
194 #define ZSTD_STATIC_LINKING_ONLY
193 #define ZSTD_STATIC_LINKING_ONLY
195 #include <zstd.h>
194 #include <zstd.h>
196 #define ZDICT_STATIC_LINKING_ONLY
195 #define ZDICT_STATIC_LINKING_ONLY
197 #define ZDICT_DISABLE_DEPRECATE_WARNINGS
196 #define ZDICT_DISABLE_DEPRECATE_WARNINGS
198 #include <zdict.h>
197 #include <zdict.h>
199 """,
198 """,
200 sources=SOURCES,
199 sources=SOURCES,
201 include_dirs=INCLUDE_DIRS,
200 include_dirs=INCLUDE_DIRS,
202 extra_compile_args=["-DZSTD_MULTITHREAD"],
201 extra_compile_args=["-DZSTD_MULTITHREAD"],
203 )
202 )
204
203
205 DEFINE = re.compile(b"^\\#define ([a-zA-Z0-9_]+) ")
204 DEFINE = re.compile(b"^\\#define ([a-zA-Z0-9_]+) ")
206
205
207 sources = []
206 sources = []
208
207
209 # Feed normalized preprocessor output for headers into the cdef parser.
208 # Feed normalized preprocessor output for headers into the cdef parser.
210 for header in HEADERS:
209 for header in HEADERS:
211 preprocessed = preprocess(header)
210 preprocessed = preprocess(header)
212 sources.append(normalize_output(preprocessed))
211 sources.append(normalize_output(preprocessed))
213
212
214 # #define's are effectively erased as part of going through preprocessor.
213 # #define's are effectively erased as part of going through preprocessor.
215 # So perform a manual pass to re-add those to the cdef source.
214 # So perform a manual pass to re-add those to the cdef source.
216 with open(header, "rb") as fh:
215 with open(header, "rb") as fh:
217 for line in fh:
216 for line in fh:
218 line = line.strip()
217 line = line.strip()
219 m = DEFINE.match(line)
218 m = DEFINE.match(line)
220 if not m:
219 if not m:
221 continue
220 continue
222
221
223 if m.group(1) == b"ZSTD_STATIC_LINKING_ONLY":
222 if m.group(1) == b"ZSTD_STATIC_LINKING_ONLY":
224 continue
223 continue
225
224
226 # The parser doesn't like some constants with complex values.
225 # The parser doesn't like some constants with complex values.
227 if m.group(1) in (b"ZSTD_LIB_VERSION", b"ZSTD_VERSION_STRING"):
226 if m.group(1) in (b"ZSTD_LIB_VERSION", b"ZSTD_VERSION_STRING"):
228 continue
227 continue
229
228
230 # The ... is magic syntax by the cdef parser to resolve the
229 # The ... is magic syntax by the cdef parser to resolve the
231 # value at compile time.
230 # value at compile time.
232 sources.append(m.group(0) + b" ...")
231 sources.append(m.group(0) + b" ...")
233
232
234 cdeflines = b"\n".join(sources).splitlines()
233 cdeflines = b"\n".join(sources).splitlines()
235 cdeflines = [l for l in cdeflines if l.strip()]
234 cdeflines = [l for l in cdeflines if l.strip()]
236 ffi.cdef(b"\n".join(cdeflines).decode("latin1"))
235 ffi.cdef(b"\n".join(cdeflines).decode("latin1"))
237
236
238 if __name__ == "__main__":
237 if __name__ == "__main__":
239 ffi.compile()
238 ffi.compile()
@@ -1,120 +1,119 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # Copyright (c) 2016-present, Gregory Szorc
2 # Copyright (c) 2016-present, Gregory Szorc
3 # All rights reserved.
3 # All rights reserved.
4 #
4 #
5 # This software may be modified and distributed under the terms
5 # This software may be modified and distributed under the terms
6 # of the BSD license. See the LICENSE file for details.
6 # of the BSD license. See the LICENSE file for details.
7
7
8 from __future__ import print_function
9
8
10 from distutils.version import LooseVersion
9 from distutils.version import LooseVersion
11 import os
10 import os
12 import sys
11 import sys
13 from setuptools import setup
12 from setuptools import setup
14
13
15 # Need change in 1.10 for ffi.from_buffer() to handle all buffer types
14 # Need change in 1.10 for ffi.from_buffer() to handle all buffer types
16 # (like memoryview).
15 # (like memoryview).
17 # Need feature in 1.11 for ffi.gc() to declare size of objects so we avoid
16 # Need feature in 1.11 for ffi.gc() to declare size of objects so we avoid
18 # garbage collection pitfalls.
17 # garbage collection pitfalls.
19 MINIMUM_CFFI_VERSION = "1.11"
18 MINIMUM_CFFI_VERSION = "1.11"
20
19
21 try:
20 try:
22 import cffi
21 import cffi
23
22
24 # PyPy (and possibly other distros) have CFFI distributed as part of
23 # PyPy (and possibly other distros) have CFFI distributed as part of
25 # them. The install_requires for CFFI below won't work. We need to sniff
24 # them. The install_requires for CFFI below won't work. We need to sniff
26 # out the CFFI version here and reject CFFI if it is too old.
25 # out the CFFI version here and reject CFFI if it is too old.
27 cffi_version = LooseVersion(cffi.__version__)
26 cffi_version = LooseVersion(cffi.__version__)
28 if cffi_version < LooseVersion(MINIMUM_CFFI_VERSION):
27 if cffi_version < LooseVersion(MINIMUM_CFFI_VERSION):
29 print(
28 print(
30 "CFFI 1.11 or newer required (%s found); "
29 "CFFI 1.11 or newer required (%s found); "
31 "not building CFFI backend" % cffi_version,
30 "not building CFFI backend" % cffi_version,
32 file=sys.stderr,
31 file=sys.stderr,
33 )
32 )
34 cffi = None
33 cffi = None
35
34
36 except ImportError:
35 except ImportError:
37 cffi = None
36 cffi = None
38
37
39 import setup_zstd
38 import setup_zstd
40
39
41 SUPPORT_LEGACY = False
40 SUPPORT_LEGACY = False
42 SYSTEM_ZSTD = False
41 SYSTEM_ZSTD = False
43 WARNINGS_AS_ERRORS = False
42 WARNINGS_AS_ERRORS = False
44
43
45 if os.environ.get("ZSTD_WARNINGS_AS_ERRORS", ""):
44 if os.environ.get("ZSTD_WARNINGS_AS_ERRORS", ""):
46 WARNINGS_AS_ERRORS = True
45 WARNINGS_AS_ERRORS = True
47
46
48 if "--legacy" in sys.argv:
47 if "--legacy" in sys.argv:
49 SUPPORT_LEGACY = True
48 SUPPORT_LEGACY = True
50 sys.argv.remove("--legacy")
49 sys.argv.remove("--legacy")
51
50
52 if "--system-zstd" in sys.argv:
51 if "--system-zstd" in sys.argv:
53 SYSTEM_ZSTD = True
52 SYSTEM_ZSTD = True
54 sys.argv.remove("--system-zstd")
53 sys.argv.remove("--system-zstd")
55
54
56 if "--warnings-as-errors" in sys.argv:
55 if "--warnings-as-errors" in sys.argv:
57 WARNINGS_AS_ERRORS = True
56 WARNINGS_AS_ERRORS = True
58 sys.argv.remove("--warning-as-errors")
57 sys.argv.remove("--warning-as-errors")
59
58
60 # Code for obtaining the Extension instance is in its own module to
59 # Code for obtaining the Extension instance is in its own module to
61 # facilitate reuse in other projects.
60 # facilitate reuse in other projects.
62 extensions = [
61 extensions = [
63 setup_zstd.get_c_extension(
62 setup_zstd.get_c_extension(
64 name="zstd",
63 name="zstd",
65 support_legacy=SUPPORT_LEGACY,
64 support_legacy=SUPPORT_LEGACY,
66 system_zstd=SYSTEM_ZSTD,
65 system_zstd=SYSTEM_ZSTD,
67 warnings_as_errors=WARNINGS_AS_ERRORS,
66 warnings_as_errors=WARNINGS_AS_ERRORS,
68 ),
67 ),
69 ]
68 ]
70
69
71 install_requires = []
70 install_requires = []
72
71
73 if cffi:
72 if cffi:
74 import make_cffi
73 import make_cffi
75
74
76 extensions.append(make_cffi.ffi.distutils_extension())
75 extensions.append(make_cffi.ffi.distutils_extension())
77 install_requires.append("cffi>=%s" % MINIMUM_CFFI_VERSION)
76 install_requires.append("cffi>=%s" % MINIMUM_CFFI_VERSION)
78
77
79 version = None
78 version = None
80
79
81 with open("c-ext/python-zstandard.h", "r") as fh:
80 with open("c-ext/python-zstandard.h", "r") as fh:
82 for line in fh:
81 for line in fh:
83 if not line.startswith("#define PYTHON_ZSTANDARD_VERSION"):
82 if not line.startswith("#define PYTHON_ZSTANDARD_VERSION"):
84 continue
83 continue
85
84
86 version = line.split()[2][1:-1]
85 version = line.split()[2][1:-1]
87 break
86 break
88
87
89 if not version:
88 if not version:
90 raise Exception(
89 raise Exception(
91 "could not resolve package version; " "this should never happen"
90 "could not resolve package version; " "this should never happen"
92 )
91 )
93
92
94 setup(
93 setup(
95 name="zstandard",
94 name="zstandard",
96 version=version,
95 version=version,
97 description="Zstandard bindings for Python",
96 description="Zstandard bindings for Python",
98 long_description=open("README.rst", "r").read(),
97 long_description=open("README.rst", "r").read(),
99 url="https://github.com/indygreg/python-zstandard",
98 url="https://github.com/indygreg/python-zstandard",
100 author="Gregory Szorc",
99 author="Gregory Szorc",
101 author_email="gregory.szorc@gmail.com",
100 author_email="gregory.szorc@gmail.com",
102 license="BSD",
101 license="BSD",
103 classifiers=[
102 classifiers=[
104 "Development Status :: 4 - Beta",
103 "Development Status :: 4 - Beta",
105 "Intended Audience :: Developers",
104 "Intended Audience :: Developers",
106 "License :: OSI Approved :: BSD License",
105 "License :: OSI Approved :: BSD License",
107 "Programming Language :: C",
106 "Programming Language :: C",
108 "Programming Language :: Python :: 2.7",
107 "Programming Language :: Python :: 2.7",
109 "Programming Language :: Python :: 3.5",
108 "Programming Language :: Python :: 3.5",
110 "Programming Language :: Python :: 3.6",
109 "Programming Language :: Python :: 3.6",
111 "Programming Language :: Python :: 3.7",
110 "Programming Language :: Python :: 3.7",
112 "Programming Language :: Python :: 3.8",
111 "Programming Language :: Python :: 3.8",
113 ],
112 ],
114 keywords="zstandard zstd compression",
113 keywords="zstandard zstd compression",
115 packages=["zstandard"],
114 packages=["zstandard"],
116 ext_modules=extensions,
115 ext_modules=extensions,
117 test_suite="tests",
116 test_suite="tests",
118 install_requires=install_requires,
117 install_requires=install_requires,
119 tests_require=["hypothesis"],
118 tests_require=["hypothesis"],
120 )
119 )
@@ -1,70 +1,68 b''
1 from __future__ import unicode_literals
2
3 import unittest
1 import unittest
4
2
5 import zstandard as zstd
3 import zstandard as zstd
6
4
7 from .common import (
5 from .common import (
8 make_cffi,
6 make_cffi,
9 TestCase,
7 TestCase,
10 )
8 )
11
9
12
10
13 @make_cffi
11 @make_cffi
14 class TestModuleAttributes(TestCase):
12 class TestModuleAttributes(TestCase):
15 def test_version(self):
13 def test_version(self):
16 self.assertEqual(zstd.ZSTD_VERSION, (1, 4, 4))
14 self.assertEqual(zstd.ZSTD_VERSION, (1, 4, 4))
17
15
18 self.assertEqual(zstd.__version__, "0.13.0")
16 self.assertEqual(zstd.__version__, "0.13.0")
19
17
20 def test_constants(self):
18 def test_constants(self):
21 self.assertEqual(zstd.MAX_COMPRESSION_LEVEL, 22)
19 self.assertEqual(zstd.MAX_COMPRESSION_LEVEL, 22)
22 self.assertEqual(zstd.FRAME_HEADER, b"\x28\xb5\x2f\xfd")
20 self.assertEqual(zstd.FRAME_HEADER, b"\x28\xb5\x2f\xfd")
23
21
24 def test_hasattr(self):
22 def test_hasattr(self):
25 attrs = (
23 attrs = (
26 "CONTENTSIZE_UNKNOWN",
24 "CONTENTSIZE_UNKNOWN",
27 "CONTENTSIZE_ERROR",
25 "CONTENTSIZE_ERROR",
28 "COMPRESSION_RECOMMENDED_INPUT_SIZE",
26 "COMPRESSION_RECOMMENDED_INPUT_SIZE",
29 "COMPRESSION_RECOMMENDED_OUTPUT_SIZE",
27 "COMPRESSION_RECOMMENDED_OUTPUT_SIZE",
30 "DECOMPRESSION_RECOMMENDED_INPUT_SIZE",
28 "DECOMPRESSION_RECOMMENDED_INPUT_SIZE",
31 "DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE",
29 "DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE",
32 "MAGIC_NUMBER",
30 "MAGIC_NUMBER",
33 "FLUSH_BLOCK",
31 "FLUSH_BLOCK",
34 "FLUSH_FRAME",
32 "FLUSH_FRAME",
35 "BLOCKSIZELOG_MAX",
33 "BLOCKSIZELOG_MAX",
36 "BLOCKSIZE_MAX",
34 "BLOCKSIZE_MAX",
37 "WINDOWLOG_MIN",
35 "WINDOWLOG_MIN",
38 "WINDOWLOG_MAX",
36 "WINDOWLOG_MAX",
39 "CHAINLOG_MIN",
37 "CHAINLOG_MIN",
40 "CHAINLOG_MAX",
38 "CHAINLOG_MAX",
41 "HASHLOG_MIN",
39 "HASHLOG_MIN",
42 "HASHLOG_MAX",
40 "HASHLOG_MAX",
43 "HASHLOG3_MAX",
41 "HASHLOG3_MAX",
44 "MINMATCH_MIN",
42 "MINMATCH_MIN",
45 "MINMATCH_MAX",
43 "MINMATCH_MAX",
46 "SEARCHLOG_MIN",
44 "SEARCHLOG_MIN",
47 "SEARCHLOG_MAX",
45 "SEARCHLOG_MAX",
48 "SEARCHLENGTH_MIN",
46 "SEARCHLENGTH_MIN",
49 "SEARCHLENGTH_MAX",
47 "SEARCHLENGTH_MAX",
50 "TARGETLENGTH_MIN",
48 "TARGETLENGTH_MIN",
51 "TARGETLENGTH_MAX",
49 "TARGETLENGTH_MAX",
52 "LDM_MINMATCH_MIN",
50 "LDM_MINMATCH_MIN",
53 "LDM_MINMATCH_MAX",
51 "LDM_MINMATCH_MAX",
54 "LDM_BUCKETSIZELOG_MAX",
52 "LDM_BUCKETSIZELOG_MAX",
55 "STRATEGY_FAST",
53 "STRATEGY_FAST",
56 "STRATEGY_DFAST",
54 "STRATEGY_DFAST",
57 "STRATEGY_GREEDY",
55 "STRATEGY_GREEDY",
58 "STRATEGY_LAZY",
56 "STRATEGY_LAZY",
59 "STRATEGY_LAZY2",
57 "STRATEGY_LAZY2",
60 "STRATEGY_BTLAZY2",
58 "STRATEGY_BTLAZY2",
61 "STRATEGY_BTOPT",
59 "STRATEGY_BTOPT",
62 "STRATEGY_BTULTRA",
60 "STRATEGY_BTULTRA",
63 "STRATEGY_BTULTRA2",
61 "STRATEGY_BTULTRA2",
64 "DICT_TYPE_AUTO",
62 "DICT_TYPE_AUTO",
65 "DICT_TYPE_RAWCONTENT",
63 "DICT_TYPE_RAWCONTENT",
66 "DICT_TYPE_FULLDICT",
64 "DICT_TYPE_FULLDICT",
67 )
65 )
68
66
69 for a in attrs:
67 for a in attrs:
70 self.assertTrue(hasattr(zstd, a), a)
68 self.assertTrue(hasattr(zstd, a), a)
@@ -1,75 +1,74 b''
1 # Copyright (c) 2017-present, Gregory Szorc
1 # Copyright (c) 2017-present, Gregory Szorc
2 # All rights reserved.
2 # All rights reserved.
3 #
3 #
4 # This software may be modified and distributed under the terms
4 # This software may be modified and distributed under the terms
5 # of the BSD license. See the LICENSE file for details.
5 # of the BSD license. See the LICENSE file for details.
6
6
7 """Python interface to the Zstandard (zstd) compression library."""
7 """Python interface to the Zstandard (zstd) compression library."""
8
8
9 from __future__ import absolute_import, unicode_literals
10
9
11 # This module serves 2 roles:
10 # This module serves 2 roles:
12 #
11 #
13 # 1) Export the C or CFFI "backend" through a central module.
12 # 1) Export the C or CFFI "backend" through a central module.
14 # 2) Implement additional functionality built on top of C or CFFI backend.
13 # 2) Implement additional functionality built on top of C or CFFI backend.
15
14
16 import os
15 import os
17 import platform
16 import platform
18
17
19 # Some Python implementations don't support C extensions. That's why we have
18 # Some Python implementations don't support C extensions. That's why we have
20 # a CFFI implementation in the first place. The code here import one of our
19 # a CFFI implementation in the first place. The code here import one of our
21 # "backends" then re-exports the symbols from this module. For convenience,
20 # "backends" then re-exports the symbols from this module. For convenience,
22 # we support falling back to the CFFI backend if the C extension can't be
21 # we support falling back to the CFFI backend if the C extension can't be
23 # imported. But for performance reasons, we only do this on unknown Python
22 # imported. But for performance reasons, we only do this on unknown Python
24 # implementation. Notably, for CPython we require the C extension by default.
23 # implementation. Notably, for CPython we require the C extension by default.
25 # Because someone will inevitably want special behavior, the behavior is
24 # Because someone will inevitably want special behavior, the behavior is
26 # configurable via an environment variable. A potentially better way to handle
25 # configurable via an environment variable. A potentially better way to handle
27 # this is to import a special ``__importpolicy__`` module or something
26 # this is to import a special ``__importpolicy__`` module or something
28 # defining a variable and `setup.py` could write the file with whatever
27 # defining a variable and `setup.py` could write the file with whatever
29 # policy was specified at build time. Until someone needs it, we go with
28 # policy was specified at build time. Until someone needs it, we go with
30 # the hacky but simple environment variable approach.
29 # the hacky but simple environment variable approach.
31 _module_policy = os.environ.get("PYTHON_ZSTANDARD_IMPORT_POLICY", "default")
30 _module_policy = os.environ.get("PYTHON_ZSTANDARD_IMPORT_POLICY", "default")
32
31
33 if _module_policy == "default":
32 if _module_policy == "default":
34 if platform.python_implementation() in ("CPython",):
33 if platform.python_implementation() in ("CPython",):
35 from zstd import *
34 from zstd import *
36
35
37 backend = "cext"
36 backend = "cext"
38 elif platform.python_implementation() in ("PyPy",):
37 elif platform.python_implementation() in ("PyPy",):
39 from .cffi import *
38 from .cffi import *
40
39
41 backend = "cffi"
40 backend = "cffi"
42 else:
41 else:
43 try:
42 try:
44 from zstd import *
43 from zstd import *
45
44
46 backend = "cext"
45 backend = "cext"
47 except ImportError:
46 except ImportError:
48 from .cffi import *
47 from .cffi import *
49
48
50 backend = "cffi"
49 backend = "cffi"
51 elif _module_policy == "cffi_fallback":
50 elif _module_policy == "cffi_fallback":
52 try:
51 try:
53 from zstd import *
52 from zstd import *
54
53
55 backend = "cext"
54 backend = "cext"
56 except ImportError:
55 except ImportError:
57 from .cffi import *
56 from .cffi import *
58
57
59 backend = "cffi"
58 backend = "cffi"
60 elif _module_policy == "cext":
59 elif _module_policy == "cext":
61 from zstd import *
60 from zstd import *
62
61
63 backend = "cext"
62 backend = "cext"
64 elif _module_policy == "cffi":
63 elif _module_policy == "cffi":
65 from .cffi import *
64 from .cffi import *
66
65
67 backend = "cffi"
66 backend = "cffi"
68 else:
67 else:
69 raise ImportError(
68 raise ImportError(
70 "unknown module import policy: %s; use default, cffi_fallback, "
69 "unknown module import policy: %s; use default, cffi_fallback, "
71 "cext, or cffi" % _module_policy
70 "cext, or cffi" % _module_policy
72 )
71 )
73
72
74 # Keep this in sync with python-zstandard.h.
73 # Keep this in sync with python-zstandard.h.
75 __version__ = "0.13.0"
74 __version__ = "0.13.0"
@@ -1,2769 +1,2768 b''
1 # Copyright (c) 2016-present, Gregory Szorc
1 # Copyright (c) 2016-present, Gregory Szorc
2 # All rights reserved.
2 # All rights reserved.
3 #
3 #
4 # This software may be modified and distributed under the terms
4 # This software may be modified and distributed under the terms
5 # of the BSD license. See the LICENSE file for details.
5 # of the BSD license. See the LICENSE file for details.
6
6
7 """Python interface to the Zstandard (zstd) compression library."""
7 """Python interface to the Zstandard (zstd) compression library."""
8
8
9 from __future__ import absolute_import, unicode_literals
10
9
11 # This should match what the C extension exports.
10 # This should match what the C extension exports.
12 __all__ = [
11 __all__ = [
13 #'BufferSegment',
12 #'BufferSegment',
14 #'BufferSegments',
13 #'BufferSegments',
15 #'BufferWithSegments',
14 #'BufferWithSegments',
16 #'BufferWithSegmentsCollection',
15 #'BufferWithSegmentsCollection',
17 "CompressionParameters",
16 "CompressionParameters",
18 "ZstdCompressionDict",
17 "ZstdCompressionDict",
19 "ZstdCompressionParameters",
18 "ZstdCompressionParameters",
20 "ZstdCompressor",
19 "ZstdCompressor",
21 "ZstdError",
20 "ZstdError",
22 "ZstdDecompressor",
21 "ZstdDecompressor",
23 "FrameParameters",
22 "FrameParameters",
24 "estimate_decompression_context_size",
23 "estimate_decompression_context_size",
25 "frame_content_size",
24 "frame_content_size",
26 "frame_header_size",
25 "frame_header_size",
27 "get_frame_parameters",
26 "get_frame_parameters",
28 "train_dictionary",
27 "train_dictionary",
29 # Constants.
28 # Constants.
30 "FLUSH_BLOCK",
29 "FLUSH_BLOCK",
31 "FLUSH_FRAME",
30 "FLUSH_FRAME",
32 "COMPRESSOBJ_FLUSH_FINISH",
31 "COMPRESSOBJ_FLUSH_FINISH",
33 "COMPRESSOBJ_FLUSH_BLOCK",
32 "COMPRESSOBJ_FLUSH_BLOCK",
34 "ZSTD_VERSION",
33 "ZSTD_VERSION",
35 "FRAME_HEADER",
34 "FRAME_HEADER",
36 "CONTENTSIZE_UNKNOWN",
35 "CONTENTSIZE_UNKNOWN",
37 "CONTENTSIZE_ERROR",
36 "CONTENTSIZE_ERROR",
38 "MAX_COMPRESSION_LEVEL",
37 "MAX_COMPRESSION_LEVEL",
39 "COMPRESSION_RECOMMENDED_INPUT_SIZE",
38 "COMPRESSION_RECOMMENDED_INPUT_SIZE",
40 "COMPRESSION_RECOMMENDED_OUTPUT_SIZE",
39 "COMPRESSION_RECOMMENDED_OUTPUT_SIZE",
41 "DECOMPRESSION_RECOMMENDED_INPUT_SIZE",
40 "DECOMPRESSION_RECOMMENDED_INPUT_SIZE",
42 "DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE",
41 "DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE",
43 "MAGIC_NUMBER",
42 "MAGIC_NUMBER",
44 "BLOCKSIZELOG_MAX",
43 "BLOCKSIZELOG_MAX",
45 "BLOCKSIZE_MAX",
44 "BLOCKSIZE_MAX",
46 "WINDOWLOG_MIN",
45 "WINDOWLOG_MIN",
47 "WINDOWLOG_MAX",
46 "WINDOWLOG_MAX",
48 "CHAINLOG_MIN",
47 "CHAINLOG_MIN",
49 "CHAINLOG_MAX",
48 "CHAINLOG_MAX",
50 "HASHLOG_MIN",
49 "HASHLOG_MIN",
51 "HASHLOG_MAX",
50 "HASHLOG_MAX",
52 "HASHLOG3_MAX",
51 "HASHLOG3_MAX",
53 "MINMATCH_MIN",
52 "MINMATCH_MIN",
54 "MINMATCH_MAX",
53 "MINMATCH_MAX",
55 "SEARCHLOG_MIN",
54 "SEARCHLOG_MIN",
56 "SEARCHLOG_MAX",
55 "SEARCHLOG_MAX",
57 "SEARCHLENGTH_MIN",
56 "SEARCHLENGTH_MIN",
58 "SEARCHLENGTH_MAX",
57 "SEARCHLENGTH_MAX",
59 "TARGETLENGTH_MIN",
58 "TARGETLENGTH_MIN",
60 "TARGETLENGTH_MAX",
59 "TARGETLENGTH_MAX",
61 "LDM_MINMATCH_MIN",
60 "LDM_MINMATCH_MIN",
62 "LDM_MINMATCH_MAX",
61 "LDM_MINMATCH_MAX",
63 "LDM_BUCKETSIZELOG_MAX",
62 "LDM_BUCKETSIZELOG_MAX",
64 "STRATEGY_FAST",
63 "STRATEGY_FAST",
65 "STRATEGY_DFAST",
64 "STRATEGY_DFAST",
66 "STRATEGY_GREEDY",
65 "STRATEGY_GREEDY",
67 "STRATEGY_LAZY",
66 "STRATEGY_LAZY",
68 "STRATEGY_LAZY2",
67 "STRATEGY_LAZY2",
69 "STRATEGY_BTLAZY2",
68 "STRATEGY_BTLAZY2",
70 "STRATEGY_BTOPT",
69 "STRATEGY_BTOPT",
71 "STRATEGY_BTULTRA",
70 "STRATEGY_BTULTRA",
72 "STRATEGY_BTULTRA2",
71 "STRATEGY_BTULTRA2",
73 "DICT_TYPE_AUTO",
72 "DICT_TYPE_AUTO",
74 "DICT_TYPE_RAWCONTENT",
73 "DICT_TYPE_RAWCONTENT",
75 "DICT_TYPE_FULLDICT",
74 "DICT_TYPE_FULLDICT",
76 "FORMAT_ZSTD1",
75 "FORMAT_ZSTD1",
77 "FORMAT_ZSTD1_MAGICLESS",
76 "FORMAT_ZSTD1_MAGICLESS",
78 ]
77 ]
79
78
80 import io
79 import io
81 import os
80 import os
82 import sys
81 import sys
83
82
84 from _zstd_cffi import (
83 from _zstd_cffi import (
85 ffi,
84 ffi,
86 lib,
85 lib,
87 )
86 )
88
87
89 if sys.version_info[0] == 2:
88 if sys.version_info[0] == 2:
90 bytes_type = str
89 bytes_type = str
91 int_type = long
90 int_type = long
92 else:
91 else:
93 bytes_type = bytes
92 bytes_type = bytes
94 int_type = int
93 int_type = int
95
94
96
95
97 COMPRESSION_RECOMMENDED_INPUT_SIZE = lib.ZSTD_CStreamInSize()
96 COMPRESSION_RECOMMENDED_INPUT_SIZE = lib.ZSTD_CStreamInSize()
98 COMPRESSION_RECOMMENDED_OUTPUT_SIZE = lib.ZSTD_CStreamOutSize()
97 COMPRESSION_RECOMMENDED_OUTPUT_SIZE = lib.ZSTD_CStreamOutSize()
99 DECOMPRESSION_RECOMMENDED_INPUT_SIZE = lib.ZSTD_DStreamInSize()
98 DECOMPRESSION_RECOMMENDED_INPUT_SIZE = lib.ZSTD_DStreamInSize()
100 DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE = lib.ZSTD_DStreamOutSize()
99 DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE = lib.ZSTD_DStreamOutSize()
101
100
102 new_nonzero = ffi.new_allocator(should_clear_after_alloc=False)
101 new_nonzero = ffi.new_allocator(should_clear_after_alloc=False)
103
102
104
103
105 MAX_COMPRESSION_LEVEL = lib.ZSTD_maxCLevel()
104 MAX_COMPRESSION_LEVEL = lib.ZSTD_maxCLevel()
106 MAGIC_NUMBER = lib.ZSTD_MAGICNUMBER
105 MAGIC_NUMBER = lib.ZSTD_MAGICNUMBER
107 FRAME_HEADER = b"\x28\xb5\x2f\xfd"
106 FRAME_HEADER = b"\x28\xb5\x2f\xfd"
108 CONTENTSIZE_UNKNOWN = lib.ZSTD_CONTENTSIZE_UNKNOWN
107 CONTENTSIZE_UNKNOWN = lib.ZSTD_CONTENTSIZE_UNKNOWN
109 CONTENTSIZE_ERROR = lib.ZSTD_CONTENTSIZE_ERROR
108 CONTENTSIZE_ERROR = lib.ZSTD_CONTENTSIZE_ERROR
110 ZSTD_VERSION = (
109 ZSTD_VERSION = (
111 lib.ZSTD_VERSION_MAJOR,
110 lib.ZSTD_VERSION_MAJOR,
112 lib.ZSTD_VERSION_MINOR,
111 lib.ZSTD_VERSION_MINOR,
113 lib.ZSTD_VERSION_RELEASE,
112 lib.ZSTD_VERSION_RELEASE,
114 )
113 )
115
114
116 BLOCKSIZELOG_MAX = lib.ZSTD_BLOCKSIZELOG_MAX
115 BLOCKSIZELOG_MAX = lib.ZSTD_BLOCKSIZELOG_MAX
117 BLOCKSIZE_MAX = lib.ZSTD_BLOCKSIZE_MAX
116 BLOCKSIZE_MAX = lib.ZSTD_BLOCKSIZE_MAX
118 WINDOWLOG_MIN = lib.ZSTD_WINDOWLOG_MIN
117 WINDOWLOG_MIN = lib.ZSTD_WINDOWLOG_MIN
119 WINDOWLOG_MAX = lib.ZSTD_WINDOWLOG_MAX
118 WINDOWLOG_MAX = lib.ZSTD_WINDOWLOG_MAX
120 CHAINLOG_MIN = lib.ZSTD_CHAINLOG_MIN
119 CHAINLOG_MIN = lib.ZSTD_CHAINLOG_MIN
121 CHAINLOG_MAX = lib.ZSTD_CHAINLOG_MAX
120 CHAINLOG_MAX = lib.ZSTD_CHAINLOG_MAX
122 HASHLOG_MIN = lib.ZSTD_HASHLOG_MIN
121 HASHLOG_MIN = lib.ZSTD_HASHLOG_MIN
123 HASHLOG_MAX = lib.ZSTD_HASHLOG_MAX
122 HASHLOG_MAX = lib.ZSTD_HASHLOG_MAX
124 HASHLOG3_MAX = lib.ZSTD_HASHLOG3_MAX
123 HASHLOG3_MAX = lib.ZSTD_HASHLOG3_MAX
125 MINMATCH_MIN = lib.ZSTD_MINMATCH_MIN
124 MINMATCH_MIN = lib.ZSTD_MINMATCH_MIN
126 MINMATCH_MAX = lib.ZSTD_MINMATCH_MAX
125 MINMATCH_MAX = lib.ZSTD_MINMATCH_MAX
127 SEARCHLOG_MIN = lib.ZSTD_SEARCHLOG_MIN
126 SEARCHLOG_MIN = lib.ZSTD_SEARCHLOG_MIN
128 SEARCHLOG_MAX = lib.ZSTD_SEARCHLOG_MAX
127 SEARCHLOG_MAX = lib.ZSTD_SEARCHLOG_MAX
129 SEARCHLENGTH_MIN = lib.ZSTD_MINMATCH_MIN
128 SEARCHLENGTH_MIN = lib.ZSTD_MINMATCH_MIN
130 SEARCHLENGTH_MAX = lib.ZSTD_MINMATCH_MAX
129 SEARCHLENGTH_MAX = lib.ZSTD_MINMATCH_MAX
131 TARGETLENGTH_MIN = lib.ZSTD_TARGETLENGTH_MIN
130 TARGETLENGTH_MIN = lib.ZSTD_TARGETLENGTH_MIN
132 TARGETLENGTH_MAX = lib.ZSTD_TARGETLENGTH_MAX
131 TARGETLENGTH_MAX = lib.ZSTD_TARGETLENGTH_MAX
133 LDM_MINMATCH_MIN = lib.ZSTD_LDM_MINMATCH_MIN
132 LDM_MINMATCH_MIN = lib.ZSTD_LDM_MINMATCH_MIN
134 LDM_MINMATCH_MAX = lib.ZSTD_LDM_MINMATCH_MAX
133 LDM_MINMATCH_MAX = lib.ZSTD_LDM_MINMATCH_MAX
135 LDM_BUCKETSIZELOG_MAX = lib.ZSTD_LDM_BUCKETSIZELOG_MAX
134 LDM_BUCKETSIZELOG_MAX = lib.ZSTD_LDM_BUCKETSIZELOG_MAX
136
135
137 STRATEGY_FAST = lib.ZSTD_fast
136 STRATEGY_FAST = lib.ZSTD_fast
138 STRATEGY_DFAST = lib.ZSTD_dfast
137 STRATEGY_DFAST = lib.ZSTD_dfast
139 STRATEGY_GREEDY = lib.ZSTD_greedy
138 STRATEGY_GREEDY = lib.ZSTD_greedy
140 STRATEGY_LAZY = lib.ZSTD_lazy
139 STRATEGY_LAZY = lib.ZSTD_lazy
141 STRATEGY_LAZY2 = lib.ZSTD_lazy2
140 STRATEGY_LAZY2 = lib.ZSTD_lazy2
142 STRATEGY_BTLAZY2 = lib.ZSTD_btlazy2
141 STRATEGY_BTLAZY2 = lib.ZSTD_btlazy2
143 STRATEGY_BTOPT = lib.ZSTD_btopt
142 STRATEGY_BTOPT = lib.ZSTD_btopt
144 STRATEGY_BTULTRA = lib.ZSTD_btultra
143 STRATEGY_BTULTRA = lib.ZSTD_btultra
145 STRATEGY_BTULTRA2 = lib.ZSTD_btultra2
144 STRATEGY_BTULTRA2 = lib.ZSTD_btultra2
146
145
147 DICT_TYPE_AUTO = lib.ZSTD_dct_auto
146 DICT_TYPE_AUTO = lib.ZSTD_dct_auto
148 DICT_TYPE_RAWCONTENT = lib.ZSTD_dct_rawContent
147 DICT_TYPE_RAWCONTENT = lib.ZSTD_dct_rawContent
149 DICT_TYPE_FULLDICT = lib.ZSTD_dct_fullDict
148 DICT_TYPE_FULLDICT = lib.ZSTD_dct_fullDict
150
149
151 FORMAT_ZSTD1 = lib.ZSTD_f_zstd1
150 FORMAT_ZSTD1 = lib.ZSTD_f_zstd1
152 FORMAT_ZSTD1_MAGICLESS = lib.ZSTD_f_zstd1_magicless
151 FORMAT_ZSTD1_MAGICLESS = lib.ZSTD_f_zstd1_magicless
153
152
154 FLUSH_BLOCK = 0
153 FLUSH_BLOCK = 0
155 FLUSH_FRAME = 1
154 FLUSH_FRAME = 1
156
155
157 COMPRESSOBJ_FLUSH_FINISH = 0
156 COMPRESSOBJ_FLUSH_FINISH = 0
158 COMPRESSOBJ_FLUSH_BLOCK = 1
157 COMPRESSOBJ_FLUSH_BLOCK = 1
159
158
160
159
161 def _cpu_count():
160 def _cpu_count():
162 # os.cpu_count() was introducd in Python 3.4.
161 # os.cpu_count() was introducd in Python 3.4.
163 try:
162 try:
164 return os.cpu_count() or 0
163 return os.cpu_count() or 0
165 except AttributeError:
164 except AttributeError:
166 pass
165 pass
167
166
168 # Linux.
167 # Linux.
169 try:
168 try:
170 if sys.version_info[0] == 2:
169 if sys.version_info[0] == 2:
171 return os.sysconf(b"SC_NPROCESSORS_ONLN")
170 return os.sysconf(b"SC_NPROCESSORS_ONLN")
172 else:
171 else:
173 return os.sysconf("SC_NPROCESSORS_ONLN")
172 return os.sysconf("SC_NPROCESSORS_ONLN")
174 except (AttributeError, ValueError):
173 except (AttributeError, ValueError):
175 pass
174 pass
176
175
177 # TODO implement on other platforms.
176 # TODO implement on other platforms.
178 return 0
177 return 0
179
178
180
179
181 class ZstdError(Exception):
180 class ZstdError(Exception):
182 pass
181 pass
183
182
184
183
185 def _zstd_error(zresult):
184 def _zstd_error(zresult):
186 # Resolves to bytes on Python 2 and 3. We use the string for formatting
185 # Resolves to bytes on Python 2 and 3. We use the string for formatting
187 # into error messages, which will be literal unicode. So convert it to
186 # into error messages, which will be literal unicode. So convert it to
188 # unicode.
187 # unicode.
189 return ffi.string(lib.ZSTD_getErrorName(zresult)).decode("utf-8")
188 return ffi.string(lib.ZSTD_getErrorName(zresult)).decode("utf-8")
190
189
191
190
192 def _make_cctx_params(params):
191 def _make_cctx_params(params):
193 res = lib.ZSTD_createCCtxParams()
192 res = lib.ZSTD_createCCtxParams()
194 if res == ffi.NULL:
193 if res == ffi.NULL:
195 raise MemoryError()
194 raise MemoryError()
196
195
197 res = ffi.gc(res, lib.ZSTD_freeCCtxParams)
196 res = ffi.gc(res, lib.ZSTD_freeCCtxParams)
198
197
199 attrs = [
198 attrs = [
200 (lib.ZSTD_c_format, params.format),
199 (lib.ZSTD_c_format, params.format),
201 (lib.ZSTD_c_compressionLevel, params.compression_level),
200 (lib.ZSTD_c_compressionLevel, params.compression_level),
202 (lib.ZSTD_c_windowLog, params.window_log),
201 (lib.ZSTD_c_windowLog, params.window_log),
203 (lib.ZSTD_c_hashLog, params.hash_log),
202 (lib.ZSTD_c_hashLog, params.hash_log),
204 (lib.ZSTD_c_chainLog, params.chain_log),
203 (lib.ZSTD_c_chainLog, params.chain_log),
205 (lib.ZSTD_c_searchLog, params.search_log),
204 (lib.ZSTD_c_searchLog, params.search_log),
206 (lib.ZSTD_c_minMatch, params.min_match),
205 (lib.ZSTD_c_minMatch, params.min_match),
207 (lib.ZSTD_c_targetLength, params.target_length),
206 (lib.ZSTD_c_targetLength, params.target_length),
208 (lib.ZSTD_c_strategy, params.compression_strategy),
207 (lib.ZSTD_c_strategy, params.compression_strategy),
209 (lib.ZSTD_c_contentSizeFlag, params.write_content_size),
208 (lib.ZSTD_c_contentSizeFlag, params.write_content_size),
210 (lib.ZSTD_c_checksumFlag, params.write_checksum),
209 (lib.ZSTD_c_checksumFlag, params.write_checksum),
211 (lib.ZSTD_c_dictIDFlag, params.write_dict_id),
210 (lib.ZSTD_c_dictIDFlag, params.write_dict_id),
212 (lib.ZSTD_c_nbWorkers, params.threads),
211 (lib.ZSTD_c_nbWorkers, params.threads),
213 (lib.ZSTD_c_jobSize, params.job_size),
212 (lib.ZSTD_c_jobSize, params.job_size),
214 (lib.ZSTD_c_overlapLog, params.overlap_log),
213 (lib.ZSTD_c_overlapLog, params.overlap_log),
215 (lib.ZSTD_c_forceMaxWindow, params.force_max_window),
214 (lib.ZSTD_c_forceMaxWindow, params.force_max_window),
216 (lib.ZSTD_c_enableLongDistanceMatching, params.enable_ldm),
215 (lib.ZSTD_c_enableLongDistanceMatching, params.enable_ldm),
217 (lib.ZSTD_c_ldmHashLog, params.ldm_hash_log),
216 (lib.ZSTD_c_ldmHashLog, params.ldm_hash_log),
218 (lib.ZSTD_c_ldmMinMatch, params.ldm_min_match),
217 (lib.ZSTD_c_ldmMinMatch, params.ldm_min_match),
219 (lib.ZSTD_c_ldmBucketSizeLog, params.ldm_bucket_size_log),
218 (lib.ZSTD_c_ldmBucketSizeLog, params.ldm_bucket_size_log),
220 (lib.ZSTD_c_ldmHashRateLog, params.ldm_hash_rate_log),
219 (lib.ZSTD_c_ldmHashRateLog, params.ldm_hash_rate_log),
221 ]
220 ]
222
221
223 for param, value in attrs:
222 for param, value in attrs:
224 _set_compression_parameter(res, param, value)
223 _set_compression_parameter(res, param, value)
225
224
226 return res
225 return res
227
226
228
227
229 class ZstdCompressionParameters(object):
228 class ZstdCompressionParameters(object):
230 @staticmethod
229 @staticmethod
231 def from_level(level, source_size=0, dict_size=0, **kwargs):
230 def from_level(level, source_size=0, dict_size=0, **kwargs):
232 params = lib.ZSTD_getCParams(level, source_size, dict_size)
231 params = lib.ZSTD_getCParams(level, source_size, dict_size)
233
232
234 args = {
233 args = {
235 "window_log": "windowLog",
234 "window_log": "windowLog",
236 "chain_log": "chainLog",
235 "chain_log": "chainLog",
237 "hash_log": "hashLog",
236 "hash_log": "hashLog",
238 "search_log": "searchLog",
237 "search_log": "searchLog",
239 "min_match": "minMatch",
238 "min_match": "minMatch",
240 "target_length": "targetLength",
239 "target_length": "targetLength",
241 "compression_strategy": "strategy",
240 "compression_strategy": "strategy",
242 }
241 }
243
242
244 for arg, attr in args.items():
243 for arg, attr in args.items():
245 if arg not in kwargs:
244 if arg not in kwargs:
246 kwargs[arg] = getattr(params, attr)
245 kwargs[arg] = getattr(params, attr)
247
246
248 return ZstdCompressionParameters(**kwargs)
247 return ZstdCompressionParameters(**kwargs)
249
248
250 def __init__(
249 def __init__(
251 self,
250 self,
252 format=0,
251 format=0,
253 compression_level=0,
252 compression_level=0,
254 window_log=0,
253 window_log=0,
255 hash_log=0,
254 hash_log=0,
256 chain_log=0,
255 chain_log=0,
257 search_log=0,
256 search_log=0,
258 min_match=0,
257 min_match=0,
259 target_length=0,
258 target_length=0,
260 strategy=-1,
259 strategy=-1,
261 compression_strategy=-1,
260 compression_strategy=-1,
262 write_content_size=1,
261 write_content_size=1,
263 write_checksum=0,
262 write_checksum=0,
264 write_dict_id=0,
263 write_dict_id=0,
265 job_size=0,
264 job_size=0,
266 overlap_log=-1,
265 overlap_log=-1,
267 overlap_size_log=-1,
266 overlap_size_log=-1,
268 force_max_window=0,
267 force_max_window=0,
269 enable_ldm=0,
268 enable_ldm=0,
270 ldm_hash_log=0,
269 ldm_hash_log=0,
271 ldm_min_match=0,
270 ldm_min_match=0,
272 ldm_bucket_size_log=0,
271 ldm_bucket_size_log=0,
273 ldm_hash_rate_log=-1,
272 ldm_hash_rate_log=-1,
274 ldm_hash_every_log=-1,
273 ldm_hash_every_log=-1,
275 threads=0,
274 threads=0,
276 ):
275 ):
277
276
278 params = lib.ZSTD_createCCtxParams()
277 params = lib.ZSTD_createCCtxParams()
279 if params == ffi.NULL:
278 if params == ffi.NULL:
280 raise MemoryError()
279 raise MemoryError()
281
280
282 params = ffi.gc(params, lib.ZSTD_freeCCtxParams)
281 params = ffi.gc(params, lib.ZSTD_freeCCtxParams)
283
282
284 self._params = params
283 self._params = params
285
284
286 if threads < 0:
285 if threads < 0:
287 threads = _cpu_count()
286 threads = _cpu_count()
288
287
289 # We need to set ZSTD_c_nbWorkers before ZSTD_c_jobSize and ZSTD_c_overlapLog
288 # We need to set ZSTD_c_nbWorkers before ZSTD_c_jobSize and ZSTD_c_overlapLog
290 # because setting ZSTD_c_nbWorkers resets the other parameters.
289 # because setting ZSTD_c_nbWorkers resets the other parameters.
291 _set_compression_parameter(params, lib.ZSTD_c_nbWorkers, threads)
290 _set_compression_parameter(params, lib.ZSTD_c_nbWorkers, threads)
292
291
293 _set_compression_parameter(params, lib.ZSTD_c_format, format)
292 _set_compression_parameter(params, lib.ZSTD_c_format, format)
294 _set_compression_parameter(
293 _set_compression_parameter(
295 params, lib.ZSTD_c_compressionLevel, compression_level
294 params, lib.ZSTD_c_compressionLevel, compression_level
296 )
295 )
297 _set_compression_parameter(params, lib.ZSTD_c_windowLog, window_log)
296 _set_compression_parameter(params, lib.ZSTD_c_windowLog, window_log)
298 _set_compression_parameter(params, lib.ZSTD_c_hashLog, hash_log)
297 _set_compression_parameter(params, lib.ZSTD_c_hashLog, hash_log)
299 _set_compression_parameter(params, lib.ZSTD_c_chainLog, chain_log)
298 _set_compression_parameter(params, lib.ZSTD_c_chainLog, chain_log)
300 _set_compression_parameter(params, lib.ZSTD_c_searchLog, search_log)
299 _set_compression_parameter(params, lib.ZSTD_c_searchLog, search_log)
301 _set_compression_parameter(params, lib.ZSTD_c_minMatch, min_match)
300 _set_compression_parameter(params, lib.ZSTD_c_minMatch, min_match)
302 _set_compression_parameter(
301 _set_compression_parameter(
303 params, lib.ZSTD_c_targetLength, target_length
302 params, lib.ZSTD_c_targetLength, target_length
304 )
303 )
305
304
306 if strategy != -1 and compression_strategy != -1:
305 if strategy != -1 and compression_strategy != -1:
307 raise ValueError(
306 raise ValueError(
308 "cannot specify both compression_strategy and strategy"
307 "cannot specify both compression_strategy and strategy"
309 )
308 )
310
309
311 if compression_strategy != -1:
310 if compression_strategy != -1:
312 strategy = compression_strategy
311 strategy = compression_strategy
313 elif strategy == -1:
312 elif strategy == -1:
314 strategy = 0
313 strategy = 0
315
314
316 _set_compression_parameter(params, lib.ZSTD_c_strategy, strategy)
315 _set_compression_parameter(params, lib.ZSTD_c_strategy, strategy)
317 _set_compression_parameter(
316 _set_compression_parameter(
318 params, lib.ZSTD_c_contentSizeFlag, write_content_size
317 params, lib.ZSTD_c_contentSizeFlag, write_content_size
319 )
318 )
320 _set_compression_parameter(
319 _set_compression_parameter(
321 params, lib.ZSTD_c_checksumFlag, write_checksum
320 params, lib.ZSTD_c_checksumFlag, write_checksum
322 )
321 )
323 _set_compression_parameter(params, lib.ZSTD_c_dictIDFlag, write_dict_id)
322 _set_compression_parameter(params, lib.ZSTD_c_dictIDFlag, write_dict_id)
324 _set_compression_parameter(params, lib.ZSTD_c_jobSize, job_size)
323 _set_compression_parameter(params, lib.ZSTD_c_jobSize, job_size)
325
324
326 if overlap_log != -1 and overlap_size_log != -1:
325 if overlap_log != -1 and overlap_size_log != -1:
327 raise ValueError(
326 raise ValueError(
328 "cannot specify both overlap_log and overlap_size_log"
327 "cannot specify both overlap_log and overlap_size_log"
329 )
328 )
330
329
331 if overlap_size_log != -1:
330 if overlap_size_log != -1:
332 overlap_log = overlap_size_log
331 overlap_log = overlap_size_log
333 elif overlap_log == -1:
332 elif overlap_log == -1:
334 overlap_log = 0
333 overlap_log = 0
335
334
336 _set_compression_parameter(params, lib.ZSTD_c_overlapLog, overlap_log)
335 _set_compression_parameter(params, lib.ZSTD_c_overlapLog, overlap_log)
337 _set_compression_parameter(
336 _set_compression_parameter(
338 params, lib.ZSTD_c_forceMaxWindow, force_max_window
337 params, lib.ZSTD_c_forceMaxWindow, force_max_window
339 )
338 )
340 _set_compression_parameter(
339 _set_compression_parameter(
341 params, lib.ZSTD_c_enableLongDistanceMatching, enable_ldm
340 params, lib.ZSTD_c_enableLongDistanceMatching, enable_ldm
342 )
341 )
343 _set_compression_parameter(params, lib.ZSTD_c_ldmHashLog, ldm_hash_log)
342 _set_compression_parameter(params, lib.ZSTD_c_ldmHashLog, ldm_hash_log)
344 _set_compression_parameter(
343 _set_compression_parameter(
345 params, lib.ZSTD_c_ldmMinMatch, ldm_min_match
344 params, lib.ZSTD_c_ldmMinMatch, ldm_min_match
346 )
345 )
347 _set_compression_parameter(
346 _set_compression_parameter(
348 params, lib.ZSTD_c_ldmBucketSizeLog, ldm_bucket_size_log
347 params, lib.ZSTD_c_ldmBucketSizeLog, ldm_bucket_size_log
349 )
348 )
350
349
351 if ldm_hash_rate_log != -1 and ldm_hash_every_log != -1:
350 if ldm_hash_rate_log != -1 and ldm_hash_every_log != -1:
352 raise ValueError(
351 raise ValueError(
353 "cannot specify both ldm_hash_rate_log and ldm_hash_every_log"
352 "cannot specify both ldm_hash_rate_log and ldm_hash_every_log"
354 )
353 )
355
354
356 if ldm_hash_every_log != -1:
355 if ldm_hash_every_log != -1:
357 ldm_hash_rate_log = ldm_hash_every_log
356 ldm_hash_rate_log = ldm_hash_every_log
358 elif ldm_hash_rate_log == -1:
357 elif ldm_hash_rate_log == -1:
359 ldm_hash_rate_log = 0
358 ldm_hash_rate_log = 0
360
359
361 _set_compression_parameter(
360 _set_compression_parameter(
362 params, lib.ZSTD_c_ldmHashRateLog, ldm_hash_rate_log
361 params, lib.ZSTD_c_ldmHashRateLog, ldm_hash_rate_log
363 )
362 )
364
363
365 @property
364 @property
366 def format(self):
365 def format(self):
367 return _get_compression_parameter(self._params, lib.ZSTD_c_format)
366 return _get_compression_parameter(self._params, lib.ZSTD_c_format)
368
367
369 @property
368 @property
370 def compression_level(self):
369 def compression_level(self):
371 return _get_compression_parameter(
370 return _get_compression_parameter(
372 self._params, lib.ZSTD_c_compressionLevel
371 self._params, lib.ZSTD_c_compressionLevel
373 )
372 )
374
373
375 @property
374 @property
376 def window_log(self):
375 def window_log(self):
377 return _get_compression_parameter(self._params, lib.ZSTD_c_windowLog)
376 return _get_compression_parameter(self._params, lib.ZSTD_c_windowLog)
378
377
379 @property
378 @property
380 def hash_log(self):
379 def hash_log(self):
381 return _get_compression_parameter(self._params, lib.ZSTD_c_hashLog)
380 return _get_compression_parameter(self._params, lib.ZSTD_c_hashLog)
382
381
383 @property
382 @property
384 def chain_log(self):
383 def chain_log(self):
385 return _get_compression_parameter(self._params, lib.ZSTD_c_chainLog)
384 return _get_compression_parameter(self._params, lib.ZSTD_c_chainLog)
386
385
387 @property
386 @property
388 def search_log(self):
387 def search_log(self):
389 return _get_compression_parameter(self._params, lib.ZSTD_c_searchLog)
388 return _get_compression_parameter(self._params, lib.ZSTD_c_searchLog)
390
389
391 @property
390 @property
392 def min_match(self):
391 def min_match(self):
393 return _get_compression_parameter(self._params, lib.ZSTD_c_minMatch)
392 return _get_compression_parameter(self._params, lib.ZSTD_c_minMatch)
394
393
395 @property
394 @property
396 def target_length(self):
395 def target_length(self):
397 return _get_compression_parameter(self._params, lib.ZSTD_c_targetLength)
396 return _get_compression_parameter(self._params, lib.ZSTD_c_targetLength)
398
397
399 @property
398 @property
400 def compression_strategy(self):
399 def compression_strategy(self):
401 return _get_compression_parameter(self._params, lib.ZSTD_c_strategy)
400 return _get_compression_parameter(self._params, lib.ZSTD_c_strategy)
402
401
403 @property
402 @property
404 def write_content_size(self):
403 def write_content_size(self):
405 return _get_compression_parameter(
404 return _get_compression_parameter(
406 self._params, lib.ZSTD_c_contentSizeFlag
405 self._params, lib.ZSTD_c_contentSizeFlag
407 )
406 )
408
407
409 @property
408 @property
410 def write_checksum(self):
409 def write_checksum(self):
411 return _get_compression_parameter(self._params, lib.ZSTD_c_checksumFlag)
410 return _get_compression_parameter(self._params, lib.ZSTD_c_checksumFlag)
412
411
413 @property
412 @property
414 def write_dict_id(self):
413 def write_dict_id(self):
415 return _get_compression_parameter(self._params, lib.ZSTD_c_dictIDFlag)
414 return _get_compression_parameter(self._params, lib.ZSTD_c_dictIDFlag)
416
415
417 @property
416 @property
418 def job_size(self):
417 def job_size(self):
419 return _get_compression_parameter(self._params, lib.ZSTD_c_jobSize)
418 return _get_compression_parameter(self._params, lib.ZSTD_c_jobSize)
420
419
421 @property
420 @property
422 def overlap_log(self):
421 def overlap_log(self):
423 return _get_compression_parameter(self._params, lib.ZSTD_c_overlapLog)
422 return _get_compression_parameter(self._params, lib.ZSTD_c_overlapLog)
424
423
425 @property
424 @property
426 def overlap_size_log(self):
425 def overlap_size_log(self):
427 return self.overlap_log
426 return self.overlap_log
428
427
429 @property
428 @property
430 def force_max_window(self):
429 def force_max_window(self):
431 return _get_compression_parameter(
430 return _get_compression_parameter(
432 self._params, lib.ZSTD_c_forceMaxWindow
431 self._params, lib.ZSTD_c_forceMaxWindow
433 )
432 )
434
433
435 @property
434 @property
436 def enable_ldm(self):
435 def enable_ldm(self):
437 return _get_compression_parameter(
436 return _get_compression_parameter(
438 self._params, lib.ZSTD_c_enableLongDistanceMatching
437 self._params, lib.ZSTD_c_enableLongDistanceMatching
439 )
438 )
440
439
441 @property
440 @property
442 def ldm_hash_log(self):
441 def ldm_hash_log(self):
443 return _get_compression_parameter(self._params, lib.ZSTD_c_ldmHashLog)
442 return _get_compression_parameter(self._params, lib.ZSTD_c_ldmHashLog)
444
443
445 @property
444 @property
446 def ldm_min_match(self):
445 def ldm_min_match(self):
447 return _get_compression_parameter(self._params, lib.ZSTD_c_ldmMinMatch)
446 return _get_compression_parameter(self._params, lib.ZSTD_c_ldmMinMatch)
448
447
449 @property
448 @property
450 def ldm_bucket_size_log(self):
449 def ldm_bucket_size_log(self):
451 return _get_compression_parameter(
450 return _get_compression_parameter(
452 self._params, lib.ZSTD_c_ldmBucketSizeLog
451 self._params, lib.ZSTD_c_ldmBucketSizeLog
453 )
452 )
454
453
455 @property
454 @property
456 def ldm_hash_rate_log(self):
455 def ldm_hash_rate_log(self):
457 return _get_compression_parameter(
456 return _get_compression_parameter(
458 self._params, lib.ZSTD_c_ldmHashRateLog
457 self._params, lib.ZSTD_c_ldmHashRateLog
459 )
458 )
460
459
461 @property
460 @property
462 def ldm_hash_every_log(self):
461 def ldm_hash_every_log(self):
463 return self.ldm_hash_rate_log
462 return self.ldm_hash_rate_log
464
463
465 @property
464 @property
466 def threads(self):
465 def threads(self):
467 return _get_compression_parameter(self._params, lib.ZSTD_c_nbWorkers)
466 return _get_compression_parameter(self._params, lib.ZSTD_c_nbWorkers)
468
467
469 def estimated_compression_context_size(self):
468 def estimated_compression_context_size(self):
470 return lib.ZSTD_estimateCCtxSize_usingCCtxParams(self._params)
469 return lib.ZSTD_estimateCCtxSize_usingCCtxParams(self._params)
471
470
472
471
473 CompressionParameters = ZstdCompressionParameters
472 CompressionParameters = ZstdCompressionParameters
474
473
475
474
476 def estimate_decompression_context_size():
475 def estimate_decompression_context_size():
477 return lib.ZSTD_estimateDCtxSize()
476 return lib.ZSTD_estimateDCtxSize()
478
477
479
478
480 def _set_compression_parameter(params, param, value):
479 def _set_compression_parameter(params, param, value):
481 zresult = lib.ZSTD_CCtxParams_setParameter(params, param, value)
480 zresult = lib.ZSTD_CCtxParams_setParameter(params, param, value)
482 if lib.ZSTD_isError(zresult):
481 if lib.ZSTD_isError(zresult):
483 raise ZstdError(
482 raise ZstdError(
484 "unable to set compression context parameter: %s"
483 "unable to set compression context parameter: %s"
485 % _zstd_error(zresult)
484 % _zstd_error(zresult)
486 )
485 )
487
486
488
487
489 def _get_compression_parameter(params, param):
488 def _get_compression_parameter(params, param):
490 result = ffi.new("int *")
489 result = ffi.new("int *")
491
490
492 zresult = lib.ZSTD_CCtxParams_getParameter(params, param, result)
491 zresult = lib.ZSTD_CCtxParams_getParameter(params, param, result)
493 if lib.ZSTD_isError(zresult):
492 if lib.ZSTD_isError(zresult):
494 raise ZstdError(
493 raise ZstdError(
495 "unable to get compression context parameter: %s"
494 "unable to get compression context parameter: %s"
496 % _zstd_error(zresult)
495 % _zstd_error(zresult)
497 )
496 )
498
497
499 return result[0]
498 return result[0]
500
499
501
500
502 class ZstdCompressionWriter(object):
501 class ZstdCompressionWriter(object):
503 def __init__(
502 def __init__(
504 self, compressor, writer, source_size, write_size, write_return_read
503 self, compressor, writer, source_size, write_size, write_return_read
505 ):
504 ):
506 self._compressor = compressor
505 self._compressor = compressor
507 self._writer = writer
506 self._writer = writer
508 self._write_size = write_size
507 self._write_size = write_size
509 self._write_return_read = bool(write_return_read)
508 self._write_return_read = bool(write_return_read)
510 self._entered = False
509 self._entered = False
511 self._closed = False
510 self._closed = False
512 self._bytes_compressed = 0
511 self._bytes_compressed = 0
513
512
514 self._dst_buffer = ffi.new("char[]", write_size)
513 self._dst_buffer = ffi.new("char[]", write_size)
515 self._out_buffer = ffi.new("ZSTD_outBuffer *")
514 self._out_buffer = ffi.new("ZSTD_outBuffer *")
516 self._out_buffer.dst = self._dst_buffer
515 self._out_buffer.dst = self._dst_buffer
517 self._out_buffer.size = len(self._dst_buffer)
516 self._out_buffer.size = len(self._dst_buffer)
518 self._out_buffer.pos = 0
517 self._out_buffer.pos = 0
519
518
520 zresult = lib.ZSTD_CCtx_setPledgedSrcSize(compressor._cctx, source_size)
519 zresult = lib.ZSTD_CCtx_setPledgedSrcSize(compressor._cctx, source_size)
521 if lib.ZSTD_isError(zresult):
520 if lib.ZSTD_isError(zresult):
522 raise ZstdError(
521 raise ZstdError(
523 "error setting source size: %s" % _zstd_error(zresult)
522 "error setting source size: %s" % _zstd_error(zresult)
524 )
523 )
525
524
526 def __enter__(self):
525 def __enter__(self):
527 if self._closed:
526 if self._closed:
528 raise ValueError("stream is closed")
527 raise ValueError("stream is closed")
529
528
530 if self._entered:
529 if self._entered:
531 raise ZstdError("cannot __enter__ multiple times")
530 raise ZstdError("cannot __enter__ multiple times")
532
531
533 self._entered = True
532 self._entered = True
534 return self
533 return self
535
534
536 def __exit__(self, exc_type, exc_value, exc_tb):
535 def __exit__(self, exc_type, exc_value, exc_tb):
537 self._entered = False
536 self._entered = False
538
537
539 if not exc_type and not exc_value and not exc_tb:
538 if not exc_type and not exc_value and not exc_tb:
540 self.close()
539 self.close()
541
540
542 self._compressor = None
541 self._compressor = None
543
542
544 return False
543 return False
545
544
546 def memory_size(self):
545 def memory_size(self):
547 return lib.ZSTD_sizeof_CCtx(self._compressor._cctx)
546 return lib.ZSTD_sizeof_CCtx(self._compressor._cctx)
548
547
549 def fileno(self):
548 def fileno(self):
550 f = getattr(self._writer, "fileno", None)
549 f = getattr(self._writer, "fileno", None)
551 if f:
550 if f:
552 return f()
551 return f()
553 else:
552 else:
554 raise OSError("fileno not available on underlying writer")
553 raise OSError("fileno not available on underlying writer")
555
554
556 def close(self):
555 def close(self):
557 if self._closed:
556 if self._closed:
558 return
557 return
559
558
560 try:
559 try:
561 self.flush(FLUSH_FRAME)
560 self.flush(FLUSH_FRAME)
562 finally:
561 finally:
563 self._closed = True
562 self._closed = True
564
563
565 # Call close() on underlying stream as well.
564 # Call close() on underlying stream as well.
566 f = getattr(self._writer, "close", None)
565 f = getattr(self._writer, "close", None)
567 if f:
566 if f:
568 f()
567 f()
569
568
570 @property
569 @property
571 def closed(self):
570 def closed(self):
572 return self._closed
571 return self._closed
573
572
574 def isatty(self):
573 def isatty(self):
575 return False
574 return False
576
575
577 def readable(self):
576 def readable(self):
578 return False
577 return False
579
578
580 def readline(self, size=-1):
579 def readline(self, size=-1):
581 raise io.UnsupportedOperation()
580 raise io.UnsupportedOperation()
582
581
583 def readlines(self, hint=-1):
582 def readlines(self, hint=-1):
584 raise io.UnsupportedOperation()
583 raise io.UnsupportedOperation()
585
584
586 def seek(self, offset, whence=None):
585 def seek(self, offset, whence=None):
587 raise io.UnsupportedOperation()
586 raise io.UnsupportedOperation()
588
587
589 def seekable(self):
588 def seekable(self):
590 return False
589 return False
591
590
592 def truncate(self, size=None):
591 def truncate(self, size=None):
593 raise io.UnsupportedOperation()
592 raise io.UnsupportedOperation()
594
593
595 def writable(self):
594 def writable(self):
596 return True
595 return True
597
596
598 def writelines(self, lines):
597 def writelines(self, lines):
599 raise NotImplementedError("writelines() is not yet implemented")
598 raise NotImplementedError("writelines() is not yet implemented")
600
599
601 def read(self, size=-1):
600 def read(self, size=-1):
602 raise io.UnsupportedOperation()
601 raise io.UnsupportedOperation()
603
602
604 def readall(self):
603 def readall(self):
605 raise io.UnsupportedOperation()
604 raise io.UnsupportedOperation()
606
605
607 def readinto(self, b):
606 def readinto(self, b):
608 raise io.UnsupportedOperation()
607 raise io.UnsupportedOperation()
609
608
610 def write(self, data):
609 def write(self, data):
611 if self._closed:
610 if self._closed:
612 raise ValueError("stream is closed")
611 raise ValueError("stream is closed")
613
612
614 total_write = 0
613 total_write = 0
615
614
616 data_buffer = ffi.from_buffer(data)
615 data_buffer = ffi.from_buffer(data)
617
616
618 in_buffer = ffi.new("ZSTD_inBuffer *")
617 in_buffer = ffi.new("ZSTD_inBuffer *")
619 in_buffer.src = data_buffer
618 in_buffer.src = data_buffer
620 in_buffer.size = len(data_buffer)
619 in_buffer.size = len(data_buffer)
621 in_buffer.pos = 0
620 in_buffer.pos = 0
622
621
623 out_buffer = self._out_buffer
622 out_buffer = self._out_buffer
624 out_buffer.pos = 0
623 out_buffer.pos = 0
625
624
626 while in_buffer.pos < in_buffer.size:
625 while in_buffer.pos < in_buffer.size:
627 zresult = lib.ZSTD_compressStream2(
626 zresult = lib.ZSTD_compressStream2(
628 self._compressor._cctx,
627 self._compressor._cctx,
629 out_buffer,
628 out_buffer,
630 in_buffer,
629 in_buffer,
631 lib.ZSTD_e_continue,
630 lib.ZSTD_e_continue,
632 )
631 )
633 if lib.ZSTD_isError(zresult):
632 if lib.ZSTD_isError(zresult):
634 raise ZstdError(
633 raise ZstdError(
635 "zstd compress error: %s" % _zstd_error(zresult)
634 "zstd compress error: %s" % _zstd_error(zresult)
636 )
635 )
637
636
638 if out_buffer.pos:
637 if out_buffer.pos:
639 self._writer.write(
638 self._writer.write(
640 ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
639 ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
641 )
640 )
642 total_write += out_buffer.pos
641 total_write += out_buffer.pos
643 self._bytes_compressed += out_buffer.pos
642 self._bytes_compressed += out_buffer.pos
644 out_buffer.pos = 0
643 out_buffer.pos = 0
645
644
646 if self._write_return_read:
645 if self._write_return_read:
647 return in_buffer.pos
646 return in_buffer.pos
648 else:
647 else:
649 return total_write
648 return total_write
650
649
651 def flush(self, flush_mode=FLUSH_BLOCK):
650 def flush(self, flush_mode=FLUSH_BLOCK):
652 if flush_mode == FLUSH_BLOCK:
651 if flush_mode == FLUSH_BLOCK:
653 flush = lib.ZSTD_e_flush
652 flush = lib.ZSTD_e_flush
654 elif flush_mode == FLUSH_FRAME:
653 elif flush_mode == FLUSH_FRAME:
655 flush = lib.ZSTD_e_end
654 flush = lib.ZSTD_e_end
656 else:
655 else:
657 raise ValueError("unknown flush_mode: %r" % flush_mode)
656 raise ValueError("unknown flush_mode: %r" % flush_mode)
658
657
659 if self._closed:
658 if self._closed:
660 raise ValueError("stream is closed")
659 raise ValueError("stream is closed")
661
660
662 total_write = 0
661 total_write = 0
663
662
664 out_buffer = self._out_buffer
663 out_buffer = self._out_buffer
665 out_buffer.pos = 0
664 out_buffer.pos = 0
666
665
667 in_buffer = ffi.new("ZSTD_inBuffer *")
666 in_buffer = ffi.new("ZSTD_inBuffer *")
668 in_buffer.src = ffi.NULL
667 in_buffer.src = ffi.NULL
669 in_buffer.size = 0
668 in_buffer.size = 0
670 in_buffer.pos = 0
669 in_buffer.pos = 0
671
670
672 while True:
671 while True:
673 zresult = lib.ZSTD_compressStream2(
672 zresult = lib.ZSTD_compressStream2(
674 self._compressor._cctx, out_buffer, in_buffer, flush
673 self._compressor._cctx, out_buffer, in_buffer, flush
675 )
674 )
676 if lib.ZSTD_isError(zresult):
675 if lib.ZSTD_isError(zresult):
677 raise ZstdError(
676 raise ZstdError(
678 "zstd compress error: %s" % _zstd_error(zresult)
677 "zstd compress error: %s" % _zstd_error(zresult)
679 )
678 )
680
679
681 if out_buffer.pos:
680 if out_buffer.pos:
682 self._writer.write(
681 self._writer.write(
683 ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
682 ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
684 )
683 )
685 total_write += out_buffer.pos
684 total_write += out_buffer.pos
686 self._bytes_compressed += out_buffer.pos
685 self._bytes_compressed += out_buffer.pos
687 out_buffer.pos = 0
686 out_buffer.pos = 0
688
687
689 if not zresult:
688 if not zresult:
690 break
689 break
691
690
692 return total_write
691 return total_write
693
692
694 def tell(self):
693 def tell(self):
695 return self._bytes_compressed
694 return self._bytes_compressed
696
695
697
696
698 class ZstdCompressionObj(object):
697 class ZstdCompressionObj(object):
699 def compress(self, data):
698 def compress(self, data):
700 if self._finished:
699 if self._finished:
701 raise ZstdError("cannot call compress() after compressor finished")
700 raise ZstdError("cannot call compress() after compressor finished")
702
701
703 data_buffer = ffi.from_buffer(data)
702 data_buffer = ffi.from_buffer(data)
704 source = ffi.new("ZSTD_inBuffer *")
703 source = ffi.new("ZSTD_inBuffer *")
705 source.src = data_buffer
704 source.src = data_buffer
706 source.size = len(data_buffer)
705 source.size = len(data_buffer)
707 source.pos = 0
706 source.pos = 0
708
707
709 chunks = []
708 chunks = []
710
709
711 while source.pos < len(data):
710 while source.pos < len(data):
712 zresult = lib.ZSTD_compressStream2(
711 zresult = lib.ZSTD_compressStream2(
713 self._compressor._cctx, self._out, source, lib.ZSTD_e_continue
712 self._compressor._cctx, self._out, source, lib.ZSTD_e_continue
714 )
713 )
715 if lib.ZSTD_isError(zresult):
714 if lib.ZSTD_isError(zresult):
716 raise ZstdError(
715 raise ZstdError(
717 "zstd compress error: %s" % _zstd_error(zresult)
716 "zstd compress error: %s" % _zstd_error(zresult)
718 )
717 )
719
718
720 if self._out.pos:
719 if self._out.pos:
721 chunks.append(ffi.buffer(self._out.dst, self._out.pos)[:])
720 chunks.append(ffi.buffer(self._out.dst, self._out.pos)[:])
722 self._out.pos = 0
721 self._out.pos = 0
723
722
724 return b"".join(chunks)
723 return b"".join(chunks)
725
724
726 def flush(self, flush_mode=COMPRESSOBJ_FLUSH_FINISH):
725 def flush(self, flush_mode=COMPRESSOBJ_FLUSH_FINISH):
727 if flush_mode not in (
726 if flush_mode not in (
728 COMPRESSOBJ_FLUSH_FINISH,
727 COMPRESSOBJ_FLUSH_FINISH,
729 COMPRESSOBJ_FLUSH_BLOCK,
728 COMPRESSOBJ_FLUSH_BLOCK,
730 ):
729 ):
731 raise ValueError("flush mode not recognized")
730 raise ValueError("flush mode not recognized")
732
731
733 if self._finished:
732 if self._finished:
734 raise ZstdError("compressor object already finished")
733 raise ZstdError("compressor object already finished")
735
734
736 if flush_mode == COMPRESSOBJ_FLUSH_BLOCK:
735 if flush_mode == COMPRESSOBJ_FLUSH_BLOCK:
737 z_flush_mode = lib.ZSTD_e_flush
736 z_flush_mode = lib.ZSTD_e_flush
738 elif flush_mode == COMPRESSOBJ_FLUSH_FINISH:
737 elif flush_mode == COMPRESSOBJ_FLUSH_FINISH:
739 z_flush_mode = lib.ZSTD_e_end
738 z_flush_mode = lib.ZSTD_e_end
740 self._finished = True
739 self._finished = True
741 else:
740 else:
742 raise ZstdError("unhandled flush mode")
741 raise ZstdError("unhandled flush mode")
743
742
744 assert self._out.pos == 0
743 assert self._out.pos == 0
745
744
746 in_buffer = ffi.new("ZSTD_inBuffer *")
745 in_buffer = ffi.new("ZSTD_inBuffer *")
747 in_buffer.src = ffi.NULL
746 in_buffer.src = ffi.NULL
748 in_buffer.size = 0
747 in_buffer.size = 0
749 in_buffer.pos = 0
748 in_buffer.pos = 0
750
749
751 chunks = []
750 chunks = []
752
751
753 while True:
752 while True:
754 zresult = lib.ZSTD_compressStream2(
753 zresult = lib.ZSTD_compressStream2(
755 self._compressor._cctx, self._out, in_buffer, z_flush_mode
754 self._compressor._cctx, self._out, in_buffer, z_flush_mode
756 )
755 )
757 if lib.ZSTD_isError(zresult):
756 if lib.ZSTD_isError(zresult):
758 raise ZstdError(
757 raise ZstdError(
759 "error ending compression stream: %s" % _zstd_error(zresult)
758 "error ending compression stream: %s" % _zstd_error(zresult)
760 )
759 )
761
760
762 if self._out.pos:
761 if self._out.pos:
763 chunks.append(ffi.buffer(self._out.dst, self._out.pos)[:])
762 chunks.append(ffi.buffer(self._out.dst, self._out.pos)[:])
764 self._out.pos = 0
763 self._out.pos = 0
765
764
766 if not zresult:
765 if not zresult:
767 break
766 break
768
767
769 return b"".join(chunks)
768 return b"".join(chunks)
770
769
771
770
772 class ZstdCompressionChunker(object):
771 class ZstdCompressionChunker(object):
773 def __init__(self, compressor, chunk_size):
772 def __init__(self, compressor, chunk_size):
774 self._compressor = compressor
773 self._compressor = compressor
775 self._out = ffi.new("ZSTD_outBuffer *")
774 self._out = ffi.new("ZSTD_outBuffer *")
776 self._dst_buffer = ffi.new("char[]", chunk_size)
775 self._dst_buffer = ffi.new("char[]", chunk_size)
777 self._out.dst = self._dst_buffer
776 self._out.dst = self._dst_buffer
778 self._out.size = chunk_size
777 self._out.size = chunk_size
779 self._out.pos = 0
778 self._out.pos = 0
780
779
781 self._in = ffi.new("ZSTD_inBuffer *")
780 self._in = ffi.new("ZSTD_inBuffer *")
782 self._in.src = ffi.NULL
781 self._in.src = ffi.NULL
783 self._in.size = 0
782 self._in.size = 0
784 self._in.pos = 0
783 self._in.pos = 0
785 self._finished = False
784 self._finished = False
786
785
787 def compress(self, data):
786 def compress(self, data):
788 if self._finished:
787 if self._finished:
789 raise ZstdError("cannot call compress() after compression finished")
788 raise ZstdError("cannot call compress() after compression finished")
790
789
791 if self._in.src != ffi.NULL:
790 if self._in.src != ffi.NULL:
792 raise ZstdError(
791 raise ZstdError(
793 "cannot perform operation before consuming output "
792 "cannot perform operation before consuming output "
794 "from previous operation"
793 "from previous operation"
795 )
794 )
796
795
797 data_buffer = ffi.from_buffer(data)
796 data_buffer = ffi.from_buffer(data)
798
797
799 if not len(data_buffer):
798 if not len(data_buffer):
800 return
799 return
801
800
802 self._in.src = data_buffer
801 self._in.src = data_buffer
803 self._in.size = len(data_buffer)
802 self._in.size = len(data_buffer)
804 self._in.pos = 0
803 self._in.pos = 0
805
804
806 while self._in.pos < self._in.size:
805 while self._in.pos < self._in.size:
807 zresult = lib.ZSTD_compressStream2(
806 zresult = lib.ZSTD_compressStream2(
808 self._compressor._cctx, self._out, self._in, lib.ZSTD_e_continue
807 self._compressor._cctx, self._out, self._in, lib.ZSTD_e_continue
809 )
808 )
810
809
811 if self._in.pos == self._in.size:
810 if self._in.pos == self._in.size:
812 self._in.src = ffi.NULL
811 self._in.src = ffi.NULL
813 self._in.size = 0
812 self._in.size = 0
814 self._in.pos = 0
813 self._in.pos = 0
815
814
816 if lib.ZSTD_isError(zresult):
815 if lib.ZSTD_isError(zresult):
817 raise ZstdError(
816 raise ZstdError(
818 "zstd compress error: %s" % _zstd_error(zresult)
817 "zstd compress error: %s" % _zstd_error(zresult)
819 )
818 )
820
819
821 if self._out.pos == self._out.size:
820 if self._out.pos == self._out.size:
822 yield ffi.buffer(self._out.dst, self._out.pos)[:]
821 yield ffi.buffer(self._out.dst, self._out.pos)[:]
823 self._out.pos = 0
822 self._out.pos = 0
824
823
825 def flush(self):
824 def flush(self):
826 if self._finished:
825 if self._finished:
827 raise ZstdError("cannot call flush() after compression finished")
826 raise ZstdError("cannot call flush() after compression finished")
828
827
829 if self._in.src != ffi.NULL:
828 if self._in.src != ffi.NULL:
830 raise ZstdError(
829 raise ZstdError(
831 "cannot call flush() before consuming output from "
830 "cannot call flush() before consuming output from "
832 "previous operation"
831 "previous operation"
833 )
832 )
834
833
835 while True:
834 while True:
836 zresult = lib.ZSTD_compressStream2(
835 zresult = lib.ZSTD_compressStream2(
837 self._compressor._cctx, self._out, self._in, lib.ZSTD_e_flush
836 self._compressor._cctx, self._out, self._in, lib.ZSTD_e_flush
838 )
837 )
839 if lib.ZSTD_isError(zresult):
838 if lib.ZSTD_isError(zresult):
840 raise ZstdError(
839 raise ZstdError(
841 "zstd compress error: %s" % _zstd_error(zresult)
840 "zstd compress error: %s" % _zstd_error(zresult)
842 )
841 )
843
842
844 if self._out.pos:
843 if self._out.pos:
845 yield ffi.buffer(self._out.dst, self._out.pos)[:]
844 yield ffi.buffer(self._out.dst, self._out.pos)[:]
846 self._out.pos = 0
845 self._out.pos = 0
847
846
848 if not zresult:
847 if not zresult:
849 return
848 return
850
849
851 def finish(self):
850 def finish(self):
852 if self._finished:
851 if self._finished:
853 raise ZstdError("cannot call finish() after compression finished")
852 raise ZstdError("cannot call finish() after compression finished")
854
853
855 if self._in.src != ffi.NULL:
854 if self._in.src != ffi.NULL:
856 raise ZstdError(
855 raise ZstdError(
857 "cannot call finish() before consuming output from "
856 "cannot call finish() before consuming output from "
858 "previous operation"
857 "previous operation"
859 )
858 )
860
859
861 while True:
860 while True:
862 zresult = lib.ZSTD_compressStream2(
861 zresult = lib.ZSTD_compressStream2(
863 self._compressor._cctx, self._out, self._in, lib.ZSTD_e_end
862 self._compressor._cctx, self._out, self._in, lib.ZSTD_e_end
864 )
863 )
865 if lib.ZSTD_isError(zresult):
864 if lib.ZSTD_isError(zresult):
866 raise ZstdError(
865 raise ZstdError(
867 "zstd compress error: %s" % _zstd_error(zresult)
866 "zstd compress error: %s" % _zstd_error(zresult)
868 )
867 )
869
868
870 if self._out.pos:
869 if self._out.pos:
871 yield ffi.buffer(self._out.dst, self._out.pos)[:]
870 yield ffi.buffer(self._out.dst, self._out.pos)[:]
872 self._out.pos = 0
871 self._out.pos = 0
873
872
874 if not zresult:
873 if not zresult:
875 self._finished = True
874 self._finished = True
876 return
875 return
877
876
878
877
879 class ZstdCompressionReader(object):
878 class ZstdCompressionReader(object):
880 def __init__(self, compressor, source, read_size):
879 def __init__(self, compressor, source, read_size):
881 self._compressor = compressor
880 self._compressor = compressor
882 self._source = source
881 self._source = source
883 self._read_size = read_size
882 self._read_size = read_size
884 self._entered = False
883 self._entered = False
885 self._closed = False
884 self._closed = False
886 self._bytes_compressed = 0
885 self._bytes_compressed = 0
887 self._finished_input = False
886 self._finished_input = False
888 self._finished_output = False
887 self._finished_output = False
889
888
890 self._in_buffer = ffi.new("ZSTD_inBuffer *")
889 self._in_buffer = ffi.new("ZSTD_inBuffer *")
891 # Holds a ref so backing bytes in self._in_buffer stay alive.
890 # Holds a ref so backing bytes in self._in_buffer stay alive.
892 self._source_buffer = None
891 self._source_buffer = None
893
892
894 def __enter__(self):
893 def __enter__(self):
895 if self._entered:
894 if self._entered:
896 raise ValueError("cannot __enter__ multiple times")
895 raise ValueError("cannot __enter__ multiple times")
897
896
898 self._entered = True
897 self._entered = True
899 return self
898 return self
900
899
901 def __exit__(self, exc_type, exc_value, exc_tb):
900 def __exit__(self, exc_type, exc_value, exc_tb):
902 self._entered = False
901 self._entered = False
903 self._closed = True
902 self._closed = True
904 self._source = None
903 self._source = None
905 self._compressor = None
904 self._compressor = None
906
905
907 return False
906 return False
908
907
909 def readable(self):
908 def readable(self):
910 return True
909 return True
911
910
912 def writable(self):
911 def writable(self):
913 return False
912 return False
914
913
915 def seekable(self):
914 def seekable(self):
916 return False
915 return False
917
916
918 def readline(self):
917 def readline(self):
919 raise io.UnsupportedOperation()
918 raise io.UnsupportedOperation()
920
919
921 def readlines(self):
920 def readlines(self):
922 raise io.UnsupportedOperation()
921 raise io.UnsupportedOperation()
923
922
924 def write(self, data):
923 def write(self, data):
925 raise OSError("stream is not writable")
924 raise OSError("stream is not writable")
926
925
927 def writelines(self, ignored):
926 def writelines(self, ignored):
928 raise OSError("stream is not writable")
927 raise OSError("stream is not writable")
929
928
930 def isatty(self):
929 def isatty(self):
931 return False
930 return False
932
931
933 def flush(self):
932 def flush(self):
934 return None
933 return None
935
934
936 def close(self):
935 def close(self):
937 self._closed = True
936 self._closed = True
938 return None
937 return None
939
938
940 @property
939 @property
941 def closed(self):
940 def closed(self):
942 return self._closed
941 return self._closed
943
942
944 def tell(self):
943 def tell(self):
945 return self._bytes_compressed
944 return self._bytes_compressed
946
945
947 def readall(self):
946 def readall(self):
948 chunks = []
947 chunks = []
949
948
950 while True:
949 while True:
951 chunk = self.read(1048576)
950 chunk = self.read(1048576)
952 if not chunk:
951 if not chunk:
953 break
952 break
954
953
955 chunks.append(chunk)
954 chunks.append(chunk)
956
955
957 return b"".join(chunks)
956 return b"".join(chunks)
958
957
959 def __iter__(self):
958 def __iter__(self):
960 raise io.UnsupportedOperation()
959 raise io.UnsupportedOperation()
961
960
962 def __next__(self):
961 def __next__(self):
963 raise io.UnsupportedOperation()
962 raise io.UnsupportedOperation()
964
963
965 next = __next__
964 next = __next__
966
965
967 def _read_input(self):
966 def _read_input(self):
968 if self._finished_input:
967 if self._finished_input:
969 return
968 return
970
969
971 if hasattr(self._source, "read"):
970 if hasattr(self._source, "read"):
972 data = self._source.read(self._read_size)
971 data = self._source.read(self._read_size)
973
972
974 if not data:
973 if not data:
975 self._finished_input = True
974 self._finished_input = True
976 return
975 return
977
976
978 self._source_buffer = ffi.from_buffer(data)
977 self._source_buffer = ffi.from_buffer(data)
979 self._in_buffer.src = self._source_buffer
978 self._in_buffer.src = self._source_buffer
980 self._in_buffer.size = len(self._source_buffer)
979 self._in_buffer.size = len(self._source_buffer)
981 self._in_buffer.pos = 0
980 self._in_buffer.pos = 0
982 else:
981 else:
983 self._source_buffer = ffi.from_buffer(self._source)
982 self._source_buffer = ffi.from_buffer(self._source)
984 self._in_buffer.src = self._source_buffer
983 self._in_buffer.src = self._source_buffer
985 self._in_buffer.size = len(self._source_buffer)
984 self._in_buffer.size = len(self._source_buffer)
986 self._in_buffer.pos = 0
985 self._in_buffer.pos = 0
987
986
988 def _compress_into_buffer(self, out_buffer):
987 def _compress_into_buffer(self, out_buffer):
989 if self._in_buffer.pos >= self._in_buffer.size:
988 if self._in_buffer.pos >= self._in_buffer.size:
990 return
989 return
991
990
992 old_pos = out_buffer.pos
991 old_pos = out_buffer.pos
993
992
994 zresult = lib.ZSTD_compressStream2(
993 zresult = lib.ZSTD_compressStream2(
995 self._compressor._cctx,
994 self._compressor._cctx,
996 out_buffer,
995 out_buffer,
997 self._in_buffer,
996 self._in_buffer,
998 lib.ZSTD_e_continue,
997 lib.ZSTD_e_continue,
999 )
998 )
1000
999
1001 self._bytes_compressed += out_buffer.pos - old_pos
1000 self._bytes_compressed += out_buffer.pos - old_pos
1002
1001
1003 if self._in_buffer.pos == self._in_buffer.size:
1002 if self._in_buffer.pos == self._in_buffer.size:
1004 self._in_buffer.src = ffi.NULL
1003 self._in_buffer.src = ffi.NULL
1005 self._in_buffer.pos = 0
1004 self._in_buffer.pos = 0
1006 self._in_buffer.size = 0
1005 self._in_buffer.size = 0
1007 self._source_buffer = None
1006 self._source_buffer = None
1008
1007
1009 if not hasattr(self._source, "read"):
1008 if not hasattr(self._source, "read"):
1010 self._finished_input = True
1009 self._finished_input = True
1011
1010
1012 if lib.ZSTD_isError(zresult):
1011 if lib.ZSTD_isError(zresult):
1013 raise ZstdError("zstd compress error: %s", _zstd_error(zresult))
1012 raise ZstdError("zstd compress error: %s", _zstd_error(zresult))
1014
1013
1015 return out_buffer.pos and out_buffer.pos == out_buffer.size
1014 return out_buffer.pos and out_buffer.pos == out_buffer.size
1016
1015
1017 def read(self, size=-1):
1016 def read(self, size=-1):
1018 if self._closed:
1017 if self._closed:
1019 raise ValueError("stream is closed")
1018 raise ValueError("stream is closed")
1020
1019
1021 if size < -1:
1020 if size < -1:
1022 raise ValueError("cannot read negative amounts less than -1")
1021 raise ValueError("cannot read negative amounts less than -1")
1023
1022
1024 if size == -1:
1023 if size == -1:
1025 return self.readall()
1024 return self.readall()
1026
1025
1027 if self._finished_output or size == 0:
1026 if self._finished_output or size == 0:
1028 return b""
1027 return b""
1029
1028
1030 # Need a dedicated ref to dest buffer otherwise it gets collected.
1029 # Need a dedicated ref to dest buffer otherwise it gets collected.
1031 dst_buffer = ffi.new("char[]", size)
1030 dst_buffer = ffi.new("char[]", size)
1032 out_buffer = ffi.new("ZSTD_outBuffer *")
1031 out_buffer = ffi.new("ZSTD_outBuffer *")
1033 out_buffer.dst = dst_buffer
1032 out_buffer.dst = dst_buffer
1034 out_buffer.size = size
1033 out_buffer.size = size
1035 out_buffer.pos = 0
1034 out_buffer.pos = 0
1036
1035
1037 if self._compress_into_buffer(out_buffer):
1036 if self._compress_into_buffer(out_buffer):
1038 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
1037 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
1039
1038
1040 while not self._finished_input:
1039 while not self._finished_input:
1041 self._read_input()
1040 self._read_input()
1042
1041
1043 if self._compress_into_buffer(out_buffer):
1042 if self._compress_into_buffer(out_buffer):
1044 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
1043 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
1045
1044
1046 # EOF
1045 # EOF
1047 old_pos = out_buffer.pos
1046 old_pos = out_buffer.pos
1048
1047
1049 zresult = lib.ZSTD_compressStream2(
1048 zresult = lib.ZSTD_compressStream2(
1050 self._compressor._cctx, out_buffer, self._in_buffer, lib.ZSTD_e_end
1049 self._compressor._cctx, out_buffer, self._in_buffer, lib.ZSTD_e_end
1051 )
1050 )
1052
1051
1053 self._bytes_compressed += out_buffer.pos - old_pos
1052 self._bytes_compressed += out_buffer.pos - old_pos
1054
1053
1055 if lib.ZSTD_isError(zresult):
1054 if lib.ZSTD_isError(zresult):
1056 raise ZstdError(
1055 raise ZstdError(
1057 "error ending compression stream: %s", _zstd_error(zresult)
1056 "error ending compression stream: %s", _zstd_error(zresult)
1058 )
1057 )
1059
1058
1060 if zresult == 0:
1059 if zresult == 0:
1061 self._finished_output = True
1060 self._finished_output = True
1062
1061
1063 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
1062 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
1064
1063
1065 def read1(self, size=-1):
1064 def read1(self, size=-1):
1066 if self._closed:
1065 if self._closed:
1067 raise ValueError("stream is closed")
1066 raise ValueError("stream is closed")
1068
1067
1069 if size < -1:
1068 if size < -1:
1070 raise ValueError("cannot read negative amounts less than -1")
1069 raise ValueError("cannot read negative amounts less than -1")
1071
1070
1072 if self._finished_output or size == 0:
1071 if self._finished_output or size == 0:
1073 return b""
1072 return b""
1074
1073
1075 # -1 returns arbitrary number of bytes.
1074 # -1 returns arbitrary number of bytes.
1076 if size == -1:
1075 if size == -1:
1077 size = COMPRESSION_RECOMMENDED_OUTPUT_SIZE
1076 size = COMPRESSION_RECOMMENDED_OUTPUT_SIZE
1078
1077
1079 dst_buffer = ffi.new("char[]", size)
1078 dst_buffer = ffi.new("char[]", size)
1080 out_buffer = ffi.new("ZSTD_outBuffer *")
1079 out_buffer = ffi.new("ZSTD_outBuffer *")
1081 out_buffer.dst = dst_buffer
1080 out_buffer.dst = dst_buffer
1082 out_buffer.size = size
1081 out_buffer.size = size
1083 out_buffer.pos = 0
1082 out_buffer.pos = 0
1084
1083
1085 # read1() dictates that we can perform at most 1 call to the
1084 # read1() dictates that we can perform at most 1 call to the
1086 # underlying stream to get input. However, we can't satisfy this
1085 # underlying stream to get input. However, we can't satisfy this
1087 # restriction with compression because not all input generates output.
1086 # restriction with compression because not all input generates output.
1088 # It is possible to perform a block flush in order to ensure output.
1087 # It is possible to perform a block flush in order to ensure output.
1089 # But this may not be desirable behavior. So we allow multiple read()
1088 # But this may not be desirable behavior. So we allow multiple read()
1090 # to the underlying stream. But unlike read(), we stop once we have
1089 # to the underlying stream. But unlike read(), we stop once we have
1091 # any output.
1090 # any output.
1092
1091
1093 self._compress_into_buffer(out_buffer)
1092 self._compress_into_buffer(out_buffer)
1094 if out_buffer.pos:
1093 if out_buffer.pos:
1095 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
1094 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
1096
1095
1097 while not self._finished_input:
1096 while not self._finished_input:
1098 self._read_input()
1097 self._read_input()
1099
1098
1100 # If we've filled the output buffer, return immediately.
1099 # If we've filled the output buffer, return immediately.
1101 if self._compress_into_buffer(out_buffer):
1100 if self._compress_into_buffer(out_buffer):
1102 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
1101 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
1103
1102
1104 # If we've populated the output buffer and we're not at EOF,
1103 # If we've populated the output buffer and we're not at EOF,
1105 # also return, as we've satisfied the read1() limits.
1104 # also return, as we've satisfied the read1() limits.
1106 if out_buffer.pos and not self._finished_input:
1105 if out_buffer.pos and not self._finished_input:
1107 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
1106 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
1108
1107
1109 # Else if we're at EOS and we have room left in the buffer,
1108 # Else if we're at EOS and we have room left in the buffer,
1110 # fall through to below and try to add more data to the output.
1109 # fall through to below and try to add more data to the output.
1111
1110
1112 # EOF.
1111 # EOF.
1113 old_pos = out_buffer.pos
1112 old_pos = out_buffer.pos
1114
1113
1115 zresult = lib.ZSTD_compressStream2(
1114 zresult = lib.ZSTD_compressStream2(
1116 self._compressor._cctx, out_buffer, self._in_buffer, lib.ZSTD_e_end
1115 self._compressor._cctx, out_buffer, self._in_buffer, lib.ZSTD_e_end
1117 )
1116 )
1118
1117
1119 self._bytes_compressed += out_buffer.pos - old_pos
1118 self._bytes_compressed += out_buffer.pos - old_pos
1120
1119
1121 if lib.ZSTD_isError(zresult):
1120 if lib.ZSTD_isError(zresult):
1122 raise ZstdError(
1121 raise ZstdError(
1123 "error ending compression stream: %s" % _zstd_error(zresult)
1122 "error ending compression stream: %s" % _zstd_error(zresult)
1124 )
1123 )
1125
1124
1126 if zresult == 0:
1125 if zresult == 0:
1127 self._finished_output = True
1126 self._finished_output = True
1128
1127
1129 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
1128 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
1130
1129
1131 def readinto(self, b):
1130 def readinto(self, b):
1132 if self._closed:
1131 if self._closed:
1133 raise ValueError("stream is closed")
1132 raise ValueError("stream is closed")
1134
1133
1135 if self._finished_output:
1134 if self._finished_output:
1136 return 0
1135 return 0
1137
1136
1138 # TODO use writable=True once we require CFFI >= 1.12.
1137 # TODO use writable=True once we require CFFI >= 1.12.
1139 dest_buffer = ffi.from_buffer(b)
1138 dest_buffer = ffi.from_buffer(b)
1140 ffi.memmove(b, b"", 0)
1139 ffi.memmove(b, b"", 0)
1141 out_buffer = ffi.new("ZSTD_outBuffer *")
1140 out_buffer = ffi.new("ZSTD_outBuffer *")
1142 out_buffer.dst = dest_buffer
1141 out_buffer.dst = dest_buffer
1143 out_buffer.size = len(dest_buffer)
1142 out_buffer.size = len(dest_buffer)
1144 out_buffer.pos = 0
1143 out_buffer.pos = 0
1145
1144
1146 if self._compress_into_buffer(out_buffer):
1145 if self._compress_into_buffer(out_buffer):
1147 return out_buffer.pos
1146 return out_buffer.pos
1148
1147
1149 while not self._finished_input:
1148 while not self._finished_input:
1150 self._read_input()
1149 self._read_input()
1151 if self._compress_into_buffer(out_buffer):
1150 if self._compress_into_buffer(out_buffer):
1152 return out_buffer.pos
1151 return out_buffer.pos
1153
1152
1154 # EOF.
1153 # EOF.
1155 old_pos = out_buffer.pos
1154 old_pos = out_buffer.pos
1156 zresult = lib.ZSTD_compressStream2(
1155 zresult = lib.ZSTD_compressStream2(
1157 self._compressor._cctx, out_buffer, self._in_buffer, lib.ZSTD_e_end
1156 self._compressor._cctx, out_buffer, self._in_buffer, lib.ZSTD_e_end
1158 )
1157 )
1159
1158
1160 self._bytes_compressed += out_buffer.pos - old_pos
1159 self._bytes_compressed += out_buffer.pos - old_pos
1161
1160
1162 if lib.ZSTD_isError(zresult):
1161 if lib.ZSTD_isError(zresult):
1163 raise ZstdError(
1162 raise ZstdError(
1164 "error ending compression stream: %s", _zstd_error(zresult)
1163 "error ending compression stream: %s", _zstd_error(zresult)
1165 )
1164 )
1166
1165
1167 if zresult == 0:
1166 if zresult == 0:
1168 self._finished_output = True
1167 self._finished_output = True
1169
1168
1170 return out_buffer.pos
1169 return out_buffer.pos
1171
1170
1172 def readinto1(self, b):
1171 def readinto1(self, b):
1173 if self._closed:
1172 if self._closed:
1174 raise ValueError("stream is closed")
1173 raise ValueError("stream is closed")
1175
1174
1176 if self._finished_output:
1175 if self._finished_output:
1177 return 0
1176 return 0
1178
1177
1179 # TODO use writable=True once we require CFFI >= 1.12.
1178 # TODO use writable=True once we require CFFI >= 1.12.
1180 dest_buffer = ffi.from_buffer(b)
1179 dest_buffer = ffi.from_buffer(b)
1181 ffi.memmove(b, b"", 0)
1180 ffi.memmove(b, b"", 0)
1182
1181
1183 out_buffer = ffi.new("ZSTD_outBuffer *")
1182 out_buffer = ffi.new("ZSTD_outBuffer *")
1184 out_buffer.dst = dest_buffer
1183 out_buffer.dst = dest_buffer
1185 out_buffer.size = len(dest_buffer)
1184 out_buffer.size = len(dest_buffer)
1186 out_buffer.pos = 0
1185 out_buffer.pos = 0
1187
1186
1188 self._compress_into_buffer(out_buffer)
1187 self._compress_into_buffer(out_buffer)
1189 if out_buffer.pos:
1188 if out_buffer.pos:
1190 return out_buffer.pos
1189 return out_buffer.pos
1191
1190
1192 while not self._finished_input:
1191 while not self._finished_input:
1193 self._read_input()
1192 self._read_input()
1194
1193
1195 if self._compress_into_buffer(out_buffer):
1194 if self._compress_into_buffer(out_buffer):
1196 return out_buffer.pos
1195 return out_buffer.pos
1197
1196
1198 if out_buffer.pos and not self._finished_input:
1197 if out_buffer.pos and not self._finished_input:
1199 return out_buffer.pos
1198 return out_buffer.pos
1200
1199
1201 # EOF.
1200 # EOF.
1202 old_pos = out_buffer.pos
1201 old_pos = out_buffer.pos
1203
1202
1204 zresult = lib.ZSTD_compressStream2(
1203 zresult = lib.ZSTD_compressStream2(
1205 self._compressor._cctx, out_buffer, self._in_buffer, lib.ZSTD_e_end
1204 self._compressor._cctx, out_buffer, self._in_buffer, lib.ZSTD_e_end
1206 )
1205 )
1207
1206
1208 self._bytes_compressed += out_buffer.pos - old_pos
1207 self._bytes_compressed += out_buffer.pos - old_pos
1209
1208
1210 if lib.ZSTD_isError(zresult):
1209 if lib.ZSTD_isError(zresult):
1211 raise ZstdError(
1210 raise ZstdError(
1212 "error ending compression stream: %s" % _zstd_error(zresult)
1211 "error ending compression stream: %s" % _zstd_error(zresult)
1213 )
1212 )
1214
1213
1215 if zresult == 0:
1214 if zresult == 0:
1216 self._finished_output = True
1215 self._finished_output = True
1217
1216
1218 return out_buffer.pos
1217 return out_buffer.pos
1219
1218
1220
1219
1221 class ZstdCompressor(object):
1220 class ZstdCompressor(object):
1222 def __init__(
1221 def __init__(
1223 self,
1222 self,
1224 level=3,
1223 level=3,
1225 dict_data=None,
1224 dict_data=None,
1226 compression_params=None,
1225 compression_params=None,
1227 write_checksum=None,
1226 write_checksum=None,
1228 write_content_size=None,
1227 write_content_size=None,
1229 write_dict_id=None,
1228 write_dict_id=None,
1230 threads=0,
1229 threads=0,
1231 ):
1230 ):
1232 if level > lib.ZSTD_maxCLevel():
1231 if level > lib.ZSTD_maxCLevel():
1233 raise ValueError(
1232 raise ValueError(
1234 "level must be less than %d" % lib.ZSTD_maxCLevel()
1233 "level must be less than %d" % lib.ZSTD_maxCLevel()
1235 )
1234 )
1236
1235
1237 if threads < 0:
1236 if threads < 0:
1238 threads = _cpu_count()
1237 threads = _cpu_count()
1239
1238
1240 if compression_params and write_checksum is not None:
1239 if compression_params and write_checksum is not None:
1241 raise ValueError(
1240 raise ValueError(
1242 "cannot define compression_params and " "write_checksum"
1241 "cannot define compression_params and " "write_checksum"
1243 )
1242 )
1244
1243
1245 if compression_params and write_content_size is not None:
1244 if compression_params and write_content_size is not None:
1246 raise ValueError(
1245 raise ValueError(
1247 "cannot define compression_params and " "write_content_size"
1246 "cannot define compression_params and " "write_content_size"
1248 )
1247 )
1249
1248
1250 if compression_params and write_dict_id is not None:
1249 if compression_params and write_dict_id is not None:
1251 raise ValueError(
1250 raise ValueError(
1252 "cannot define compression_params and " "write_dict_id"
1251 "cannot define compression_params and " "write_dict_id"
1253 )
1252 )
1254
1253
1255 if compression_params and threads:
1254 if compression_params and threads:
1256 raise ValueError("cannot define compression_params and threads")
1255 raise ValueError("cannot define compression_params and threads")
1257
1256
1258 if compression_params:
1257 if compression_params:
1259 self._params = _make_cctx_params(compression_params)
1258 self._params = _make_cctx_params(compression_params)
1260 else:
1259 else:
1261 if write_dict_id is None:
1260 if write_dict_id is None:
1262 write_dict_id = True
1261 write_dict_id = True
1263
1262
1264 params = lib.ZSTD_createCCtxParams()
1263 params = lib.ZSTD_createCCtxParams()
1265 if params == ffi.NULL:
1264 if params == ffi.NULL:
1266 raise MemoryError()
1265 raise MemoryError()
1267
1266
1268 self._params = ffi.gc(params, lib.ZSTD_freeCCtxParams)
1267 self._params = ffi.gc(params, lib.ZSTD_freeCCtxParams)
1269
1268
1270 _set_compression_parameter(
1269 _set_compression_parameter(
1271 self._params, lib.ZSTD_c_compressionLevel, level
1270 self._params, lib.ZSTD_c_compressionLevel, level
1272 )
1271 )
1273
1272
1274 _set_compression_parameter(
1273 _set_compression_parameter(
1275 self._params,
1274 self._params,
1276 lib.ZSTD_c_contentSizeFlag,
1275 lib.ZSTD_c_contentSizeFlag,
1277 write_content_size if write_content_size is not None else 1,
1276 write_content_size if write_content_size is not None else 1,
1278 )
1277 )
1279
1278
1280 _set_compression_parameter(
1279 _set_compression_parameter(
1281 self._params,
1280 self._params,
1282 lib.ZSTD_c_checksumFlag,
1281 lib.ZSTD_c_checksumFlag,
1283 1 if write_checksum else 0,
1282 1 if write_checksum else 0,
1284 )
1283 )
1285
1284
1286 _set_compression_parameter(
1285 _set_compression_parameter(
1287 self._params, lib.ZSTD_c_dictIDFlag, 1 if write_dict_id else 0
1286 self._params, lib.ZSTD_c_dictIDFlag, 1 if write_dict_id else 0
1288 )
1287 )
1289
1288
1290 if threads:
1289 if threads:
1291 _set_compression_parameter(
1290 _set_compression_parameter(
1292 self._params, lib.ZSTD_c_nbWorkers, threads
1291 self._params, lib.ZSTD_c_nbWorkers, threads
1293 )
1292 )
1294
1293
1295 cctx = lib.ZSTD_createCCtx()
1294 cctx = lib.ZSTD_createCCtx()
1296 if cctx == ffi.NULL:
1295 if cctx == ffi.NULL:
1297 raise MemoryError()
1296 raise MemoryError()
1298
1297
1299 self._cctx = cctx
1298 self._cctx = cctx
1300 self._dict_data = dict_data
1299 self._dict_data = dict_data
1301
1300
1302 # We defer setting up garbage collection until after calling
1301 # We defer setting up garbage collection until after calling
1303 # _setup_cctx() to ensure the memory size estimate is more accurate.
1302 # _setup_cctx() to ensure the memory size estimate is more accurate.
1304 try:
1303 try:
1305 self._setup_cctx()
1304 self._setup_cctx()
1306 finally:
1305 finally:
1307 self._cctx = ffi.gc(
1306 self._cctx = ffi.gc(
1308 cctx, lib.ZSTD_freeCCtx, size=lib.ZSTD_sizeof_CCtx(cctx)
1307 cctx, lib.ZSTD_freeCCtx, size=lib.ZSTD_sizeof_CCtx(cctx)
1309 )
1308 )
1310
1309
1311 def _setup_cctx(self):
1310 def _setup_cctx(self):
1312 zresult = lib.ZSTD_CCtx_setParametersUsingCCtxParams(
1311 zresult = lib.ZSTD_CCtx_setParametersUsingCCtxParams(
1313 self._cctx, self._params
1312 self._cctx, self._params
1314 )
1313 )
1315 if lib.ZSTD_isError(zresult):
1314 if lib.ZSTD_isError(zresult):
1316 raise ZstdError(
1315 raise ZstdError(
1317 "could not set compression parameters: %s"
1316 "could not set compression parameters: %s"
1318 % _zstd_error(zresult)
1317 % _zstd_error(zresult)
1319 )
1318 )
1320
1319
1321 dict_data = self._dict_data
1320 dict_data = self._dict_data
1322
1321
1323 if dict_data:
1322 if dict_data:
1324 if dict_data._cdict:
1323 if dict_data._cdict:
1325 zresult = lib.ZSTD_CCtx_refCDict(self._cctx, dict_data._cdict)
1324 zresult = lib.ZSTD_CCtx_refCDict(self._cctx, dict_data._cdict)
1326 else:
1325 else:
1327 zresult = lib.ZSTD_CCtx_loadDictionary_advanced(
1326 zresult = lib.ZSTD_CCtx_loadDictionary_advanced(
1328 self._cctx,
1327 self._cctx,
1329 dict_data.as_bytes(),
1328 dict_data.as_bytes(),
1330 len(dict_data),
1329 len(dict_data),
1331 lib.ZSTD_dlm_byRef,
1330 lib.ZSTD_dlm_byRef,
1332 dict_data._dict_type,
1331 dict_data._dict_type,
1333 )
1332 )
1334
1333
1335 if lib.ZSTD_isError(zresult):
1334 if lib.ZSTD_isError(zresult):
1336 raise ZstdError(
1335 raise ZstdError(
1337 "could not load compression dictionary: %s"
1336 "could not load compression dictionary: %s"
1338 % _zstd_error(zresult)
1337 % _zstd_error(zresult)
1339 )
1338 )
1340
1339
1341 def memory_size(self):
1340 def memory_size(self):
1342 return lib.ZSTD_sizeof_CCtx(self._cctx)
1341 return lib.ZSTD_sizeof_CCtx(self._cctx)
1343
1342
1344 def compress(self, data):
1343 def compress(self, data):
1345 lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only)
1344 lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only)
1346
1345
1347 data_buffer = ffi.from_buffer(data)
1346 data_buffer = ffi.from_buffer(data)
1348
1347
1349 dest_size = lib.ZSTD_compressBound(len(data_buffer))
1348 dest_size = lib.ZSTD_compressBound(len(data_buffer))
1350 out = new_nonzero("char[]", dest_size)
1349 out = new_nonzero("char[]", dest_size)
1351
1350
1352 zresult = lib.ZSTD_CCtx_setPledgedSrcSize(self._cctx, len(data_buffer))
1351 zresult = lib.ZSTD_CCtx_setPledgedSrcSize(self._cctx, len(data_buffer))
1353 if lib.ZSTD_isError(zresult):
1352 if lib.ZSTD_isError(zresult):
1354 raise ZstdError(
1353 raise ZstdError(
1355 "error setting source size: %s" % _zstd_error(zresult)
1354 "error setting source size: %s" % _zstd_error(zresult)
1356 )
1355 )
1357
1356
1358 out_buffer = ffi.new("ZSTD_outBuffer *")
1357 out_buffer = ffi.new("ZSTD_outBuffer *")
1359 in_buffer = ffi.new("ZSTD_inBuffer *")
1358 in_buffer = ffi.new("ZSTD_inBuffer *")
1360
1359
1361 out_buffer.dst = out
1360 out_buffer.dst = out
1362 out_buffer.size = dest_size
1361 out_buffer.size = dest_size
1363 out_buffer.pos = 0
1362 out_buffer.pos = 0
1364
1363
1365 in_buffer.src = data_buffer
1364 in_buffer.src = data_buffer
1366 in_buffer.size = len(data_buffer)
1365 in_buffer.size = len(data_buffer)
1367 in_buffer.pos = 0
1366 in_buffer.pos = 0
1368
1367
1369 zresult = lib.ZSTD_compressStream2(
1368 zresult = lib.ZSTD_compressStream2(
1370 self._cctx, out_buffer, in_buffer, lib.ZSTD_e_end
1369 self._cctx, out_buffer, in_buffer, lib.ZSTD_e_end
1371 )
1370 )
1372
1371
1373 if lib.ZSTD_isError(zresult):
1372 if lib.ZSTD_isError(zresult):
1374 raise ZstdError("cannot compress: %s" % _zstd_error(zresult))
1373 raise ZstdError("cannot compress: %s" % _zstd_error(zresult))
1375 elif zresult:
1374 elif zresult:
1376 raise ZstdError("unexpected partial frame flush")
1375 raise ZstdError("unexpected partial frame flush")
1377
1376
1378 return ffi.buffer(out, out_buffer.pos)[:]
1377 return ffi.buffer(out, out_buffer.pos)[:]
1379
1378
1380 def compressobj(self, size=-1):
1379 def compressobj(self, size=-1):
1381 lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only)
1380 lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only)
1382
1381
1383 if size < 0:
1382 if size < 0:
1384 size = lib.ZSTD_CONTENTSIZE_UNKNOWN
1383 size = lib.ZSTD_CONTENTSIZE_UNKNOWN
1385
1384
1386 zresult = lib.ZSTD_CCtx_setPledgedSrcSize(self._cctx, size)
1385 zresult = lib.ZSTD_CCtx_setPledgedSrcSize(self._cctx, size)
1387 if lib.ZSTD_isError(zresult):
1386 if lib.ZSTD_isError(zresult):
1388 raise ZstdError(
1387 raise ZstdError(
1389 "error setting source size: %s" % _zstd_error(zresult)
1388 "error setting source size: %s" % _zstd_error(zresult)
1390 )
1389 )
1391
1390
1392 cobj = ZstdCompressionObj()
1391 cobj = ZstdCompressionObj()
1393 cobj._out = ffi.new("ZSTD_outBuffer *")
1392 cobj._out = ffi.new("ZSTD_outBuffer *")
1394 cobj._dst_buffer = ffi.new(
1393 cobj._dst_buffer = ffi.new(
1395 "char[]", COMPRESSION_RECOMMENDED_OUTPUT_SIZE
1394 "char[]", COMPRESSION_RECOMMENDED_OUTPUT_SIZE
1396 )
1395 )
1397 cobj._out.dst = cobj._dst_buffer
1396 cobj._out.dst = cobj._dst_buffer
1398 cobj._out.size = COMPRESSION_RECOMMENDED_OUTPUT_SIZE
1397 cobj._out.size = COMPRESSION_RECOMMENDED_OUTPUT_SIZE
1399 cobj._out.pos = 0
1398 cobj._out.pos = 0
1400 cobj._compressor = self
1399 cobj._compressor = self
1401 cobj._finished = False
1400 cobj._finished = False
1402
1401
1403 return cobj
1402 return cobj
1404
1403
1405 def chunker(self, size=-1, chunk_size=COMPRESSION_RECOMMENDED_OUTPUT_SIZE):
1404 def chunker(self, size=-1, chunk_size=COMPRESSION_RECOMMENDED_OUTPUT_SIZE):
1406 lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only)
1405 lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only)
1407
1406
1408 if size < 0:
1407 if size < 0:
1409 size = lib.ZSTD_CONTENTSIZE_UNKNOWN
1408 size = lib.ZSTD_CONTENTSIZE_UNKNOWN
1410
1409
1411 zresult = lib.ZSTD_CCtx_setPledgedSrcSize(self._cctx, size)
1410 zresult = lib.ZSTD_CCtx_setPledgedSrcSize(self._cctx, size)
1412 if lib.ZSTD_isError(zresult):
1411 if lib.ZSTD_isError(zresult):
1413 raise ZstdError(
1412 raise ZstdError(
1414 "error setting source size: %s" % _zstd_error(zresult)
1413 "error setting source size: %s" % _zstd_error(zresult)
1415 )
1414 )
1416
1415
1417 return ZstdCompressionChunker(self, chunk_size=chunk_size)
1416 return ZstdCompressionChunker(self, chunk_size=chunk_size)
1418
1417
1419 def copy_stream(
1418 def copy_stream(
1420 self,
1419 self,
1421 ifh,
1420 ifh,
1422 ofh,
1421 ofh,
1423 size=-1,
1422 size=-1,
1424 read_size=COMPRESSION_RECOMMENDED_INPUT_SIZE,
1423 read_size=COMPRESSION_RECOMMENDED_INPUT_SIZE,
1425 write_size=COMPRESSION_RECOMMENDED_OUTPUT_SIZE,
1424 write_size=COMPRESSION_RECOMMENDED_OUTPUT_SIZE,
1426 ):
1425 ):
1427
1426
1428 if not hasattr(ifh, "read"):
1427 if not hasattr(ifh, "read"):
1429 raise ValueError("first argument must have a read() method")
1428 raise ValueError("first argument must have a read() method")
1430 if not hasattr(ofh, "write"):
1429 if not hasattr(ofh, "write"):
1431 raise ValueError("second argument must have a write() method")
1430 raise ValueError("second argument must have a write() method")
1432
1431
1433 lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only)
1432 lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only)
1434
1433
1435 if size < 0:
1434 if size < 0:
1436 size = lib.ZSTD_CONTENTSIZE_UNKNOWN
1435 size = lib.ZSTD_CONTENTSIZE_UNKNOWN
1437
1436
1438 zresult = lib.ZSTD_CCtx_setPledgedSrcSize(self._cctx, size)
1437 zresult = lib.ZSTD_CCtx_setPledgedSrcSize(self._cctx, size)
1439 if lib.ZSTD_isError(zresult):
1438 if lib.ZSTD_isError(zresult):
1440 raise ZstdError(
1439 raise ZstdError(
1441 "error setting source size: %s" % _zstd_error(zresult)
1440 "error setting source size: %s" % _zstd_error(zresult)
1442 )
1441 )
1443
1442
1444 in_buffer = ffi.new("ZSTD_inBuffer *")
1443 in_buffer = ffi.new("ZSTD_inBuffer *")
1445 out_buffer = ffi.new("ZSTD_outBuffer *")
1444 out_buffer = ffi.new("ZSTD_outBuffer *")
1446
1445
1447 dst_buffer = ffi.new("char[]", write_size)
1446 dst_buffer = ffi.new("char[]", write_size)
1448 out_buffer.dst = dst_buffer
1447 out_buffer.dst = dst_buffer
1449 out_buffer.size = write_size
1448 out_buffer.size = write_size
1450 out_buffer.pos = 0
1449 out_buffer.pos = 0
1451
1450
1452 total_read, total_write = 0, 0
1451 total_read, total_write = 0, 0
1453
1452
1454 while True:
1453 while True:
1455 data = ifh.read(read_size)
1454 data = ifh.read(read_size)
1456 if not data:
1455 if not data:
1457 break
1456 break
1458
1457
1459 data_buffer = ffi.from_buffer(data)
1458 data_buffer = ffi.from_buffer(data)
1460 total_read += len(data_buffer)
1459 total_read += len(data_buffer)
1461 in_buffer.src = data_buffer
1460 in_buffer.src = data_buffer
1462 in_buffer.size = len(data_buffer)
1461 in_buffer.size = len(data_buffer)
1463 in_buffer.pos = 0
1462 in_buffer.pos = 0
1464
1463
1465 while in_buffer.pos < in_buffer.size:
1464 while in_buffer.pos < in_buffer.size:
1466 zresult = lib.ZSTD_compressStream2(
1465 zresult = lib.ZSTD_compressStream2(
1467 self._cctx, out_buffer, in_buffer, lib.ZSTD_e_continue
1466 self._cctx, out_buffer, in_buffer, lib.ZSTD_e_continue
1468 )
1467 )
1469 if lib.ZSTD_isError(zresult):
1468 if lib.ZSTD_isError(zresult):
1470 raise ZstdError(
1469 raise ZstdError(
1471 "zstd compress error: %s" % _zstd_error(zresult)
1470 "zstd compress error: %s" % _zstd_error(zresult)
1472 )
1471 )
1473
1472
1474 if out_buffer.pos:
1473 if out_buffer.pos:
1475 ofh.write(ffi.buffer(out_buffer.dst, out_buffer.pos))
1474 ofh.write(ffi.buffer(out_buffer.dst, out_buffer.pos))
1476 total_write += out_buffer.pos
1475 total_write += out_buffer.pos
1477 out_buffer.pos = 0
1476 out_buffer.pos = 0
1478
1477
1479 # We've finished reading. Flush the compressor.
1478 # We've finished reading. Flush the compressor.
1480 while True:
1479 while True:
1481 zresult = lib.ZSTD_compressStream2(
1480 zresult = lib.ZSTD_compressStream2(
1482 self._cctx, out_buffer, in_buffer, lib.ZSTD_e_end
1481 self._cctx, out_buffer, in_buffer, lib.ZSTD_e_end
1483 )
1482 )
1484 if lib.ZSTD_isError(zresult):
1483 if lib.ZSTD_isError(zresult):
1485 raise ZstdError(
1484 raise ZstdError(
1486 "error ending compression stream: %s" % _zstd_error(zresult)
1485 "error ending compression stream: %s" % _zstd_error(zresult)
1487 )
1486 )
1488
1487
1489 if out_buffer.pos:
1488 if out_buffer.pos:
1490 ofh.write(ffi.buffer(out_buffer.dst, out_buffer.pos))
1489 ofh.write(ffi.buffer(out_buffer.dst, out_buffer.pos))
1491 total_write += out_buffer.pos
1490 total_write += out_buffer.pos
1492 out_buffer.pos = 0
1491 out_buffer.pos = 0
1493
1492
1494 if zresult == 0:
1493 if zresult == 0:
1495 break
1494 break
1496
1495
1497 return total_read, total_write
1496 return total_read, total_write
1498
1497
1499 def stream_reader(
1498 def stream_reader(
1500 self, source, size=-1, read_size=COMPRESSION_RECOMMENDED_INPUT_SIZE
1499 self, source, size=-1, read_size=COMPRESSION_RECOMMENDED_INPUT_SIZE
1501 ):
1500 ):
1502 lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only)
1501 lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only)
1503
1502
1504 try:
1503 try:
1505 size = len(source)
1504 size = len(source)
1506 except Exception:
1505 except Exception:
1507 pass
1506 pass
1508
1507
1509 if size < 0:
1508 if size < 0:
1510 size = lib.ZSTD_CONTENTSIZE_UNKNOWN
1509 size = lib.ZSTD_CONTENTSIZE_UNKNOWN
1511
1510
1512 zresult = lib.ZSTD_CCtx_setPledgedSrcSize(self._cctx, size)
1511 zresult = lib.ZSTD_CCtx_setPledgedSrcSize(self._cctx, size)
1513 if lib.ZSTD_isError(zresult):
1512 if lib.ZSTD_isError(zresult):
1514 raise ZstdError(
1513 raise ZstdError(
1515 "error setting source size: %s" % _zstd_error(zresult)
1514 "error setting source size: %s" % _zstd_error(zresult)
1516 )
1515 )
1517
1516
1518 return ZstdCompressionReader(self, source, read_size)
1517 return ZstdCompressionReader(self, source, read_size)
1519
1518
1520 def stream_writer(
1519 def stream_writer(
1521 self,
1520 self,
1522 writer,
1521 writer,
1523 size=-1,
1522 size=-1,
1524 write_size=COMPRESSION_RECOMMENDED_OUTPUT_SIZE,
1523 write_size=COMPRESSION_RECOMMENDED_OUTPUT_SIZE,
1525 write_return_read=False,
1524 write_return_read=False,
1526 ):
1525 ):
1527
1526
1528 if not hasattr(writer, "write"):
1527 if not hasattr(writer, "write"):
1529 raise ValueError("must pass an object with a write() method")
1528 raise ValueError("must pass an object with a write() method")
1530
1529
1531 lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only)
1530 lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only)
1532
1531
1533 if size < 0:
1532 if size < 0:
1534 size = lib.ZSTD_CONTENTSIZE_UNKNOWN
1533 size = lib.ZSTD_CONTENTSIZE_UNKNOWN
1535
1534
1536 return ZstdCompressionWriter(
1535 return ZstdCompressionWriter(
1537 self, writer, size, write_size, write_return_read
1536 self, writer, size, write_size, write_return_read
1538 )
1537 )
1539
1538
1540 write_to = stream_writer
1539 write_to = stream_writer
1541
1540
1542 def read_to_iter(
1541 def read_to_iter(
1543 self,
1542 self,
1544 reader,
1543 reader,
1545 size=-1,
1544 size=-1,
1546 read_size=COMPRESSION_RECOMMENDED_INPUT_SIZE,
1545 read_size=COMPRESSION_RECOMMENDED_INPUT_SIZE,
1547 write_size=COMPRESSION_RECOMMENDED_OUTPUT_SIZE,
1546 write_size=COMPRESSION_RECOMMENDED_OUTPUT_SIZE,
1548 ):
1547 ):
1549 if hasattr(reader, "read"):
1548 if hasattr(reader, "read"):
1550 have_read = True
1549 have_read = True
1551 elif hasattr(reader, "__getitem__"):
1550 elif hasattr(reader, "__getitem__"):
1552 have_read = False
1551 have_read = False
1553 buffer_offset = 0
1552 buffer_offset = 0
1554 size = len(reader)
1553 size = len(reader)
1555 else:
1554 else:
1556 raise ValueError(
1555 raise ValueError(
1557 "must pass an object with a read() method or "
1556 "must pass an object with a read() method or "
1558 "conforms to buffer protocol"
1557 "conforms to buffer protocol"
1559 )
1558 )
1560
1559
1561 lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only)
1560 lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only)
1562
1561
1563 if size < 0:
1562 if size < 0:
1564 size = lib.ZSTD_CONTENTSIZE_UNKNOWN
1563 size = lib.ZSTD_CONTENTSIZE_UNKNOWN
1565
1564
1566 zresult = lib.ZSTD_CCtx_setPledgedSrcSize(self._cctx, size)
1565 zresult = lib.ZSTD_CCtx_setPledgedSrcSize(self._cctx, size)
1567 if lib.ZSTD_isError(zresult):
1566 if lib.ZSTD_isError(zresult):
1568 raise ZstdError(
1567 raise ZstdError(
1569 "error setting source size: %s" % _zstd_error(zresult)
1568 "error setting source size: %s" % _zstd_error(zresult)
1570 )
1569 )
1571
1570
1572 in_buffer = ffi.new("ZSTD_inBuffer *")
1571 in_buffer = ffi.new("ZSTD_inBuffer *")
1573 out_buffer = ffi.new("ZSTD_outBuffer *")
1572 out_buffer = ffi.new("ZSTD_outBuffer *")
1574
1573
1575 in_buffer.src = ffi.NULL
1574 in_buffer.src = ffi.NULL
1576 in_buffer.size = 0
1575 in_buffer.size = 0
1577 in_buffer.pos = 0
1576 in_buffer.pos = 0
1578
1577
1579 dst_buffer = ffi.new("char[]", write_size)
1578 dst_buffer = ffi.new("char[]", write_size)
1580 out_buffer.dst = dst_buffer
1579 out_buffer.dst = dst_buffer
1581 out_buffer.size = write_size
1580 out_buffer.size = write_size
1582 out_buffer.pos = 0
1581 out_buffer.pos = 0
1583
1582
1584 while True:
1583 while True:
1585 # We should never have output data sitting around after a previous
1584 # We should never have output data sitting around after a previous
1586 # iteration.
1585 # iteration.
1587 assert out_buffer.pos == 0
1586 assert out_buffer.pos == 0
1588
1587
1589 # Collect input data.
1588 # Collect input data.
1590 if have_read:
1589 if have_read:
1591 read_result = reader.read(read_size)
1590 read_result = reader.read(read_size)
1592 else:
1591 else:
1593 remaining = len(reader) - buffer_offset
1592 remaining = len(reader) - buffer_offset
1594 slice_size = min(remaining, read_size)
1593 slice_size = min(remaining, read_size)
1595 read_result = reader[buffer_offset : buffer_offset + slice_size]
1594 read_result = reader[buffer_offset : buffer_offset + slice_size]
1596 buffer_offset += slice_size
1595 buffer_offset += slice_size
1597
1596
1598 # No new input data. Break out of the read loop.
1597 # No new input data. Break out of the read loop.
1599 if not read_result:
1598 if not read_result:
1600 break
1599 break
1601
1600
1602 # Feed all read data into the compressor and emit output until
1601 # Feed all read data into the compressor and emit output until
1603 # exhausted.
1602 # exhausted.
1604 read_buffer = ffi.from_buffer(read_result)
1603 read_buffer = ffi.from_buffer(read_result)
1605 in_buffer.src = read_buffer
1604 in_buffer.src = read_buffer
1606 in_buffer.size = len(read_buffer)
1605 in_buffer.size = len(read_buffer)
1607 in_buffer.pos = 0
1606 in_buffer.pos = 0
1608
1607
1609 while in_buffer.pos < in_buffer.size:
1608 while in_buffer.pos < in_buffer.size:
1610 zresult = lib.ZSTD_compressStream2(
1609 zresult = lib.ZSTD_compressStream2(
1611 self._cctx, out_buffer, in_buffer, lib.ZSTD_e_continue
1610 self._cctx, out_buffer, in_buffer, lib.ZSTD_e_continue
1612 )
1611 )
1613 if lib.ZSTD_isError(zresult):
1612 if lib.ZSTD_isError(zresult):
1614 raise ZstdError(
1613 raise ZstdError(
1615 "zstd compress error: %s" % _zstd_error(zresult)
1614 "zstd compress error: %s" % _zstd_error(zresult)
1616 )
1615 )
1617
1616
1618 if out_buffer.pos:
1617 if out_buffer.pos:
1619 data = ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
1618 data = ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
1620 out_buffer.pos = 0
1619 out_buffer.pos = 0
1621 yield data
1620 yield data
1622
1621
1623 assert out_buffer.pos == 0
1622 assert out_buffer.pos == 0
1624
1623
1625 # And repeat the loop to collect more data.
1624 # And repeat the loop to collect more data.
1626 continue
1625 continue
1627
1626
1628 # If we get here, input is exhausted. End the stream and emit what
1627 # If we get here, input is exhausted. End the stream and emit what
1629 # remains.
1628 # remains.
1630 while True:
1629 while True:
1631 assert out_buffer.pos == 0
1630 assert out_buffer.pos == 0
1632 zresult = lib.ZSTD_compressStream2(
1631 zresult = lib.ZSTD_compressStream2(
1633 self._cctx, out_buffer, in_buffer, lib.ZSTD_e_end
1632 self._cctx, out_buffer, in_buffer, lib.ZSTD_e_end
1634 )
1633 )
1635 if lib.ZSTD_isError(zresult):
1634 if lib.ZSTD_isError(zresult):
1636 raise ZstdError(
1635 raise ZstdError(
1637 "error ending compression stream: %s" % _zstd_error(zresult)
1636 "error ending compression stream: %s" % _zstd_error(zresult)
1638 )
1637 )
1639
1638
1640 if out_buffer.pos:
1639 if out_buffer.pos:
1641 data = ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
1640 data = ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
1642 out_buffer.pos = 0
1641 out_buffer.pos = 0
1643 yield data
1642 yield data
1644
1643
1645 if zresult == 0:
1644 if zresult == 0:
1646 break
1645 break
1647
1646
1648 read_from = read_to_iter
1647 read_from = read_to_iter
1649
1648
1650 def frame_progression(self):
1649 def frame_progression(self):
1651 progression = lib.ZSTD_getFrameProgression(self._cctx)
1650 progression = lib.ZSTD_getFrameProgression(self._cctx)
1652
1651
1653 return progression.ingested, progression.consumed, progression.produced
1652 return progression.ingested, progression.consumed, progression.produced
1654
1653
1655
1654
1656 class FrameParameters(object):
1655 class FrameParameters(object):
1657 def __init__(self, fparams):
1656 def __init__(self, fparams):
1658 self.content_size = fparams.frameContentSize
1657 self.content_size = fparams.frameContentSize
1659 self.window_size = fparams.windowSize
1658 self.window_size = fparams.windowSize
1660 self.dict_id = fparams.dictID
1659 self.dict_id = fparams.dictID
1661 self.has_checksum = bool(fparams.checksumFlag)
1660 self.has_checksum = bool(fparams.checksumFlag)
1662
1661
1663
1662
1664 def frame_content_size(data):
1663 def frame_content_size(data):
1665 data_buffer = ffi.from_buffer(data)
1664 data_buffer = ffi.from_buffer(data)
1666
1665
1667 size = lib.ZSTD_getFrameContentSize(data_buffer, len(data_buffer))
1666 size = lib.ZSTD_getFrameContentSize(data_buffer, len(data_buffer))
1668
1667
1669 if size == lib.ZSTD_CONTENTSIZE_ERROR:
1668 if size == lib.ZSTD_CONTENTSIZE_ERROR:
1670 raise ZstdError("error when determining content size")
1669 raise ZstdError("error when determining content size")
1671 elif size == lib.ZSTD_CONTENTSIZE_UNKNOWN:
1670 elif size == lib.ZSTD_CONTENTSIZE_UNKNOWN:
1672 return -1
1671 return -1
1673 else:
1672 else:
1674 return size
1673 return size
1675
1674
1676
1675
1677 def frame_header_size(data):
1676 def frame_header_size(data):
1678 data_buffer = ffi.from_buffer(data)
1677 data_buffer = ffi.from_buffer(data)
1679
1678
1680 zresult = lib.ZSTD_frameHeaderSize(data_buffer, len(data_buffer))
1679 zresult = lib.ZSTD_frameHeaderSize(data_buffer, len(data_buffer))
1681 if lib.ZSTD_isError(zresult):
1680 if lib.ZSTD_isError(zresult):
1682 raise ZstdError(
1681 raise ZstdError(
1683 "could not determine frame header size: %s" % _zstd_error(zresult)
1682 "could not determine frame header size: %s" % _zstd_error(zresult)
1684 )
1683 )
1685
1684
1686 return zresult
1685 return zresult
1687
1686
1688
1687
1689 def get_frame_parameters(data):
1688 def get_frame_parameters(data):
1690 params = ffi.new("ZSTD_frameHeader *")
1689 params = ffi.new("ZSTD_frameHeader *")
1691
1690
1692 data_buffer = ffi.from_buffer(data)
1691 data_buffer = ffi.from_buffer(data)
1693 zresult = lib.ZSTD_getFrameHeader(params, data_buffer, len(data_buffer))
1692 zresult = lib.ZSTD_getFrameHeader(params, data_buffer, len(data_buffer))
1694 if lib.ZSTD_isError(zresult):
1693 if lib.ZSTD_isError(zresult):
1695 raise ZstdError(
1694 raise ZstdError(
1696 "cannot get frame parameters: %s" % _zstd_error(zresult)
1695 "cannot get frame parameters: %s" % _zstd_error(zresult)
1697 )
1696 )
1698
1697
1699 if zresult:
1698 if zresult:
1700 raise ZstdError(
1699 raise ZstdError(
1701 "not enough data for frame parameters; need %d bytes" % zresult
1700 "not enough data for frame parameters; need %d bytes" % zresult
1702 )
1701 )
1703
1702
1704 return FrameParameters(params[0])
1703 return FrameParameters(params[0])
1705
1704
1706
1705
1707 class ZstdCompressionDict(object):
1706 class ZstdCompressionDict(object):
1708 def __init__(self, data, dict_type=DICT_TYPE_AUTO, k=0, d=0):
1707 def __init__(self, data, dict_type=DICT_TYPE_AUTO, k=0, d=0):
1709 assert isinstance(data, bytes_type)
1708 assert isinstance(data, bytes_type)
1710 self._data = data
1709 self._data = data
1711 self.k = k
1710 self.k = k
1712 self.d = d
1711 self.d = d
1713
1712
1714 if dict_type not in (
1713 if dict_type not in (
1715 DICT_TYPE_AUTO,
1714 DICT_TYPE_AUTO,
1716 DICT_TYPE_RAWCONTENT,
1715 DICT_TYPE_RAWCONTENT,
1717 DICT_TYPE_FULLDICT,
1716 DICT_TYPE_FULLDICT,
1718 ):
1717 ):
1719 raise ValueError(
1718 raise ValueError(
1720 "invalid dictionary load mode: %d; must use "
1719 "invalid dictionary load mode: %d; must use "
1721 "DICT_TYPE_* constants"
1720 "DICT_TYPE_* constants"
1722 )
1721 )
1723
1722
1724 self._dict_type = dict_type
1723 self._dict_type = dict_type
1725 self._cdict = None
1724 self._cdict = None
1726
1725
1727 def __len__(self):
1726 def __len__(self):
1728 return len(self._data)
1727 return len(self._data)
1729
1728
1730 def dict_id(self):
1729 def dict_id(self):
1731 return int_type(lib.ZDICT_getDictID(self._data, len(self._data)))
1730 return int_type(lib.ZDICT_getDictID(self._data, len(self._data)))
1732
1731
1733 def as_bytes(self):
1732 def as_bytes(self):
1734 return self._data
1733 return self._data
1735
1734
1736 def precompute_compress(self, level=0, compression_params=None):
1735 def precompute_compress(self, level=0, compression_params=None):
1737 if level and compression_params:
1736 if level and compression_params:
1738 raise ValueError(
1737 raise ValueError(
1739 "must only specify one of level or " "compression_params"
1738 "must only specify one of level or " "compression_params"
1740 )
1739 )
1741
1740
1742 if not level and not compression_params:
1741 if not level and not compression_params:
1743 raise ValueError("must specify one of level or compression_params")
1742 raise ValueError("must specify one of level or compression_params")
1744
1743
1745 if level:
1744 if level:
1746 cparams = lib.ZSTD_getCParams(level, 0, len(self._data))
1745 cparams = lib.ZSTD_getCParams(level, 0, len(self._data))
1747 else:
1746 else:
1748 cparams = ffi.new("ZSTD_compressionParameters")
1747 cparams = ffi.new("ZSTD_compressionParameters")
1749 cparams.chainLog = compression_params.chain_log
1748 cparams.chainLog = compression_params.chain_log
1750 cparams.hashLog = compression_params.hash_log
1749 cparams.hashLog = compression_params.hash_log
1751 cparams.minMatch = compression_params.min_match
1750 cparams.minMatch = compression_params.min_match
1752 cparams.searchLog = compression_params.search_log
1751 cparams.searchLog = compression_params.search_log
1753 cparams.strategy = compression_params.compression_strategy
1752 cparams.strategy = compression_params.compression_strategy
1754 cparams.targetLength = compression_params.target_length
1753 cparams.targetLength = compression_params.target_length
1755 cparams.windowLog = compression_params.window_log
1754 cparams.windowLog = compression_params.window_log
1756
1755
1757 cdict = lib.ZSTD_createCDict_advanced(
1756 cdict = lib.ZSTD_createCDict_advanced(
1758 self._data,
1757 self._data,
1759 len(self._data),
1758 len(self._data),
1760 lib.ZSTD_dlm_byRef,
1759 lib.ZSTD_dlm_byRef,
1761 self._dict_type,
1760 self._dict_type,
1762 cparams,
1761 cparams,
1763 lib.ZSTD_defaultCMem,
1762 lib.ZSTD_defaultCMem,
1764 )
1763 )
1765 if cdict == ffi.NULL:
1764 if cdict == ffi.NULL:
1766 raise ZstdError("unable to precompute dictionary")
1765 raise ZstdError("unable to precompute dictionary")
1767
1766
1768 self._cdict = ffi.gc(
1767 self._cdict = ffi.gc(
1769 cdict, lib.ZSTD_freeCDict, size=lib.ZSTD_sizeof_CDict(cdict)
1768 cdict, lib.ZSTD_freeCDict, size=lib.ZSTD_sizeof_CDict(cdict)
1770 )
1769 )
1771
1770
1772 @property
1771 @property
1773 def _ddict(self):
1772 def _ddict(self):
1774 ddict = lib.ZSTD_createDDict_advanced(
1773 ddict = lib.ZSTD_createDDict_advanced(
1775 self._data,
1774 self._data,
1776 len(self._data),
1775 len(self._data),
1777 lib.ZSTD_dlm_byRef,
1776 lib.ZSTD_dlm_byRef,
1778 self._dict_type,
1777 self._dict_type,
1779 lib.ZSTD_defaultCMem,
1778 lib.ZSTD_defaultCMem,
1780 )
1779 )
1781
1780
1782 if ddict == ffi.NULL:
1781 if ddict == ffi.NULL:
1783 raise ZstdError("could not create decompression dict")
1782 raise ZstdError("could not create decompression dict")
1784
1783
1785 ddict = ffi.gc(
1784 ddict = ffi.gc(
1786 ddict, lib.ZSTD_freeDDict, size=lib.ZSTD_sizeof_DDict(ddict)
1785 ddict, lib.ZSTD_freeDDict, size=lib.ZSTD_sizeof_DDict(ddict)
1787 )
1786 )
1788 self.__dict__["_ddict"] = ddict
1787 self.__dict__["_ddict"] = ddict
1789
1788
1790 return ddict
1789 return ddict
1791
1790
1792
1791
1793 def train_dictionary(
1792 def train_dictionary(
1794 dict_size,
1793 dict_size,
1795 samples,
1794 samples,
1796 k=0,
1795 k=0,
1797 d=0,
1796 d=0,
1798 notifications=0,
1797 notifications=0,
1799 dict_id=0,
1798 dict_id=0,
1800 level=0,
1799 level=0,
1801 steps=0,
1800 steps=0,
1802 threads=0,
1801 threads=0,
1803 ):
1802 ):
1804 if not isinstance(samples, list):
1803 if not isinstance(samples, list):
1805 raise TypeError("samples must be a list")
1804 raise TypeError("samples must be a list")
1806
1805
1807 if threads < 0:
1806 if threads < 0:
1808 threads = _cpu_count()
1807 threads = _cpu_count()
1809
1808
1810 total_size = sum(map(len, samples))
1809 total_size = sum(map(len, samples))
1811
1810
1812 samples_buffer = new_nonzero("char[]", total_size)
1811 samples_buffer = new_nonzero("char[]", total_size)
1813 sample_sizes = new_nonzero("size_t[]", len(samples))
1812 sample_sizes = new_nonzero("size_t[]", len(samples))
1814
1813
1815 offset = 0
1814 offset = 0
1816 for i, sample in enumerate(samples):
1815 for i, sample in enumerate(samples):
1817 if not isinstance(sample, bytes_type):
1816 if not isinstance(sample, bytes_type):
1818 raise ValueError("samples must be bytes")
1817 raise ValueError("samples must be bytes")
1819
1818
1820 l = len(sample)
1819 l = len(sample)
1821 ffi.memmove(samples_buffer + offset, sample, l)
1820 ffi.memmove(samples_buffer + offset, sample, l)
1822 offset += l
1821 offset += l
1823 sample_sizes[i] = l
1822 sample_sizes[i] = l
1824
1823
1825 dict_data = new_nonzero("char[]", dict_size)
1824 dict_data = new_nonzero("char[]", dict_size)
1826
1825
1827 dparams = ffi.new("ZDICT_cover_params_t *")[0]
1826 dparams = ffi.new("ZDICT_cover_params_t *")[0]
1828 dparams.k = k
1827 dparams.k = k
1829 dparams.d = d
1828 dparams.d = d
1830 dparams.steps = steps
1829 dparams.steps = steps
1831 dparams.nbThreads = threads
1830 dparams.nbThreads = threads
1832 dparams.zParams.notificationLevel = notifications
1831 dparams.zParams.notificationLevel = notifications
1833 dparams.zParams.dictID = dict_id
1832 dparams.zParams.dictID = dict_id
1834 dparams.zParams.compressionLevel = level
1833 dparams.zParams.compressionLevel = level
1835
1834
1836 if (
1835 if (
1837 not dparams.k
1836 not dparams.k
1838 and not dparams.d
1837 and not dparams.d
1839 and not dparams.steps
1838 and not dparams.steps
1840 and not dparams.nbThreads
1839 and not dparams.nbThreads
1841 and not dparams.zParams.notificationLevel
1840 and not dparams.zParams.notificationLevel
1842 and not dparams.zParams.dictID
1841 and not dparams.zParams.dictID
1843 and not dparams.zParams.compressionLevel
1842 and not dparams.zParams.compressionLevel
1844 ):
1843 ):
1845 zresult = lib.ZDICT_trainFromBuffer(
1844 zresult = lib.ZDICT_trainFromBuffer(
1846 ffi.addressof(dict_data),
1845 ffi.addressof(dict_data),
1847 dict_size,
1846 dict_size,
1848 ffi.addressof(samples_buffer),
1847 ffi.addressof(samples_buffer),
1849 ffi.addressof(sample_sizes, 0),
1848 ffi.addressof(sample_sizes, 0),
1850 len(samples),
1849 len(samples),
1851 )
1850 )
1852 elif dparams.steps or dparams.nbThreads:
1851 elif dparams.steps or dparams.nbThreads:
1853 zresult = lib.ZDICT_optimizeTrainFromBuffer_cover(
1852 zresult = lib.ZDICT_optimizeTrainFromBuffer_cover(
1854 ffi.addressof(dict_data),
1853 ffi.addressof(dict_data),
1855 dict_size,
1854 dict_size,
1856 ffi.addressof(samples_buffer),
1855 ffi.addressof(samples_buffer),
1857 ffi.addressof(sample_sizes, 0),
1856 ffi.addressof(sample_sizes, 0),
1858 len(samples),
1857 len(samples),
1859 ffi.addressof(dparams),
1858 ffi.addressof(dparams),
1860 )
1859 )
1861 else:
1860 else:
1862 zresult = lib.ZDICT_trainFromBuffer_cover(
1861 zresult = lib.ZDICT_trainFromBuffer_cover(
1863 ffi.addressof(dict_data),
1862 ffi.addressof(dict_data),
1864 dict_size,
1863 dict_size,
1865 ffi.addressof(samples_buffer),
1864 ffi.addressof(samples_buffer),
1866 ffi.addressof(sample_sizes, 0),
1865 ffi.addressof(sample_sizes, 0),
1867 len(samples),
1866 len(samples),
1868 dparams,
1867 dparams,
1869 )
1868 )
1870
1869
1871 if lib.ZDICT_isError(zresult):
1870 if lib.ZDICT_isError(zresult):
1872 msg = ffi.string(lib.ZDICT_getErrorName(zresult)).decode("utf-8")
1871 msg = ffi.string(lib.ZDICT_getErrorName(zresult)).decode("utf-8")
1873 raise ZstdError("cannot train dict: %s" % msg)
1872 raise ZstdError("cannot train dict: %s" % msg)
1874
1873
1875 return ZstdCompressionDict(
1874 return ZstdCompressionDict(
1876 ffi.buffer(dict_data, zresult)[:],
1875 ffi.buffer(dict_data, zresult)[:],
1877 dict_type=DICT_TYPE_FULLDICT,
1876 dict_type=DICT_TYPE_FULLDICT,
1878 k=dparams.k,
1877 k=dparams.k,
1879 d=dparams.d,
1878 d=dparams.d,
1880 )
1879 )
1881
1880
1882
1881
1883 class ZstdDecompressionObj(object):
1882 class ZstdDecompressionObj(object):
1884 def __init__(self, decompressor, write_size):
1883 def __init__(self, decompressor, write_size):
1885 self._decompressor = decompressor
1884 self._decompressor = decompressor
1886 self._write_size = write_size
1885 self._write_size = write_size
1887 self._finished = False
1886 self._finished = False
1888
1887
1889 def decompress(self, data):
1888 def decompress(self, data):
1890 if self._finished:
1889 if self._finished:
1891 raise ZstdError("cannot use a decompressobj multiple times")
1890 raise ZstdError("cannot use a decompressobj multiple times")
1892
1891
1893 in_buffer = ffi.new("ZSTD_inBuffer *")
1892 in_buffer = ffi.new("ZSTD_inBuffer *")
1894 out_buffer = ffi.new("ZSTD_outBuffer *")
1893 out_buffer = ffi.new("ZSTD_outBuffer *")
1895
1894
1896 data_buffer = ffi.from_buffer(data)
1895 data_buffer = ffi.from_buffer(data)
1897
1896
1898 if len(data_buffer) == 0:
1897 if len(data_buffer) == 0:
1899 return b""
1898 return b""
1900
1899
1901 in_buffer.src = data_buffer
1900 in_buffer.src = data_buffer
1902 in_buffer.size = len(data_buffer)
1901 in_buffer.size = len(data_buffer)
1903 in_buffer.pos = 0
1902 in_buffer.pos = 0
1904
1903
1905 dst_buffer = ffi.new("char[]", self._write_size)
1904 dst_buffer = ffi.new("char[]", self._write_size)
1906 out_buffer.dst = dst_buffer
1905 out_buffer.dst = dst_buffer
1907 out_buffer.size = len(dst_buffer)
1906 out_buffer.size = len(dst_buffer)
1908 out_buffer.pos = 0
1907 out_buffer.pos = 0
1909
1908
1910 chunks = []
1909 chunks = []
1911
1910
1912 while True:
1911 while True:
1913 zresult = lib.ZSTD_decompressStream(
1912 zresult = lib.ZSTD_decompressStream(
1914 self._decompressor._dctx, out_buffer, in_buffer
1913 self._decompressor._dctx, out_buffer, in_buffer
1915 )
1914 )
1916 if lib.ZSTD_isError(zresult):
1915 if lib.ZSTD_isError(zresult):
1917 raise ZstdError(
1916 raise ZstdError(
1918 "zstd decompressor error: %s" % _zstd_error(zresult)
1917 "zstd decompressor error: %s" % _zstd_error(zresult)
1919 )
1918 )
1920
1919
1921 if zresult == 0:
1920 if zresult == 0:
1922 self._finished = True
1921 self._finished = True
1923 self._decompressor = None
1922 self._decompressor = None
1924
1923
1925 if out_buffer.pos:
1924 if out_buffer.pos:
1926 chunks.append(ffi.buffer(out_buffer.dst, out_buffer.pos)[:])
1925 chunks.append(ffi.buffer(out_buffer.dst, out_buffer.pos)[:])
1927
1926
1928 if zresult == 0 or (
1927 if zresult == 0 or (
1929 in_buffer.pos == in_buffer.size and out_buffer.pos == 0
1928 in_buffer.pos == in_buffer.size and out_buffer.pos == 0
1930 ):
1929 ):
1931 break
1930 break
1932
1931
1933 out_buffer.pos = 0
1932 out_buffer.pos = 0
1934
1933
1935 return b"".join(chunks)
1934 return b"".join(chunks)
1936
1935
1937 def flush(self, length=0):
1936 def flush(self, length=0):
1938 pass
1937 pass
1939
1938
1940
1939
1941 class ZstdDecompressionReader(object):
1940 class ZstdDecompressionReader(object):
1942 def __init__(self, decompressor, source, read_size, read_across_frames):
1941 def __init__(self, decompressor, source, read_size, read_across_frames):
1943 self._decompressor = decompressor
1942 self._decompressor = decompressor
1944 self._source = source
1943 self._source = source
1945 self._read_size = read_size
1944 self._read_size = read_size
1946 self._read_across_frames = bool(read_across_frames)
1945 self._read_across_frames = bool(read_across_frames)
1947 self._entered = False
1946 self._entered = False
1948 self._closed = False
1947 self._closed = False
1949 self._bytes_decompressed = 0
1948 self._bytes_decompressed = 0
1950 self._finished_input = False
1949 self._finished_input = False
1951 self._finished_output = False
1950 self._finished_output = False
1952 self._in_buffer = ffi.new("ZSTD_inBuffer *")
1951 self._in_buffer = ffi.new("ZSTD_inBuffer *")
1953 # Holds a ref to self._in_buffer.src.
1952 # Holds a ref to self._in_buffer.src.
1954 self._source_buffer = None
1953 self._source_buffer = None
1955
1954
1956 def __enter__(self):
1955 def __enter__(self):
1957 if self._entered:
1956 if self._entered:
1958 raise ValueError("cannot __enter__ multiple times")
1957 raise ValueError("cannot __enter__ multiple times")
1959
1958
1960 self._entered = True
1959 self._entered = True
1961 return self
1960 return self
1962
1961
1963 def __exit__(self, exc_type, exc_value, exc_tb):
1962 def __exit__(self, exc_type, exc_value, exc_tb):
1964 self._entered = False
1963 self._entered = False
1965 self._closed = True
1964 self._closed = True
1966 self._source = None
1965 self._source = None
1967 self._decompressor = None
1966 self._decompressor = None
1968
1967
1969 return False
1968 return False
1970
1969
1971 def readable(self):
1970 def readable(self):
1972 return True
1971 return True
1973
1972
1974 def writable(self):
1973 def writable(self):
1975 return False
1974 return False
1976
1975
1977 def seekable(self):
1976 def seekable(self):
1978 return True
1977 return True
1979
1978
1980 def readline(self):
1979 def readline(self):
1981 raise io.UnsupportedOperation()
1980 raise io.UnsupportedOperation()
1982
1981
1983 def readlines(self):
1982 def readlines(self):
1984 raise io.UnsupportedOperation()
1983 raise io.UnsupportedOperation()
1985
1984
1986 def write(self, data):
1985 def write(self, data):
1987 raise io.UnsupportedOperation()
1986 raise io.UnsupportedOperation()
1988
1987
1989 def writelines(self, lines):
1988 def writelines(self, lines):
1990 raise io.UnsupportedOperation()
1989 raise io.UnsupportedOperation()
1991
1990
1992 def isatty(self):
1991 def isatty(self):
1993 return False
1992 return False
1994
1993
1995 def flush(self):
1994 def flush(self):
1996 return None
1995 return None
1997
1996
1998 def close(self):
1997 def close(self):
1999 self._closed = True
1998 self._closed = True
2000 return None
1999 return None
2001
2000
2002 @property
2001 @property
2003 def closed(self):
2002 def closed(self):
2004 return self._closed
2003 return self._closed
2005
2004
2006 def tell(self):
2005 def tell(self):
2007 return self._bytes_decompressed
2006 return self._bytes_decompressed
2008
2007
2009 def readall(self):
2008 def readall(self):
2010 chunks = []
2009 chunks = []
2011
2010
2012 while True:
2011 while True:
2013 chunk = self.read(1048576)
2012 chunk = self.read(1048576)
2014 if not chunk:
2013 if not chunk:
2015 break
2014 break
2016
2015
2017 chunks.append(chunk)
2016 chunks.append(chunk)
2018
2017
2019 return b"".join(chunks)
2018 return b"".join(chunks)
2020
2019
2021 def __iter__(self):
2020 def __iter__(self):
2022 raise io.UnsupportedOperation()
2021 raise io.UnsupportedOperation()
2023
2022
2024 def __next__(self):
2023 def __next__(self):
2025 raise io.UnsupportedOperation()
2024 raise io.UnsupportedOperation()
2026
2025
2027 next = __next__
2026 next = __next__
2028
2027
2029 def _read_input(self):
2028 def _read_input(self):
2030 # We have data left over in the input buffer. Use it.
2029 # We have data left over in the input buffer. Use it.
2031 if self._in_buffer.pos < self._in_buffer.size:
2030 if self._in_buffer.pos < self._in_buffer.size:
2032 return
2031 return
2033
2032
2034 # All input data exhausted. Nothing to do.
2033 # All input data exhausted. Nothing to do.
2035 if self._finished_input:
2034 if self._finished_input:
2036 return
2035 return
2037
2036
2038 # Else populate the input buffer from our source.
2037 # Else populate the input buffer from our source.
2039 if hasattr(self._source, "read"):
2038 if hasattr(self._source, "read"):
2040 data = self._source.read(self._read_size)
2039 data = self._source.read(self._read_size)
2041
2040
2042 if not data:
2041 if not data:
2043 self._finished_input = True
2042 self._finished_input = True
2044 return
2043 return
2045
2044
2046 self._source_buffer = ffi.from_buffer(data)
2045 self._source_buffer = ffi.from_buffer(data)
2047 self._in_buffer.src = self._source_buffer
2046 self._in_buffer.src = self._source_buffer
2048 self._in_buffer.size = len(self._source_buffer)
2047 self._in_buffer.size = len(self._source_buffer)
2049 self._in_buffer.pos = 0
2048 self._in_buffer.pos = 0
2050 else:
2049 else:
2051 self._source_buffer = ffi.from_buffer(self._source)
2050 self._source_buffer = ffi.from_buffer(self._source)
2052 self._in_buffer.src = self._source_buffer
2051 self._in_buffer.src = self._source_buffer
2053 self._in_buffer.size = len(self._source_buffer)
2052 self._in_buffer.size = len(self._source_buffer)
2054 self._in_buffer.pos = 0
2053 self._in_buffer.pos = 0
2055
2054
2056 def _decompress_into_buffer(self, out_buffer):
2055 def _decompress_into_buffer(self, out_buffer):
2057 """Decompress available input into an output buffer.
2056 """Decompress available input into an output buffer.
2058
2057
2059 Returns True if data in output buffer should be emitted.
2058 Returns True if data in output buffer should be emitted.
2060 """
2059 """
2061 zresult = lib.ZSTD_decompressStream(
2060 zresult = lib.ZSTD_decompressStream(
2062 self._decompressor._dctx, out_buffer, self._in_buffer
2061 self._decompressor._dctx, out_buffer, self._in_buffer
2063 )
2062 )
2064
2063
2065 if self._in_buffer.pos == self._in_buffer.size:
2064 if self._in_buffer.pos == self._in_buffer.size:
2066 self._in_buffer.src = ffi.NULL
2065 self._in_buffer.src = ffi.NULL
2067 self._in_buffer.pos = 0
2066 self._in_buffer.pos = 0
2068 self._in_buffer.size = 0
2067 self._in_buffer.size = 0
2069 self._source_buffer = None
2068 self._source_buffer = None
2070
2069
2071 if not hasattr(self._source, "read"):
2070 if not hasattr(self._source, "read"):
2072 self._finished_input = True
2071 self._finished_input = True
2073
2072
2074 if lib.ZSTD_isError(zresult):
2073 if lib.ZSTD_isError(zresult):
2075 raise ZstdError("zstd decompress error: %s" % _zstd_error(zresult))
2074 raise ZstdError("zstd decompress error: %s" % _zstd_error(zresult))
2076
2075
2077 # Emit data if there is data AND either:
2076 # Emit data if there is data AND either:
2078 # a) output buffer is full (read amount is satisfied)
2077 # a) output buffer is full (read amount is satisfied)
2079 # b) we're at end of a frame and not in frame spanning mode
2078 # b) we're at end of a frame and not in frame spanning mode
2080 return out_buffer.pos and (
2079 return out_buffer.pos and (
2081 out_buffer.pos == out_buffer.size
2080 out_buffer.pos == out_buffer.size
2082 or zresult == 0
2081 or zresult == 0
2083 and not self._read_across_frames
2082 and not self._read_across_frames
2084 )
2083 )
2085
2084
2086 def read(self, size=-1):
2085 def read(self, size=-1):
2087 if self._closed:
2086 if self._closed:
2088 raise ValueError("stream is closed")
2087 raise ValueError("stream is closed")
2089
2088
2090 if size < -1:
2089 if size < -1:
2091 raise ValueError("cannot read negative amounts less than -1")
2090 raise ValueError("cannot read negative amounts less than -1")
2092
2091
2093 if size == -1:
2092 if size == -1:
2094 # This is recursive. But it gets the job done.
2093 # This is recursive. But it gets the job done.
2095 return self.readall()
2094 return self.readall()
2096
2095
2097 if self._finished_output or size == 0:
2096 if self._finished_output or size == 0:
2098 return b""
2097 return b""
2099
2098
2100 # We /could/ call into readinto() here. But that introduces more
2099 # We /could/ call into readinto() here. But that introduces more
2101 # overhead.
2100 # overhead.
2102 dst_buffer = ffi.new("char[]", size)
2101 dst_buffer = ffi.new("char[]", size)
2103 out_buffer = ffi.new("ZSTD_outBuffer *")
2102 out_buffer = ffi.new("ZSTD_outBuffer *")
2104 out_buffer.dst = dst_buffer
2103 out_buffer.dst = dst_buffer
2105 out_buffer.size = size
2104 out_buffer.size = size
2106 out_buffer.pos = 0
2105 out_buffer.pos = 0
2107
2106
2108 self._read_input()
2107 self._read_input()
2109 if self._decompress_into_buffer(out_buffer):
2108 if self._decompress_into_buffer(out_buffer):
2110 self._bytes_decompressed += out_buffer.pos
2109 self._bytes_decompressed += out_buffer.pos
2111 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
2110 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
2112
2111
2113 while not self._finished_input:
2112 while not self._finished_input:
2114 self._read_input()
2113 self._read_input()
2115 if self._decompress_into_buffer(out_buffer):
2114 if self._decompress_into_buffer(out_buffer):
2116 self._bytes_decompressed += out_buffer.pos
2115 self._bytes_decompressed += out_buffer.pos
2117 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
2116 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
2118
2117
2119 self._bytes_decompressed += out_buffer.pos
2118 self._bytes_decompressed += out_buffer.pos
2120 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
2119 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
2121
2120
2122 def readinto(self, b):
2121 def readinto(self, b):
2123 if self._closed:
2122 if self._closed:
2124 raise ValueError("stream is closed")
2123 raise ValueError("stream is closed")
2125
2124
2126 if self._finished_output:
2125 if self._finished_output:
2127 return 0
2126 return 0
2128
2127
2129 # TODO use writable=True once we require CFFI >= 1.12.
2128 # TODO use writable=True once we require CFFI >= 1.12.
2130 dest_buffer = ffi.from_buffer(b)
2129 dest_buffer = ffi.from_buffer(b)
2131 ffi.memmove(b, b"", 0)
2130 ffi.memmove(b, b"", 0)
2132 out_buffer = ffi.new("ZSTD_outBuffer *")
2131 out_buffer = ffi.new("ZSTD_outBuffer *")
2133 out_buffer.dst = dest_buffer
2132 out_buffer.dst = dest_buffer
2134 out_buffer.size = len(dest_buffer)
2133 out_buffer.size = len(dest_buffer)
2135 out_buffer.pos = 0
2134 out_buffer.pos = 0
2136
2135
2137 self._read_input()
2136 self._read_input()
2138 if self._decompress_into_buffer(out_buffer):
2137 if self._decompress_into_buffer(out_buffer):
2139 self._bytes_decompressed += out_buffer.pos
2138 self._bytes_decompressed += out_buffer.pos
2140 return out_buffer.pos
2139 return out_buffer.pos
2141
2140
2142 while not self._finished_input:
2141 while not self._finished_input:
2143 self._read_input()
2142 self._read_input()
2144 if self._decompress_into_buffer(out_buffer):
2143 if self._decompress_into_buffer(out_buffer):
2145 self._bytes_decompressed += out_buffer.pos
2144 self._bytes_decompressed += out_buffer.pos
2146 return out_buffer.pos
2145 return out_buffer.pos
2147
2146
2148 self._bytes_decompressed += out_buffer.pos
2147 self._bytes_decompressed += out_buffer.pos
2149 return out_buffer.pos
2148 return out_buffer.pos
2150
2149
2151 def read1(self, size=-1):
2150 def read1(self, size=-1):
2152 if self._closed:
2151 if self._closed:
2153 raise ValueError("stream is closed")
2152 raise ValueError("stream is closed")
2154
2153
2155 if size < -1:
2154 if size < -1:
2156 raise ValueError("cannot read negative amounts less than -1")
2155 raise ValueError("cannot read negative amounts less than -1")
2157
2156
2158 if self._finished_output or size == 0:
2157 if self._finished_output or size == 0:
2159 return b""
2158 return b""
2160
2159
2161 # -1 returns arbitrary number of bytes.
2160 # -1 returns arbitrary number of bytes.
2162 if size == -1:
2161 if size == -1:
2163 size = DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE
2162 size = DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE
2164
2163
2165 dst_buffer = ffi.new("char[]", size)
2164 dst_buffer = ffi.new("char[]", size)
2166 out_buffer = ffi.new("ZSTD_outBuffer *")
2165 out_buffer = ffi.new("ZSTD_outBuffer *")
2167 out_buffer.dst = dst_buffer
2166 out_buffer.dst = dst_buffer
2168 out_buffer.size = size
2167 out_buffer.size = size
2169 out_buffer.pos = 0
2168 out_buffer.pos = 0
2170
2169
2171 # read1() dictates that we can perform at most 1 call to underlying
2170 # read1() dictates that we can perform at most 1 call to underlying
2172 # stream to get input. However, we can't satisfy this restriction with
2171 # stream to get input. However, we can't satisfy this restriction with
2173 # decompression because not all input generates output. So we allow
2172 # decompression because not all input generates output. So we allow
2174 # multiple read(). But unlike read(), we stop once we have any output.
2173 # multiple read(). But unlike read(), we stop once we have any output.
2175 while not self._finished_input:
2174 while not self._finished_input:
2176 self._read_input()
2175 self._read_input()
2177 self._decompress_into_buffer(out_buffer)
2176 self._decompress_into_buffer(out_buffer)
2178
2177
2179 if out_buffer.pos:
2178 if out_buffer.pos:
2180 break
2179 break
2181
2180
2182 self._bytes_decompressed += out_buffer.pos
2181 self._bytes_decompressed += out_buffer.pos
2183 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
2182 return ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
2184
2183
2185 def readinto1(self, b):
2184 def readinto1(self, b):
2186 if self._closed:
2185 if self._closed:
2187 raise ValueError("stream is closed")
2186 raise ValueError("stream is closed")
2188
2187
2189 if self._finished_output:
2188 if self._finished_output:
2190 return 0
2189 return 0
2191
2190
2192 # TODO use writable=True once we require CFFI >= 1.12.
2191 # TODO use writable=True once we require CFFI >= 1.12.
2193 dest_buffer = ffi.from_buffer(b)
2192 dest_buffer = ffi.from_buffer(b)
2194 ffi.memmove(b, b"", 0)
2193 ffi.memmove(b, b"", 0)
2195
2194
2196 out_buffer = ffi.new("ZSTD_outBuffer *")
2195 out_buffer = ffi.new("ZSTD_outBuffer *")
2197 out_buffer.dst = dest_buffer
2196 out_buffer.dst = dest_buffer
2198 out_buffer.size = len(dest_buffer)
2197 out_buffer.size = len(dest_buffer)
2199 out_buffer.pos = 0
2198 out_buffer.pos = 0
2200
2199
2201 while not self._finished_input and not self._finished_output:
2200 while not self._finished_input and not self._finished_output:
2202 self._read_input()
2201 self._read_input()
2203 self._decompress_into_buffer(out_buffer)
2202 self._decompress_into_buffer(out_buffer)
2204
2203
2205 if out_buffer.pos:
2204 if out_buffer.pos:
2206 break
2205 break
2207
2206
2208 self._bytes_decompressed += out_buffer.pos
2207 self._bytes_decompressed += out_buffer.pos
2209 return out_buffer.pos
2208 return out_buffer.pos
2210
2209
2211 def seek(self, pos, whence=os.SEEK_SET):
2210 def seek(self, pos, whence=os.SEEK_SET):
2212 if self._closed:
2211 if self._closed:
2213 raise ValueError("stream is closed")
2212 raise ValueError("stream is closed")
2214
2213
2215 read_amount = 0
2214 read_amount = 0
2216
2215
2217 if whence == os.SEEK_SET:
2216 if whence == os.SEEK_SET:
2218 if pos < 0:
2217 if pos < 0:
2219 raise ValueError(
2218 raise ValueError(
2220 "cannot seek to negative position with SEEK_SET"
2219 "cannot seek to negative position with SEEK_SET"
2221 )
2220 )
2222
2221
2223 if pos < self._bytes_decompressed:
2222 if pos < self._bytes_decompressed:
2224 raise ValueError(
2223 raise ValueError(
2225 "cannot seek zstd decompression stream " "backwards"
2224 "cannot seek zstd decompression stream " "backwards"
2226 )
2225 )
2227
2226
2228 read_amount = pos - self._bytes_decompressed
2227 read_amount = pos - self._bytes_decompressed
2229
2228
2230 elif whence == os.SEEK_CUR:
2229 elif whence == os.SEEK_CUR:
2231 if pos < 0:
2230 if pos < 0:
2232 raise ValueError(
2231 raise ValueError(
2233 "cannot seek zstd decompression stream " "backwards"
2232 "cannot seek zstd decompression stream " "backwards"
2234 )
2233 )
2235
2234
2236 read_amount = pos
2235 read_amount = pos
2237 elif whence == os.SEEK_END:
2236 elif whence == os.SEEK_END:
2238 raise ValueError(
2237 raise ValueError(
2239 "zstd decompression streams cannot be seeked " "with SEEK_END"
2238 "zstd decompression streams cannot be seeked " "with SEEK_END"
2240 )
2239 )
2241
2240
2242 while read_amount:
2241 while read_amount:
2243 result = self.read(
2242 result = self.read(
2244 min(read_amount, DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE)
2243 min(read_amount, DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE)
2245 )
2244 )
2246
2245
2247 if not result:
2246 if not result:
2248 break
2247 break
2249
2248
2250 read_amount -= len(result)
2249 read_amount -= len(result)
2251
2250
2252 return self._bytes_decompressed
2251 return self._bytes_decompressed
2253
2252
2254
2253
2255 class ZstdDecompressionWriter(object):
2254 class ZstdDecompressionWriter(object):
2256 def __init__(self, decompressor, writer, write_size, write_return_read):
2255 def __init__(self, decompressor, writer, write_size, write_return_read):
2257 decompressor._ensure_dctx()
2256 decompressor._ensure_dctx()
2258
2257
2259 self._decompressor = decompressor
2258 self._decompressor = decompressor
2260 self._writer = writer
2259 self._writer = writer
2261 self._write_size = write_size
2260 self._write_size = write_size
2262 self._write_return_read = bool(write_return_read)
2261 self._write_return_read = bool(write_return_read)
2263 self._entered = False
2262 self._entered = False
2264 self._closed = False
2263 self._closed = False
2265
2264
2266 def __enter__(self):
2265 def __enter__(self):
2267 if self._closed:
2266 if self._closed:
2268 raise ValueError("stream is closed")
2267 raise ValueError("stream is closed")
2269
2268
2270 if self._entered:
2269 if self._entered:
2271 raise ZstdError("cannot __enter__ multiple times")
2270 raise ZstdError("cannot __enter__ multiple times")
2272
2271
2273 self._entered = True
2272 self._entered = True
2274
2273
2275 return self
2274 return self
2276
2275
2277 def __exit__(self, exc_type, exc_value, exc_tb):
2276 def __exit__(self, exc_type, exc_value, exc_tb):
2278 self._entered = False
2277 self._entered = False
2279 self.close()
2278 self.close()
2280
2279
2281 def memory_size(self):
2280 def memory_size(self):
2282 return lib.ZSTD_sizeof_DCtx(self._decompressor._dctx)
2281 return lib.ZSTD_sizeof_DCtx(self._decompressor._dctx)
2283
2282
2284 def close(self):
2283 def close(self):
2285 if self._closed:
2284 if self._closed:
2286 return
2285 return
2287
2286
2288 try:
2287 try:
2289 self.flush()
2288 self.flush()
2290 finally:
2289 finally:
2291 self._closed = True
2290 self._closed = True
2292
2291
2293 f = getattr(self._writer, "close", None)
2292 f = getattr(self._writer, "close", None)
2294 if f:
2293 if f:
2295 f()
2294 f()
2296
2295
2297 @property
2296 @property
2298 def closed(self):
2297 def closed(self):
2299 return self._closed
2298 return self._closed
2300
2299
2301 def fileno(self):
2300 def fileno(self):
2302 f = getattr(self._writer, "fileno", None)
2301 f = getattr(self._writer, "fileno", None)
2303 if f:
2302 if f:
2304 return f()
2303 return f()
2305 else:
2304 else:
2306 raise OSError("fileno not available on underlying writer")
2305 raise OSError("fileno not available on underlying writer")
2307
2306
2308 def flush(self):
2307 def flush(self):
2309 if self._closed:
2308 if self._closed:
2310 raise ValueError("stream is closed")
2309 raise ValueError("stream is closed")
2311
2310
2312 f = getattr(self._writer, "flush", None)
2311 f = getattr(self._writer, "flush", None)
2313 if f:
2312 if f:
2314 return f()
2313 return f()
2315
2314
2316 def isatty(self):
2315 def isatty(self):
2317 return False
2316 return False
2318
2317
2319 def readable(self):
2318 def readable(self):
2320 return False
2319 return False
2321
2320
2322 def readline(self, size=-1):
2321 def readline(self, size=-1):
2323 raise io.UnsupportedOperation()
2322 raise io.UnsupportedOperation()
2324
2323
2325 def readlines(self, hint=-1):
2324 def readlines(self, hint=-1):
2326 raise io.UnsupportedOperation()
2325 raise io.UnsupportedOperation()
2327
2326
2328 def seek(self, offset, whence=None):
2327 def seek(self, offset, whence=None):
2329 raise io.UnsupportedOperation()
2328 raise io.UnsupportedOperation()
2330
2329
2331 def seekable(self):
2330 def seekable(self):
2332 return False
2331 return False
2333
2332
2334 def tell(self):
2333 def tell(self):
2335 raise io.UnsupportedOperation()
2334 raise io.UnsupportedOperation()
2336
2335
2337 def truncate(self, size=None):
2336 def truncate(self, size=None):
2338 raise io.UnsupportedOperation()
2337 raise io.UnsupportedOperation()
2339
2338
2340 def writable(self):
2339 def writable(self):
2341 return True
2340 return True
2342
2341
2343 def writelines(self, lines):
2342 def writelines(self, lines):
2344 raise io.UnsupportedOperation()
2343 raise io.UnsupportedOperation()
2345
2344
2346 def read(self, size=-1):
2345 def read(self, size=-1):
2347 raise io.UnsupportedOperation()
2346 raise io.UnsupportedOperation()
2348
2347
2349 def readall(self):
2348 def readall(self):
2350 raise io.UnsupportedOperation()
2349 raise io.UnsupportedOperation()
2351
2350
2352 def readinto(self, b):
2351 def readinto(self, b):
2353 raise io.UnsupportedOperation()
2352 raise io.UnsupportedOperation()
2354
2353
2355 def write(self, data):
2354 def write(self, data):
2356 if self._closed:
2355 if self._closed:
2357 raise ValueError("stream is closed")
2356 raise ValueError("stream is closed")
2358
2357
2359 total_write = 0
2358 total_write = 0
2360
2359
2361 in_buffer = ffi.new("ZSTD_inBuffer *")
2360 in_buffer = ffi.new("ZSTD_inBuffer *")
2362 out_buffer = ffi.new("ZSTD_outBuffer *")
2361 out_buffer = ffi.new("ZSTD_outBuffer *")
2363
2362
2364 data_buffer = ffi.from_buffer(data)
2363 data_buffer = ffi.from_buffer(data)
2365 in_buffer.src = data_buffer
2364 in_buffer.src = data_buffer
2366 in_buffer.size = len(data_buffer)
2365 in_buffer.size = len(data_buffer)
2367 in_buffer.pos = 0
2366 in_buffer.pos = 0
2368
2367
2369 dst_buffer = ffi.new("char[]", self._write_size)
2368 dst_buffer = ffi.new("char[]", self._write_size)
2370 out_buffer.dst = dst_buffer
2369 out_buffer.dst = dst_buffer
2371 out_buffer.size = len(dst_buffer)
2370 out_buffer.size = len(dst_buffer)
2372 out_buffer.pos = 0
2371 out_buffer.pos = 0
2373
2372
2374 dctx = self._decompressor._dctx
2373 dctx = self._decompressor._dctx
2375
2374
2376 while in_buffer.pos < in_buffer.size:
2375 while in_buffer.pos < in_buffer.size:
2377 zresult = lib.ZSTD_decompressStream(dctx, out_buffer, in_buffer)
2376 zresult = lib.ZSTD_decompressStream(dctx, out_buffer, in_buffer)
2378 if lib.ZSTD_isError(zresult):
2377 if lib.ZSTD_isError(zresult):
2379 raise ZstdError(
2378 raise ZstdError(
2380 "zstd decompress error: %s" % _zstd_error(zresult)
2379 "zstd decompress error: %s" % _zstd_error(zresult)
2381 )
2380 )
2382
2381
2383 if out_buffer.pos:
2382 if out_buffer.pos:
2384 self._writer.write(
2383 self._writer.write(
2385 ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
2384 ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
2386 )
2385 )
2387 total_write += out_buffer.pos
2386 total_write += out_buffer.pos
2388 out_buffer.pos = 0
2387 out_buffer.pos = 0
2389
2388
2390 if self._write_return_read:
2389 if self._write_return_read:
2391 return in_buffer.pos
2390 return in_buffer.pos
2392 else:
2391 else:
2393 return total_write
2392 return total_write
2394
2393
2395
2394
2396 class ZstdDecompressor(object):
2395 class ZstdDecompressor(object):
2397 def __init__(self, dict_data=None, max_window_size=0, format=FORMAT_ZSTD1):
2396 def __init__(self, dict_data=None, max_window_size=0, format=FORMAT_ZSTD1):
2398 self._dict_data = dict_data
2397 self._dict_data = dict_data
2399 self._max_window_size = max_window_size
2398 self._max_window_size = max_window_size
2400 self._format = format
2399 self._format = format
2401
2400
2402 dctx = lib.ZSTD_createDCtx()
2401 dctx = lib.ZSTD_createDCtx()
2403 if dctx == ffi.NULL:
2402 if dctx == ffi.NULL:
2404 raise MemoryError()
2403 raise MemoryError()
2405
2404
2406 self._dctx = dctx
2405 self._dctx = dctx
2407
2406
2408 # Defer setting up garbage collection until full state is loaded so
2407 # Defer setting up garbage collection until full state is loaded so
2409 # the memory size is more accurate.
2408 # the memory size is more accurate.
2410 try:
2409 try:
2411 self._ensure_dctx()
2410 self._ensure_dctx()
2412 finally:
2411 finally:
2413 self._dctx = ffi.gc(
2412 self._dctx = ffi.gc(
2414 dctx, lib.ZSTD_freeDCtx, size=lib.ZSTD_sizeof_DCtx(dctx)
2413 dctx, lib.ZSTD_freeDCtx, size=lib.ZSTD_sizeof_DCtx(dctx)
2415 )
2414 )
2416
2415
2417 def memory_size(self):
2416 def memory_size(self):
2418 return lib.ZSTD_sizeof_DCtx(self._dctx)
2417 return lib.ZSTD_sizeof_DCtx(self._dctx)
2419
2418
2420 def decompress(self, data, max_output_size=0):
2419 def decompress(self, data, max_output_size=0):
2421 self._ensure_dctx()
2420 self._ensure_dctx()
2422
2421
2423 data_buffer = ffi.from_buffer(data)
2422 data_buffer = ffi.from_buffer(data)
2424
2423
2425 output_size = lib.ZSTD_getFrameContentSize(
2424 output_size = lib.ZSTD_getFrameContentSize(
2426 data_buffer, len(data_buffer)
2425 data_buffer, len(data_buffer)
2427 )
2426 )
2428
2427
2429 if output_size == lib.ZSTD_CONTENTSIZE_ERROR:
2428 if output_size == lib.ZSTD_CONTENTSIZE_ERROR:
2430 raise ZstdError("error determining content size from frame header")
2429 raise ZstdError("error determining content size from frame header")
2431 elif output_size == 0:
2430 elif output_size == 0:
2432 return b""
2431 return b""
2433 elif output_size == lib.ZSTD_CONTENTSIZE_UNKNOWN:
2432 elif output_size == lib.ZSTD_CONTENTSIZE_UNKNOWN:
2434 if not max_output_size:
2433 if not max_output_size:
2435 raise ZstdError(
2434 raise ZstdError(
2436 "could not determine content size in frame header"
2435 "could not determine content size in frame header"
2437 )
2436 )
2438
2437
2439 result_buffer = ffi.new("char[]", max_output_size)
2438 result_buffer = ffi.new("char[]", max_output_size)
2440 result_size = max_output_size
2439 result_size = max_output_size
2441 output_size = 0
2440 output_size = 0
2442 else:
2441 else:
2443 result_buffer = ffi.new("char[]", output_size)
2442 result_buffer = ffi.new("char[]", output_size)
2444 result_size = output_size
2443 result_size = output_size
2445
2444
2446 out_buffer = ffi.new("ZSTD_outBuffer *")
2445 out_buffer = ffi.new("ZSTD_outBuffer *")
2447 out_buffer.dst = result_buffer
2446 out_buffer.dst = result_buffer
2448 out_buffer.size = result_size
2447 out_buffer.size = result_size
2449 out_buffer.pos = 0
2448 out_buffer.pos = 0
2450
2449
2451 in_buffer = ffi.new("ZSTD_inBuffer *")
2450 in_buffer = ffi.new("ZSTD_inBuffer *")
2452 in_buffer.src = data_buffer
2451 in_buffer.src = data_buffer
2453 in_buffer.size = len(data_buffer)
2452 in_buffer.size = len(data_buffer)
2454 in_buffer.pos = 0
2453 in_buffer.pos = 0
2455
2454
2456 zresult = lib.ZSTD_decompressStream(self._dctx, out_buffer, in_buffer)
2455 zresult = lib.ZSTD_decompressStream(self._dctx, out_buffer, in_buffer)
2457 if lib.ZSTD_isError(zresult):
2456 if lib.ZSTD_isError(zresult):
2458 raise ZstdError("decompression error: %s" % _zstd_error(zresult))
2457 raise ZstdError("decompression error: %s" % _zstd_error(zresult))
2459 elif zresult:
2458 elif zresult:
2460 raise ZstdError(
2459 raise ZstdError(
2461 "decompression error: did not decompress full frame"
2460 "decompression error: did not decompress full frame"
2462 )
2461 )
2463 elif output_size and out_buffer.pos != output_size:
2462 elif output_size and out_buffer.pos != output_size:
2464 raise ZstdError(
2463 raise ZstdError(
2465 "decompression error: decompressed %d bytes; expected %d"
2464 "decompression error: decompressed %d bytes; expected %d"
2466 % (zresult, output_size)
2465 % (zresult, output_size)
2467 )
2466 )
2468
2467
2469 return ffi.buffer(result_buffer, out_buffer.pos)[:]
2468 return ffi.buffer(result_buffer, out_buffer.pos)[:]
2470
2469
2471 def stream_reader(
2470 def stream_reader(
2472 self,
2471 self,
2473 source,
2472 source,
2474 read_size=DECOMPRESSION_RECOMMENDED_INPUT_SIZE,
2473 read_size=DECOMPRESSION_RECOMMENDED_INPUT_SIZE,
2475 read_across_frames=False,
2474 read_across_frames=False,
2476 ):
2475 ):
2477 self._ensure_dctx()
2476 self._ensure_dctx()
2478 return ZstdDecompressionReader(
2477 return ZstdDecompressionReader(
2479 self, source, read_size, read_across_frames
2478 self, source, read_size, read_across_frames
2480 )
2479 )
2481
2480
2482 def decompressobj(self, write_size=DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE):
2481 def decompressobj(self, write_size=DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE):
2483 if write_size < 1:
2482 if write_size < 1:
2484 raise ValueError("write_size must be positive")
2483 raise ValueError("write_size must be positive")
2485
2484
2486 self._ensure_dctx()
2485 self._ensure_dctx()
2487 return ZstdDecompressionObj(self, write_size=write_size)
2486 return ZstdDecompressionObj(self, write_size=write_size)
2488
2487
2489 def read_to_iter(
2488 def read_to_iter(
2490 self,
2489 self,
2491 reader,
2490 reader,
2492 read_size=DECOMPRESSION_RECOMMENDED_INPUT_SIZE,
2491 read_size=DECOMPRESSION_RECOMMENDED_INPUT_SIZE,
2493 write_size=DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE,
2492 write_size=DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE,
2494 skip_bytes=0,
2493 skip_bytes=0,
2495 ):
2494 ):
2496 if skip_bytes >= read_size:
2495 if skip_bytes >= read_size:
2497 raise ValueError("skip_bytes must be smaller than read_size")
2496 raise ValueError("skip_bytes must be smaller than read_size")
2498
2497
2499 if hasattr(reader, "read"):
2498 if hasattr(reader, "read"):
2500 have_read = True
2499 have_read = True
2501 elif hasattr(reader, "__getitem__"):
2500 elif hasattr(reader, "__getitem__"):
2502 have_read = False
2501 have_read = False
2503 buffer_offset = 0
2502 buffer_offset = 0
2504 size = len(reader)
2503 size = len(reader)
2505 else:
2504 else:
2506 raise ValueError(
2505 raise ValueError(
2507 "must pass an object with a read() method or "
2506 "must pass an object with a read() method or "
2508 "conforms to buffer protocol"
2507 "conforms to buffer protocol"
2509 )
2508 )
2510
2509
2511 if skip_bytes:
2510 if skip_bytes:
2512 if have_read:
2511 if have_read:
2513 reader.read(skip_bytes)
2512 reader.read(skip_bytes)
2514 else:
2513 else:
2515 if skip_bytes > size:
2514 if skip_bytes > size:
2516 raise ValueError("skip_bytes larger than first input chunk")
2515 raise ValueError("skip_bytes larger than first input chunk")
2517
2516
2518 buffer_offset = skip_bytes
2517 buffer_offset = skip_bytes
2519
2518
2520 self._ensure_dctx()
2519 self._ensure_dctx()
2521
2520
2522 in_buffer = ffi.new("ZSTD_inBuffer *")
2521 in_buffer = ffi.new("ZSTD_inBuffer *")
2523 out_buffer = ffi.new("ZSTD_outBuffer *")
2522 out_buffer = ffi.new("ZSTD_outBuffer *")
2524
2523
2525 dst_buffer = ffi.new("char[]", write_size)
2524 dst_buffer = ffi.new("char[]", write_size)
2526 out_buffer.dst = dst_buffer
2525 out_buffer.dst = dst_buffer
2527 out_buffer.size = len(dst_buffer)
2526 out_buffer.size = len(dst_buffer)
2528 out_buffer.pos = 0
2527 out_buffer.pos = 0
2529
2528
2530 while True:
2529 while True:
2531 assert out_buffer.pos == 0
2530 assert out_buffer.pos == 0
2532
2531
2533 if have_read:
2532 if have_read:
2534 read_result = reader.read(read_size)
2533 read_result = reader.read(read_size)
2535 else:
2534 else:
2536 remaining = size - buffer_offset
2535 remaining = size - buffer_offset
2537 slice_size = min(remaining, read_size)
2536 slice_size = min(remaining, read_size)
2538 read_result = reader[buffer_offset : buffer_offset + slice_size]
2537 read_result = reader[buffer_offset : buffer_offset + slice_size]
2539 buffer_offset += slice_size
2538 buffer_offset += slice_size
2540
2539
2541 # No new input. Break out of read loop.
2540 # No new input. Break out of read loop.
2542 if not read_result:
2541 if not read_result:
2543 break
2542 break
2544
2543
2545 # Feed all read data into decompressor and emit output until
2544 # Feed all read data into decompressor and emit output until
2546 # exhausted.
2545 # exhausted.
2547 read_buffer = ffi.from_buffer(read_result)
2546 read_buffer = ffi.from_buffer(read_result)
2548 in_buffer.src = read_buffer
2547 in_buffer.src = read_buffer
2549 in_buffer.size = len(read_buffer)
2548 in_buffer.size = len(read_buffer)
2550 in_buffer.pos = 0
2549 in_buffer.pos = 0
2551
2550
2552 while in_buffer.pos < in_buffer.size:
2551 while in_buffer.pos < in_buffer.size:
2553 assert out_buffer.pos == 0
2552 assert out_buffer.pos == 0
2554
2553
2555 zresult = lib.ZSTD_decompressStream(
2554 zresult = lib.ZSTD_decompressStream(
2556 self._dctx, out_buffer, in_buffer
2555 self._dctx, out_buffer, in_buffer
2557 )
2556 )
2558 if lib.ZSTD_isError(zresult):
2557 if lib.ZSTD_isError(zresult):
2559 raise ZstdError(
2558 raise ZstdError(
2560 "zstd decompress error: %s" % _zstd_error(zresult)
2559 "zstd decompress error: %s" % _zstd_error(zresult)
2561 )
2560 )
2562
2561
2563 if out_buffer.pos:
2562 if out_buffer.pos:
2564 data = ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
2563 data = ffi.buffer(out_buffer.dst, out_buffer.pos)[:]
2565 out_buffer.pos = 0
2564 out_buffer.pos = 0
2566 yield data
2565 yield data
2567
2566
2568 if zresult == 0:
2567 if zresult == 0:
2569 return
2568 return
2570
2569
2571 # Repeat loop to collect more input data.
2570 # Repeat loop to collect more input data.
2572 continue
2571 continue
2573
2572
2574 # If we get here, input is exhausted.
2573 # If we get here, input is exhausted.
2575
2574
2576 read_from = read_to_iter
2575 read_from = read_to_iter
2577
2576
2578 def stream_writer(
2577 def stream_writer(
2579 self,
2578 self,
2580 writer,
2579 writer,
2581 write_size=DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE,
2580 write_size=DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE,
2582 write_return_read=False,
2581 write_return_read=False,
2583 ):
2582 ):
2584 if not hasattr(writer, "write"):
2583 if not hasattr(writer, "write"):
2585 raise ValueError("must pass an object with a write() method")
2584 raise ValueError("must pass an object with a write() method")
2586
2585
2587 return ZstdDecompressionWriter(
2586 return ZstdDecompressionWriter(
2588 self, writer, write_size, write_return_read
2587 self, writer, write_size, write_return_read
2589 )
2588 )
2590
2589
2591 write_to = stream_writer
2590 write_to = stream_writer
2592
2591
2593 def copy_stream(
2592 def copy_stream(
2594 self,
2593 self,
2595 ifh,
2594 ifh,
2596 ofh,
2595 ofh,
2597 read_size=DECOMPRESSION_RECOMMENDED_INPUT_SIZE,
2596 read_size=DECOMPRESSION_RECOMMENDED_INPUT_SIZE,
2598 write_size=DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE,
2597 write_size=DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE,
2599 ):
2598 ):
2600 if not hasattr(ifh, "read"):
2599 if not hasattr(ifh, "read"):
2601 raise ValueError("first argument must have a read() method")
2600 raise ValueError("first argument must have a read() method")
2602 if not hasattr(ofh, "write"):
2601 if not hasattr(ofh, "write"):
2603 raise ValueError("second argument must have a write() method")
2602 raise ValueError("second argument must have a write() method")
2604
2603
2605 self._ensure_dctx()
2604 self._ensure_dctx()
2606
2605
2607 in_buffer = ffi.new("ZSTD_inBuffer *")
2606 in_buffer = ffi.new("ZSTD_inBuffer *")
2608 out_buffer = ffi.new("ZSTD_outBuffer *")
2607 out_buffer = ffi.new("ZSTD_outBuffer *")
2609
2608
2610 dst_buffer = ffi.new("char[]", write_size)
2609 dst_buffer = ffi.new("char[]", write_size)
2611 out_buffer.dst = dst_buffer
2610 out_buffer.dst = dst_buffer
2612 out_buffer.size = write_size
2611 out_buffer.size = write_size
2613 out_buffer.pos = 0
2612 out_buffer.pos = 0
2614
2613
2615 total_read, total_write = 0, 0
2614 total_read, total_write = 0, 0
2616
2615
2617 # Read all available input.
2616 # Read all available input.
2618 while True:
2617 while True:
2619 data = ifh.read(read_size)
2618 data = ifh.read(read_size)
2620 if not data:
2619 if not data:
2621 break
2620 break
2622
2621
2623 data_buffer = ffi.from_buffer(data)
2622 data_buffer = ffi.from_buffer(data)
2624 total_read += len(data_buffer)
2623 total_read += len(data_buffer)
2625 in_buffer.src = data_buffer
2624 in_buffer.src = data_buffer
2626 in_buffer.size = len(data_buffer)
2625 in_buffer.size = len(data_buffer)
2627 in_buffer.pos = 0
2626 in_buffer.pos = 0
2628
2627
2629 # Flush all read data to output.
2628 # Flush all read data to output.
2630 while in_buffer.pos < in_buffer.size:
2629 while in_buffer.pos < in_buffer.size:
2631 zresult = lib.ZSTD_decompressStream(
2630 zresult = lib.ZSTD_decompressStream(
2632 self._dctx, out_buffer, in_buffer
2631 self._dctx, out_buffer, in_buffer
2633 )
2632 )
2634 if lib.ZSTD_isError(zresult):
2633 if lib.ZSTD_isError(zresult):
2635 raise ZstdError(
2634 raise ZstdError(
2636 "zstd decompressor error: %s" % _zstd_error(zresult)
2635 "zstd decompressor error: %s" % _zstd_error(zresult)
2637 )
2636 )
2638
2637
2639 if out_buffer.pos:
2638 if out_buffer.pos:
2640 ofh.write(ffi.buffer(out_buffer.dst, out_buffer.pos))
2639 ofh.write(ffi.buffer(out_buffer.dst, out_buffer.pos))
2641 total_write += out_buffer.pos
2640 total_write += out_buffer.pos
2642 out_buffer.pos = 0
2641 out_buffer.pos = 0
2643
2642
2644 # Continue loop to keep reading.
2643 # Continue loop to keep reading.
2645
2644
2646 return total_read, total_write
2645 return total_read, total_write
2647
2646
2648 def decompress_content_dict_chain(self, frames):
2647 def decompress_content_dict_chain(self, frames):
2649 if not isinstance(frames, list):
2648 if not isinstance(frames, list):
2650 raise TypeError("argument must be a list")
2649 raise TypeError("argument must be a list")
2651
2650
2652 if not frames:
2651 if not frames:
2653 raise ValueError("empty input chain")
2652 raise ValueError("empty input chain")
2654
2653
2655 # First chunk should not be using a dictionary. We handle it specially.
2654 # First chunk should not be using a dictionary. We handle it specially.
2656 chunk = frames[0]
2655 chunk = frames[0]
2657 if not isinstance(chunk, bytes_type):
2656 if not isinstance(chunk, bytes_type):
2658 raise ValueError("chunk 0 must be bytes")
2657 raise ValueError("chunk 0 must be bytes")
2659
2658
2660 # All chunks should be zstd frames and should have content size set.
2659 # All chunks should be zstd frames and should have content size set.
2661 chunk_buffer = ffi.from_buffer(chunk)
2660 chunk_buffer = ffi.from_buffer(chunk)
2662 params = ffi.new("ZSTD_frameHeader *")
2661 params = ffi.new("ZSTD_frameHeader *")
2663 zresult = lib.ZSTD_getFrameHeader(
2662 zresult = lib.ZSTD_getFrameHeader(
2664 params, chunk_buffer, len(chunk_buffer)
2663 params, chunk_buffer, len(chunk_buffer)
2665 )
2664 )
2666 if lib.ZSTD_isError(zresult):
2665 if lib.ZSTD_isError(zresult):
2667 raise ValueError("chunk 0 is not a valid zstd frame")
2666 raise ValueError("chunk 0 is not a valid zstd frame")
2668 elif zresult:
2667 elif zresult:
2669 raise ValueError("chunk 0 is too small to contain a zstd frame")
2668 raise ValueError("chunk 0 is too small to contain a zstd frame")
2670
2669
2671 if params.frameContentSize == lib.ZSTD_CONTENTSIZE_UNKNOWN:
2670 if params.frameContentSize == lib.ZSTD_CONTENTSIZE_UNKNOWN:
2672 raise ValueError("chunk 0 missing content size in frame")
2671 raise ValueError("chunk 0 missing content size in frame")
2673
2672
2674 self._ensure_dctx(load_dict=False)
2673 self._ensure_dctx(load_dict=False)
2675
2674
2676 last_buffer = ffi.new("char[]", params.frameContentSize)
2675 last_buffer = ffi.new("char[]", params.frameContentSize)
2677
2676
2678 out_buffer = ffi.new("ZSTD_outBuffer *")
2677 out_buffer = ffi.new("ZSTD_outBuffer *")
2679 out_buffer.dst = last_buffer
2678 out_buffer.dst = last_buffer
2680 out_buffer.size = len(last_buffer)
2679 out_buffer.size = len(last_buffer)
2681 out_buffer.pos = 0
2680 out_buffer.pos = 0
2682
2681
2683 in_buffer = ffi.new("ZSTD_inBuffer *")
2682 in_buffer = ffi.new("ZSTD_inBuffer *")
2684 in_buffer.src = chunk_buffer
2683 in_buffer.src = chunk_buffer
2685 in_buffer.size = len(chunk_buffer)
2684 in_buffer.size = len(chunk_buffer)
2686 in_buffer.pos = 0
2685 in_buffer.pos = 0
2687
2686
2688 zresult = lib.ZSTD_decompressStream(self._dctx, out_buffer, in_buffer)
2687 zresult = lib.ZSTD_decompressStream(self._dctx, out_buffer, in_buffer)
2689 if lib.ZSTD_isError(zresult):
2688 if lib.ZSTD_isError(zresult):
2690 raise ZstdError(
2689 raise ZstdError(
2691 "could not decompress chunk 0: %s" % _zstd_error(zresult)
2690 "could not decompress chunk 0: %s" % _zstd_error(zresult)
2692 )
2691 )
2693 elif zresult:
2692 elif zresult:
2694 raise ZstdError("chunk 0 did not decompress full frame")
2693 raise ZstdError("chunk 0 did not decompress full frame")
2695
2694
2696 # Special case of chain length of 1
2695 # Special case of chain length of 1
2697 if len(frames) == 1:
2696 if len(frames) == 1:
2698 return ffi.buffer(last_buffer, len(last_buffer))[:]
2697 return ffi.buffer(last_buffer, len(last_buffer))[:]
2699
2698
2700 i = 1
2699 i = 1
2701 while i < len(frames):
2700 while i < len(frames):
2702 chunk = frames[i]
2701 chunk = frames[i]
2703 if not isinstance(chunk, bytes_type):
2702 if not isinstance(chunk, bytes_type):
2704 raise ValueError("chunk %d must be bytes" % i)
2703 raise ValueError("chunk %d must be bytes" % i)
2705
2704
2706 chunk_buffer = ffi.from_buffer(chunk)
2705 chunk_buffer = ffi.from_buffer(chunk)
2707 zresult = lib.ZSTD_getFrameHeader(
2706 zresult = lib.ZSTD_getFrameHeader(
2708 params, chunk_buffer, len(chunk_buffer)
2707 params, chunk_buffer, len(chunk_buffer)
2709 )
2708 )
2710 if lib.ZSTD_isError(zresult):
2709 if lib.ZSTD_isError(zresult):
2711 raise ValueError("chunk %d is not a valid zstd frame" % i)
2710 raise ValueError("chunk %d is not a valid zstd frame" % i)
2712 elif zresult:
2711 elif zresult:
2713 raise ValueError(
2712 raise ValueError(
2714 "chunk %d is too small to contain a zstd frame" % i
2713 "chunk %d is too small to contain a zstd frame" % i
2715 )
2714 )
2716
2715
2717 if params.frameContentSize == lib.ZSTD_CONTENTSIZE_UNKNOWN:
2716 if params.frameContentSize == lib.ZSTD_CONTENTSIZE_UNKNOWN:
2718 raise ValueError("chunk %d missing content size in frame" % i)
2717 raise ValueError("chunk %d missing content size in frame" % i)
2719
2718
2720 dest_buffer = ffi.new("char[]", params.frameContentSize)
2719 dest_buffer = ffi.new("char[]", params.frameContentSize)
2721
2720
2722 out_buffer.dst = dest_buffer
2721 out_buffer.dst = dest_buffer
2723 out_buffer.size = len(dest_buffer)
2722 out_buffer.size = len(dest_buffer)
2724 out_buffer.pos = 0
2723 out_buffer.pos = 0
2725
2724
2726 in_buffer.src = chunk_buffer
2725 in_buffer.src = chunk_buffer
2727 in_buffer.size = len(chunk_buffer)
2726 in_buffer.size = len(chunk_buffer)
2728 in_buffer.pos = 0
2727 in_buffer.pos = 0
2729
2728
2730 zresult = lib.ZSTD_decompressStream(
2729 zresult = lib.ZSTD_decompressStream(
2731 self._dctx, out_buffer, in_buffer
2730 self._dctx, out_buffer, in_buffer
2732 )
2731 )
2733 if lib.ZSTD_isError(zresult):
2732 if lib.ZSTD_isError(zresult):
2734 raise ZstdError(
2733 raise ZstdError(
2735 "could not decompress chunk %d: %s" % _zstd_error(zresult)
2734 "could not decompress chunk %d: %s" % _zstd_error(zresult)
2736 )
2735 )
2737 elif zresult:
2736 elif zresult:
2738 raise ZstdError("chunk %d did not decompress full frame" % i)
2737 raise ZstdError("chunk %d did not decompress full frame" % i)
2739
2738
2740 last_buffer = dest_buffer
2739 last_buffer = dest_buffer
2741 i += 1
2740 i += 1
2742
2741
2743 return ffi.buffer(last_buffer, len(last_buffer))[:]
2742 return ffi.buffer(last_buffer, len(last_buffer))[:]
2744
2743
2745 def _ensure_dctx(self, load_dict=True):
2744 def _ensure_dctx(self, load_dict=True):
2746 lib.ZSTD_DCtx_reset(self._dctx, lib.ZSTD_reset_session_only)
2745 lib.ZSTD_DCtx_reset(self._dctx, lib.ZSTD_reset_session_only)
2747
2746
2748 if self._max_window_size:
2747 if self._max_window_size:
2749 zresult = lib.ZSTD_DCtx_setMaxWindowSize(
2748 zresult = lib.ZSTD_DCtx_setMaxWindowSize(
2750 self._dctx, self._max_window_size
2749 self._dctx, self._max_window_size
2751 )
2750 )
2752 if lib.ZSTD_isError(zresult):
2751 if lib.ZSTD_isError(zresult):
2753 raise ZstdError(
2752 raise ZstdError(
2754 "unable to set max window size: %s" % _zstd_error(zresult)
2753 "unable to set max window size: %s" % _zstd_error(zresult)
2755 )
2754 )
2756
2755
2757 zresult = lib.ZSTD_DCtx_setFormat(self._dctx, self._format)
2756 zresult = lib.ZSTD_DCtx_setFormat(self._dctx, self._format)
2758 if lib.ZSTD_isError(zresult):
2757 if lib.ZSTD_isError(zresult):
2759 raise ZstdError(
2758 raise ZstdError(
2760 "unable to set decoding format: %s" % _zstd_error(zresult)
2759 "unable to set decoding format: %s" % _zstd_error(zresult)
2761 )
2760 )
2762
2761
2763 if self._dict_data and load_dict:
2762 if self._dict_data and load_dict:
2764 zresult = lib.ZSTD_DCtx_refDDict(self._dctx, self._dict_data._ddict)
2763 zresult = lib.ZSTD_DCtx_refDDict(self._dctx, self._dict_data._ddict)
2765 if lib.ZSTD_isError(zresult):
2764 if lib.ZSTD_isError(zresult):
2766 raise ZstdError(
2765 raise ZstdError(
2767 "unable to reference prepared dictionary: %s"
2766 "unable to reference prepared dictionary: %s"
2768 % _zstd_error(zresult)
2767 % _zstd_error(zresult)
2769 )
2768 )
@@ -1,163 +1,161 b''
1 # Copyright 2012 Facebook
1 # Copyright 2012 Facebook
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5 """Find tests that newly pass under Python 3.
5 """Find tests that newly pass under Python 3.
6
6
7 The approach is simple: we maintain a whitelist of Python 3 passing
7 The approach is simple: we maintain a whitelist of Python 3 passing
8 tests in the repository, and periodically run all the /other/ tests
8 tests in the repository, and periodically run all the /other/ tests
9 and look for new passes. Any newly passing tests get automatically
9 and look for new passes. Any newly passing tests get automatically
10 added to the whitelist.
10 added to the whitelist.
11
11
12 You probably want to run it like this:
12 You probably want to run it like this:
13
13
14 $ cd tests
14 $ cd tests
15 $ python3 ../contrib/python3-ratchet.py \
15 $ python3 ../contrib/python3-ratchet.py \
16 > --working-tests=../contrib/python3-whitelist
16 > --working-tests=../contrib/python3-whitelist
17 """
17 """
18 from __future__ import print_function
19 from __future__ import absolute_import
20
18
21 import argparse
19 import argparse
22 import json
20 import json
23 import os
21 import os
24 import subprocess
22 import subprocess
25 import sys
23 import sys
26
24
27 _hgenv = dict(os.environ)
25 _hgenv = dict(os.environ)
28 _hgenv.update(
26 _hgenv.update(
29 {
27 {
30 'HGPLAIN': '1',
28 'HGPLAIN': '1',
31 }
29 }
32 )
30 )
33
31
34 _HG_FIRST_CHANGE = '9117c6561b0bd7792fa13b50d28239d51b78e51f'
32 _HG_FIRST_CHANGE = '9117c6561b0bd7792fa13b50d28239d51b78e51f'
35
33
36
34
37 def _runhg(*args):
35 def _runhg(*args):
38 return subprocess.check_output(args, env=_hgenv)
36 return subprocess.check_output(args, env=_hgenv)
39
37
40
38
41 def _is_hg_repo(path):
39 def _is_hg_repo(path):
42 return (
40 return (
43 _runhg('hg', 'log', '-R', path, '-r0', '--template={node}').strip()
41 _runhg('hg', 'log', '-R', path, '-r0', '--template={node}').strip()
44 == _HG_FIRST_CHANGE
42 == _HG_FIRST_CHANGE
45 )
43 )
46
44
47
45
48 def _py3default():
46 def _py3default():
49 if sys.version_info[0] >= 3:
47 if sys.version_info[0] >= 3:
50 return sys.executable
48 return sys.executable
51 return 'python3'
49 return 'python3'
52
50
53
51
54 def main(argv=()):
52 def main(argv=()):
55 p = argparse.ArgumentParser()
53 p = argparse.ArgumentParser()
56 p.add_argument(
54 p.add_argument(
57 '--working-tests', help='List of tests that already work in Python 3.'
55 '--working-tests', help='List of tests that already work in Python 3.'
58 )
56 )
59 p.add_argument(
57 p.add_argument(
60 '--commit-to-repo',
58 '--commit-to-repo',
61 help='If set, commit newly fixed tests to the given repo',
59 help='If set, commit newly fixed tests to the given repo',
62 )
60 )
63 p.add_argument(
61 p.add_argument(
64 '-j',
62 '-j',
65 default=os.sysconf('SC_NPROCESSORS_ONLN'),
63 default=os.sysconf('SC_NPROCESSORS_ONLN'),
66 type=int,
64 type=int,
67 help='Number of parallel tests to run.',
65 help='Number of parallel tests to run.',
68 )
66 )
69 p.add_argument(
67 p.add_argument(
70 '--python3',
68 '--python3',
71 default=_py3default(),
69 default=_py3default(),
72 help='python3 interpreter to use for test run',
70 help='python3 interpreter to use for test run',
73 )
71 )
74 p.add_argument(
72 p.add_argument(
75 '--commit-user',
73 '--commit-user',
76 default='python3-ratchet@mercurial-scm.org',
74 default='python3-ratchet@mercurial-scm.org',
77 help='Username to specify when committing to a repo.',
75 help='Username to specify when committing to a repo.',
78 )
76 )
79 opts = p.parse_args(argv)
77 opts = p.parse_args(argv)
80 if opts.commit_to_repo:
78 if opts.commit_to_repo:
81 if not _is_hg_repo(opts.commit_to_repo):
79 if not _is_hg_repo(opts.commit_to_repo):
82 print('abort: specified repository is not the hg repository')
80 print('abort: specified repository is not the hg repository')
83 sys.exit(1)
81 sys.exit(1)
84 if not opts.working_tests or not os.path.isfile(opts.working_tests):
82 if not opts.working_tests or not os.path.isfile(opts.working_tests):
85 print(
83 print(
86 'abort: --working-tests must exist and be a file (got %r)'
84 'abort: --working-tests must exist and be a file (got %r)'
87 % opts.working_tests
85 % opts.working_tests
88 )
86 )
89 sys.exit(1)
87 sys.exit(1)
90 elif opts.commit_to_repo:
88 elif opts.commit_to_repo:
91 root = _runhg('hg', 'root').strip()
89 root = _runhg('hg', 'root').strip()
92 if not opts.working_tests.startswith(root):
90 if not opts.working_tests.startswith(root):
93 print(
91 print(
94 'abort: if --commit-to-repo is given, '
92 'abort: if --commit-to-repo is given, '
95 '--working-tests must be from that repo'
93 '--working-tests must be from that repo'
96 )
94 )
97 sys.exit(1)
95 sys.exit(1)
98 try:
96 try:
99 subprocess.check_call(
97 subprocess.check_call(
100 [
98 [
101 opts.python3,
99 opts.python3,
102 '-c',
100 '-c',
103 'import sys ; '
101 'import sys ; '
104 'assert ((3, 5) <= sys.version_info < (3, 6) '
102 'assert ((3, 5) <= sys.version_info < (3, 6) '
105 'or sys.version_info >= (3, 6, 2))',
103 'or sys.version_info >= (3, 6, 2))',
106 ]
104 ]
107 )
105 )
108 except subprocess.CalledProcessError:
106 except subprocess.CalledProcessError:
109 print(
107 print(
110 'warning: Python 3.6.0 and 3.6.1 have '
108 'warning: Python 3.6.0 and 3.6.1 have '
111 'a bug which breaks Mercurial'
109 'a bug which breaks Mercurial'
112 )
110 )
113 print('(see https://bugs.python.org/issue29714 for details)')
111 print('(see https://bugs.python.org/issue29714 for details)')
114 sys.exit(1)
112 sys.exit(1)
115
113
116 rt = subprocess.Popen(
114 rt = subprocess.Popen(
117 [
115 [
118 opts.python3,
116 opts.python3,
119 'run-tests.py',
117 'run-tests.py',
120 '-j',
118 '-j',
121 str(opts.j),
119 str(opts.j),
122 '--blacklist',
120 '--blacklist',
123 opts.working_tests,
121 opts.working_tests,
124 '--json',
122 '--json',
125 ]
123 ]
126 )
124 )
127 rt.wait()
125 rt.wait()
128 with open('report.json') as f:
126 with open('report.json') as f:
129 data = f.read()
127 data = f.read()
130 report = json.loads(data.split('=', 1)[1])
128 report = json.loads(data.split('=', 1)[1])
131 newpass = set()
129 newpass = set()
132 for test, result in report.items():
130 for test, result in report.items():
133 if result['result'] != 'success':
131 if result['result'] != 'success':
134 continue
132 continue
135 # A new passing test! Huzzah!
133 # A new passing test! Huzzah!
136 newpass.add(test)
134 newpass.add(test)
137 if newpass:
135 if newpass:
138 # We already validated the repo, so we can just dive right in
136 # We already validated the repo, so we can just dive right in
139 # and commit.
137 # and commit.
140 if opts.commit_to_repo:
138 if opts.commit_to_repo:
141 print(len(newpass), 'new passing tests on Python 3!')
139 print(len(newpass), 'new passing tests on Python 3!')
142 with open(opts.working_tests) as f:
140 with open(opts.working_tests) as f:
143 oldpass = {l for l in f.read().splitlines() if l}
141 oldpass = {l for l in f.read().splitlines() if l}
144 with open(opts.working_tests, 'w') as f:
142 with open(opts.working_tests, 'w') as f:
145 for p in sorted(oldpass | newpass):
143 for p in sorted(oldpass | newpass):
146 f.write('%s\n' % p)
144 f.write('%s\n' % p)
147 _runhg(
145 _runhg(
148 'hg',
146 'hg',
149 'commit',
147 'commit',
150 '-R',
148 '-R',
151 opts.commit_to_repo,
149 opts.commit_to_repo,
152 '--user',
150 '--user',
153 opts.commit_user,
151 opts.commit_user,
154 '--message',
152 '--message',
155 'python3: expand list of passing tests',
153 'python3: expand list of passing tests',
156 )
154 )
157 else:
155 else:
158 print('Newly passing tests:', '\n'.join(sorted(newpass)))
156 print('Newly passing tests:', '\n'.join(sorted(newpass)))
159 sys.exit(2)
157 sys.exit(2)
160
158
161
159
162 if __name__ == '__main__':
160 if __name__ == '__main__':
163 main(sys.argv[1:])
161 main(sys.argv[1:])
@@ -1,389 +1,388 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2
2
3 # Measure the performance of a list of revsets against multiple revisions
3 # Measure the performance of a list of revsets against multiple revisions
4 # defined by parameter. Checkout one by one and run perfrevset with every
4 # defined by parameter. Checkout one by one and run perfrevset with every
5 # revset in the list to benchmark its performance.
5 # revset in the list to benchmark its performance.
6 #
6 #
7 # You should run this from the root of your mercurial repository.
7 # You should run this from the root of your mercurial repository.
8 #
8 #
9 # call with --help for details
9 # call with --help for details
10
10
11 from __future__ import absolute_import, print_function
12 import math
11 import math
13 import optparse # cannot use argparse, python 2.7 only
12 import optparse # cannot use argparse, python 2.7 only
14 import os
13 import os
15 import re
14 import re
16 import subprocess
15 import subprocess
17 import sys
16 import sys
18
17
19 DEFAULTVARIANTS = [
18 DEFAULTVARIANTS = [
20 'plain',
19 'plain',
21 'min',
20 'min',
22 'max',
21 'max',
23 'first',
22 'first',
24 'last',
23 'last',
25 'reverse',
24 'reverse',
26 'reverse+first',
25 'reverse+first',
27 'reverse+last',
26 'reverse+last',
28 'sort',
27 'sort',
29 'sort+first',
28 'sort+first',
30 'sort+last',
29 'sort+last',
31 ]
30 ]
32
31
33
32
34 def check_output(*args, **kwargs):
33 def check_output(*args, **kwargs):
35 kwargs.setdefault('stderr', subprocess.PIPE)
34 kwargs.setdefault('stderr', subprocess.PIPE)
36 kwargs.setdefault('stdout', subprocess.PIPE)
35 kwargs.setdefault('stdout', subprocess.PIPE)
37 proc = subprocess.Popen(*args, **kwargs)
36 proc = subprocess.Popen(*args, **kwargs)
38 output, error = proc.communicate()
37 output, error = proc.communicate()
39 if proc.returncode != 0:
38 if proc.returncode != 0:
40 raise subprocess.CalledProcessError(proc.returncode, ' '.join(args[0]))
39 raise subprocess.CalledProcessError(proc.returncode, ' '.join(args[0]))
41 return output
40 return output
42
41
43
42
44 def update(rev):
43 def update(rev):
45 """update the repo to a revision"""
44 """update the repo to a revision"""
46 try:
45 try:
47 subprocess.check_call(['hg', 'update', '--quiet', '--check', str(rev)])
46 subprocess.check_call(['hg', 'update', '--quiet', '--check', str(rev)])
48 check_output(
47 check_output(
49 ['make', 'local'], stderr=None
48 ['make', 'local'], stderr=None
50 ) # suppress output except for error/warning
49 ) # suppress output except for error/warning
51 except subprocess.CalledProcessError as exc:
50 except subprocess.CalledProcessError as exc:
52 print('update to revision %s failed, aborting' % rev, file=sys.stderr)
51 print('update to revision %s failed, aborting' % rev, file=sys.stderr)
53 sys.exit(exc.returncode)
52 sys.exit(exc.returncode)
54
53
55
54
56 def hg(cmd, repo=None):
55 def hg(cmd, repo=None):
57 """run a mercurial command
56 """run a mercurial command
58
57
59 <cmd> is the list of command + argument,
58 <cmd> is the list of command + argument,
60 <repo> is an optional repository path to run this command in."""
59 <repo> is an optional repository path to run this command in."""
61 fullcmd = ['./hg']
60 fullcmd = ['./hg']
62 if repo is not None:
61 if repo is not None:
63 fullcmd += ['-R', repo]
62 fullcmd += ['-R', repo]
64 fullcmd += [
63 fullcmd += [
65 '--config',
64 '--config',
66 'extensions.perf=' + os.path.join(contribdir, 'perf.py'),
65 'extensions.perf=' + os.path.join(contribdir, 'perf.py'),
67 ]
66 ]
68 fullcmd += cmd
67 fullcmd += cmd
69 return check_output(fullcmd, stderr=subprocess.STDOUT)
68 return check_output(fullcmd, stderr=subprocess.STDOUT)
70
69
71
70
72 def perf(revset, target=None, contexts=False):
71 def perf(revset, target=None, contexts=False):
73 """run benchmark for this very revset"""
72 """run benchmark for this very revset"""
74 try:
73 try:
75 args = ['perfrevset']
74 args = ['perfrevset']
76 if contexts:
75 if contexts:
77 args.append('--contexts')
76 args.append('--contexts')
78 args.append('--')
77 args.append('--')
79 args.append(revset)
78 args.append(revset)
80 output = hg(args, repo=target)
79 output = hg(args, repo=target)
81 return parseoutput(output)
80 return parseoutput(output)
82 except subprocess.CalledProcessError as exc:
81 except subprocess.CalledProcessError as exc:
83 print(
82 print(
84 'abort: cannot run revset benchmark: %s' % exc.cmd, file=sys.stderr
83 'abort: cannot run revset benchmark: %s' % exc.cmd, file=sys.stderr
85 )
84 )
86 if getattr(exc, 'output', None) is None: # no output before 2.7
85 if getattr(exc, 'output', None) is None: # no output before 2.7
87 print('(no output)', file=sys.stderr)
86 print('(no output)', file=sys.stderr)
88 else:
87 else:
89 print(exc.output, file=sys.stderr)
88 print(exc.output, file=sys.stderr)
90 return None
89 return None
91
90
92
91
93 outputre = re.compile(
92 outputre = re.compile(
94 br'! wall (\d+.\d+) comb (\d+.\d+) user (\d+.\d+) '
93 br'! wall (\d+.\d+) comb (\d+.\d+) user (\d+.\d+) '
95 br'sys (\d+.\d+) \(best of (\d+)\)'
94 br'sys (\d+.\d+) \(best of (\d+)\)'
96 )
95 )
97
96
98
97
99 def parseoutput(output):
98 def parseoutput(output):
100 """parse a textual output into a dict
99 """parse a textual output into a dict
101
100
102 We cannot just use json because we want to compare with old
101 We cannot just use json because we want to compare with old
103 versions of Mercurial that may not support json output.
102 versions of Mercurial that may not support json output.
104 """
103 """
105 match = outputre.search(output)
104 match = outputre.search(output)
106 if not match:
105 if not match:
107 print('abort: invalid output:', file=sys.stderr)
106 print('abort: invalid output:', file=sys.stderr)
108 print(output, file=sys.stderr)
107 print(output, file=sys.stderr)
109 sys.exit(1)
108 sys.exit(1)
110 return {
109 return {
111 'comb': float(match.group(2)),
110 'comb': float(match.group(2)),
112 'count': int(match.group(5)),
111 'count': int(match.group(5)),
113 'sys': float(match.group(3)),
112 'sys': float(match.group(3)),
114 'user': float(match.group(4)),
113 'user': float(match.group(4)),
115 'wall': float(match.group(1)),
114 'wall': float(match.group(1)),
116 }
115 }
117
116
118
117
119 def printrevision(rev):
118 def printrevision(rev):
120 """print data about a revision"""
119 """print data about a revision"""
121 sys.stdout.write("Revision ")
120 sys.stdout.write("Revision ")
122 sys.stdout.flush()
121 sys.stdout.flush()
123 subprocess.check_call(
122 subprocess.check_call(
124 [
123 [
125 'hg',
124 'hg',
126 'log',
125 'log',
127 '--rev',
126 '--rev',
128 str(rev),
127 str(rev),
129 '--template',
128 '--template',
130 '{if(tags, " ({tags})")} ' '{rev}:{node|short}: {desc|firstline}\n',
129 '{if(tags, " ({tags})")} ' '{rev}:{node|short}: {desc|firstline}\n',
131 ]
130 ]
132 )
131 )
133
132
134
133
135 def idxwidth(nbidx):
134 def idxwidth(nbidx):
136 """return the max width of number used for index
135 """return the max width of number used for index
137
136
138 This is similar to log10(nbidx), but we use custom code here
137 This is similar to log10(nbidx), but we use custom code here
139 because we start with zero and we'd rather not deal with all the
138 because we start with zero and we'd rather not deal with all the
140 extra rounding business that log10 would imply.
139 extra rounding business that log10 would imply.
141 """
140 """
142 nbidx -= 1 # starts at 0
141 nbidx -= 1 # starts at 0
143 idxwidth = 0
142 idxwidth = 0
144 while nbidx:
143 while nbidx:
145 idxwidth += 1
144 idxwidth += 1
146 nbidx //= 10
145 nbidx //= 10
147 if not idxwidth:
146 if not idxwidth:
148 idxwidth = 1
147 idxwidth = 1
149 return idxwidth
148 return idxwidth
150
149
151
150
152 def getfactor(main, other, field, sensitivity=0.05):
151 def getfactor(main, other, field, sensitivity=0.05):
153 """return the relative factor between values for 'field' in main and other
152 """return the relative factor between values for 'field' in main and other
154
153
155 Return None if the factor is insignificant (less than <sensitivity>
154 Return None if the factor is insignificant (less than <sensitivity>
156 variation)."""
155 variation)."""
157 factor = 1
156 factor = 1
158 if main is not None:
157 if main is not None:
159 factor = other[field] / main[field]
158 factor = other[field] / main[field]
160 low, high = 1 - sensitivity, 1 + sensitivity
159 low, high = 1 - sensitivity, 1 + sensitivity
161 if low < factor < high:
160 if low < factor < high:
162 return None
161 return None
163 return factor
162 return factor
164
163
165
164
166 def formatfactor(factor):
165 def formatfactor(factor):
167 """format a factor into a 4 char string
166 """format a factor into a 4 char string
168
167
169 22%
168 22%
170 156%
169 156%
171 x2.4
170 x2.4
172 x23
171 x23
173 x789
172 x789
174 x1e4
173 x1e4
175 x5x7
174 x5x7
176
175
177 """
176 """
178 if factor is None:
177 if factor is None:
179 return ' '
178 return ' '
180 elif factor < 2:
179 elif factor < 2:
181 return '%3i%%' % (factor * 100)
180 return '%3i%%' % (factor * 100)
182 elif factor < 10:
181 elif factor < 10:
183 return 'x%3.1f' % factor
182 return 'x%3.1f' % factor
184 elif factor < 1000:
183 elif factor < 1000:
185 return '%4s' % ('x%i' % factor)
184 return '%4s' % ('x%i' % factor)
186 else:
185 else:
187 order = int(math.log(factor)) + 1
186 order = int(math.log(factor)) + 1
188 while math.log(factor) > 1:
187 while math.log(factor) > 1:
189 factor //= 0
188 factor //= 0
190 return 'x%ix%i' % (factor, order)
189 return 'x%ix%i' % (factor, order)
191
190
192
191
193 def formattiming(value):
192 def formattiming(value):
194 """format a value to strictly 8 char, dropping some precision if needed"""
193 """format a value to strictly 8 char, dropping some precision if needed"""
195 if value < 10 ** 7:
194 if value < 10 ** 7:
196 return ('%.6f' % value)[:8]
195 return ('%.6f' % value)[:8]
197 else:
196 else:
198 # value is HUGE very unlikely to happen (4+ month run)
197 # value is HUGE very unlikely to happen (4+ month run)
199 return '%i' % value
198 return '%i' % value
200
199
201
200
202 _marker = object()
201 _marker = object()
203
202
204
203
205 def printresult(variants, idx, data, maxidx, verbose=False, reference=_marker):
204 def printresult(variants, idx, data, maxidx, verbose=False, reference=_marker):
206 """print a line of result to stdout"""
205 """print a line of result to stdout"""
207 mask = '%%0%ii) %%s' % idxwidth(maxidx)
206 mask = '%%0%ii) %%s' % idxwidth(maxidx)
208
207
209 out = []
208 out = []
210 for var in variants:
209 for var in variants:
211 if data[var] is None:
210 if data[var] is None:
212 out.append('error ')
211 out.append('error ')
213 out.append(' ' * 4)
212 out.append(' ' * 4)
214 continue
213 continue
215 out.append(formattiming(data[var]['wall']))
214 out.append(formattiming(data[var]['wall']))
216 if reference is not _marker:
215 if reference is not _marker:
217 factor = None
216 factor = None
218 if reference is not None:
217 if reference is not None:
219 factor = getfactor(reference[var], data[var], 'wall')
218 factor = getfactor(reference[var], data[var], 'wall')
220 out.append(formatfactor(factor))
219 out.append(formatfactor(factor))
221 if verbose:
220 if verbose:
222 out.append(formattiming(data[var]['comb']))
221 out.append(formattiming(data[var]['comb']))
223 out.append(formattiming(data[var]['user']))
222 out.append(formattiming(data[var]['user']))
224 out.append(formattiming(data[var]['sys']))
223 out.append(formattiming(data[var]['sys']))
225 out.append('%6d' % data[var]['count'])
224 out.append('%6d' % data[var]['count'])
226 print(mask % (idx, ' '.join(out)))
225 print(mask % (idx, ' '.join(out)))
227
226
228
227
229 def printheader(variants, maxidx, verbose=False, relative=False):
228 def printheader(variants, maxidx, verbose=False, relative=False):
230 header = [' ' * (idxwidth(maxidx) + 1)]
229 header = [' ' * (idxwidth(maxidx) + 1)]
231 for var in variants:
230 for var in variants:
232 if not var:
231 if not var:
233 var = 'iter'
232 var = 'iter'
234 if len(var) > 8:
233 if len(var) > 8:
235 var = var[:3] + '..' + var[-3:]
234 var = var[:3] + '..' + var[-3:]
236 header.append('%-8s' % var)
235 header.append('%-8s' % var)
237 if relative:
236 if relative:
238 header.append(' ')
237 header.append(' ')
239 if verbose:
238 if verbose:
240 header.append('%-8s' % 'comb')
239 header.append('%-8s' % 'comb')
241 header.append('%-8s' % 'user')
240 header.append('%-8s' % 'user')
242 header.append('%-8s' % 'sys')
241 header.append('%-8s' % 'sys')
243 header.append('%6s' % 'count')
242 header.append('%6s' % 'count')
244 print(' '.join(header))
243 print(' '.join(header))
245
244
246
245
247 def getrevs(spec):
246 def getrevs(spec):
248 """get the list of rev matched by a revset"""
247 """get the list of rev matched by a revset"""
249 try:
248 try:
250 out = check_output(['hg', 'log', '--template={rev}\n', '--rev', spec])
249 out = check_output(['hg', 'log', '--template={rev}\n', '--rev', spec])
251 except subprocess.CalledProcessError as exc:
250 except subprocess.CalledProcessError as exc:
252 print("abort, can't get revision from %s" % spec, file=sys.stderr)
251 print("abort, can't get revision from %s" % spec, file=sys.stderr)
253 sys.exit(exc.returncode)
252 sys.exit(exc.returncode)
254 return [r for r in out.split() if r]
253 return [r for r in out.split() if r]
255
254
256
255
257 def applyvariants(revset, variant):
256 def applyvariants(revset, variant):
258 if variant == 'plain':
257 if variant == 'plain':
259 return revset
258 return revset
260 for var in variant.split('+'):
259 for var in variant.split('+'):
261 revset = '%s(%s)' % (var, revset)
260 revset = '%s(%s)' % (var, revset)
262 return revset
261 return revset
263
262
264
263
265 helptext = """This script will run multiple variants of provided revsets using
264 helptext = """This script will run multiple variants of provided revsets using
266 different revisions in your mercurial repository. After the benchmark are run
265 different revisions in your mercurial repository. After the benchmark are run
267 summary output is provided. Use it to demonstrate speed improvements or pin
266 summary output is provided. Use it to demonstrate speed improvements or pin
268 point regressions. Revsets to run are specified in a file (or from stdin), one
267 point regressions. Revsets to run are specified in a file (or from stdin), one
269 revsets per line. Line starting with '#' will be ignored, allowing insertion of
268 revsets per line. Line starting with '#' will be ignored, allowing insertion of
270 comments."""
269 comments."""
271 parser = optparse.OptionParser(
270 parser = optparse.OptionParser(
272 usage="usage: %prog [options] <revs>", description=helptext
271 usage="usage: %prog [options] <revs>", description=helptext
273 )
272 )
274 parser.add_option(
273 parser.add_option(
275 "-f",
274 "-f",
276 "--file",
275 "--file",
277 help="read revset from FILE (stdin if omitted)",
276 help="read revset from FILE (stdin if omitted)",
278 metavar="FILE",
277 metavar="FILE",
279 )
278 )
280 parser.add_option("-R", "--repo", help="run benchmark on REPO", metavar="REPO")
279 parser.add_option("-R", "--repo", help="run benchmark on REPO", metavar="REPO")
281
280
282 parser.add_option(
281 parser.add_option(
283 "-v",
282 "-v",
284 "--verbose",
283 "--verbose",
285 action='store_true',
284 action='store_true',
286 help="display all timing data (not just best total time)",
285 help="display all timing data (not just best total time)",
287 )
286 )
288
287
289 parser.add_option(
288 parser.add_option(
290 "",
289 "",
291 "--variants",
290 "--variants",
292 default=','.join(DEFAULTVARIANTS),
291 default=','.join(DEFAULTVARIANTS),
293 help="comma separated list of variant to test "
292 help="comma separated list of variant to test "
294 "(eg: plain,min,sorted) (plain = no modification)",
293 "(eg: plain,min,sorted) (plain = no modification)",
295 )
294 )
296 parser.add_option(
295 parser.add_option(
297 '',
296 '',
298 '--contexts',
297 '--contexts',
299 action='store_true',
298 action='store_true',
300 help='obtain changectx from results instead of integer revs',
299 help='obtain changectx from results instead of integer revs',
301 )
300 )
302
301
303 (options, args) = parser.parse_args()
302 (options, args) = parser.parse_args()
304
303
305 if not args:
304 if not args:
306 parser.print_help()
305 parser.print_help()
307 sys.exit(255)
306 sys.exit(255)
308
307
309 # the directory where both this script and the perf.py extension live.
308 # the directory where both this script and the perf.py extension live.
310 contribdir = os.path.dirname(__file__)
309 contribdir = os.path.dirname(__file__)
311
310
312 revsetsfile = sys.stdin
311 revsetsfile = sys.stdin
313 if options.file:
312 if options.file:
314 revsetsfile = open(options.file)
313 revsetsfile = open(options.file)
315
314
316 revsets = [l.strip() for l in revsetsfile if not l.startswith('#')]
315 revsets = [l.strip() for l in revsetsfile if not l.startswith('#')]
317 revsets = [l for l in revsets if l]
316 revsets = [l for l in revsets if l]
318
317
319 print("Revsets to benchmark")
318 print("Revsets to benchmark")
320 print("----------------------------")
319 print("----------------------------")
321
320
322 for idx, rset in enumerate(revsets):
321 for idx, rset in enumerate(revsets):
323 print("%i) %s" % (idx, rset))
322 print("%i) %s" % (idx, rset))
324
323
325 print("----------------------------")
324 print("----------------------------")
326 print()
325 print()
327
326
328 revs = []
327 revs = []
329 for a in args:
328 for a in args:
330 revs.extend(getrevs(a))
329 revs.extend(getrevs(a))
331
330
332 variants = options.variants.split(',')
331 variants = options.variants.split(',')
333
332
334 results = []
333 results = []
335 for r in revs:
334 for r in revs:
336 print("----------------------------")
335 print("----------------------------")
337 printrevision(r)
336 printrevision(r)
338 print("----------------------------")
337 print("----------------------------")
339 update(r)
338 update(r)
340 res = []
339 res = []
341 results.append(res)
340 results.append(res)
342 printheader(variants, len(revsets), verbose=options.verbose)
341 printheader(variants, len(revsets), verbose=options.verbose)
343 for idx, rset in enumerate(revsets):
342 for idx, rset in enumerate(revsets):
344 varres = {}
343 varres = {}
345 for var in variants:
344 for var in variants:
346 varrset = applyvariants(rset, var)
345 varrset = applyvariants(rset, var)
347 data = perf(varrset, target=options.repo, contexts=options.contexts)
346 data = perf(varrset, target=options.repo, contexts=options.contexts)
348 varres[var] = data
347 varres[var] = data
349 res.append(varres)
348 res.append(varres)
350 printresult(
349 printresult(
351 variants, idx, varres, len(revsets), verbose=options.verbose
350 variants, idx, varres, len(revsets), verbose=options.verbose
352 )
351 )
353 sys.stdout.flush()
352 sys.stdout.flush()
354 print("----------------------------")
353 print("----------------------------")
355
354
356
355
357 print(
356 print(
358 """
357 """
359
358
360 Result by revset
359 Result by revset
361 ================
360 ================
362 """
361 """
363 )
362 )
364
363
365 print('Revision:')
364 print('Revision:')
366 for idx, rev in enumerate(revs):
365 for idx, rev in enumerate(revs):
367 sys.stdout.write('%i) ' % idx)
366 sys.stdout.write('%i) ' % idx)
368 sys.stdout.flush()
367 sys.stdout.flush()
369 printrevision(rev)
368 printrevision(rev)
370
369
371 print()
370 print()
372 print()
371 print()
373
372
374 for ridx, rset in enumerate(revsets):
373 for ridx, rset in enumerate(revsets):
375
374
376 print("revset #%i: %s" % (ridx, rset))
375 print("revset #%i: %s" % (ridx, rset))
377 printheader(variants, len(results), verbose=options.verbose, relative=True)
376 printheader(variants, len(results), verbose=options.verbose, relative=True)
378 ref = None
377 ref = None
379 for idx, data in enumerate(results):
378 for idx, data in enumerate(results):
380 printresult(
379 printresult(
381 variants,
380 variants,
382 idx,
381 idx,
383 data[ridx],
382 data[ridx],
384 len(results),
383 len(results),
385 verbose=options.verbose,
384 verbose=options.verbose,
386 reference=ref,
385 reference=ref,
387 )
386 )
388 ref = data[ridx]
387 ref = data[ridx]
389 print()
388 print()
@@ -1,31 +1,30 b''
1 # showstack.py - extension to dump a Python stack trace on signal
1 # showstack.py - extension to dump a Python stack trace on signal
2 #
2 #
3 # binds to both SIGQUIT (Ctrl-\) and SIGINFO (Ctrl-T on BSDs)
3 # binds to both SIGQUIT (Ctrl-\) and SIGINFO (Ctrl-T on BSDs)
4 r"""dump stack trace when receiving SIGQUIT (Ctrl-\) or SIGINFO (Ctrl-T on BSDs)
4 r"""dump stack trace when receiving SIGQUIT (Ctrl-\) or SIGINFO (Ctrl-T on BSDs)
5 """
5 """
6
6
7 from __future__ import absolute_import, print_function
8 import signal
7 import signal
9 import sys
8 import sys
10 import traceback
9 import traceback
11
10
12
11
13 def sigshow(*args):
12 def sigshow(*args):
14 sys.stderr.write("\n")
13 sys.stderr.write("\n")
15 traceback.print_stack(args[1], limit=10, file=sys.stderr)
14 traceback.print_stack(args[1], limit=10, file=sys.stderr)
16 sys.stderr.write("----\n")
15 sys.stderr.write("----\n")
17
16
18
17
19 def sigexit(*args):
18 def sigexit(*args):
20 sigshow(*args)
19 sigshow(*args)
21 print('alarm!')
20 print('alarm!')
22 sys.exit(1)
21 sys.exit(1)
23
22
24
23
25 def extsetup(ui):
24 def extsetup(ui):
26 signal.signal(signal.SIGQUIT, sigshow)
25 signal.signal(signal.SIGQUIT, sigshow)
27 signal.signal(signal.SIGALRM, sigexit)
26 signal.signal(signal.SIGALRM, sigexit)
28 try:
27 try:
29 signal.signal(signal.SIGINFO, sigshow)
28 signal.signal(signal.SIGINFO, sigshow)
30 except AttributeError:
29 except AttributeError:
31 pass
30 pass
@@ -1,141 +1,140 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 from __future__ import absolute_import
3
2
4 import getopt
3 import getopt
5 import sys
4 import sys
6
5
7 import hgdemandimport
6 import hgdemandimport
8
7
9 hgdemandimport.enable()
8 hgdemandimport.enable()
10
9
11 from mercurial.i18n import _
10 from mercurial.i18n import _
12 from mercurial import (
11 from mercurial import (
13 context,
12 context,
14 error,
13 error,
15 fancyopts,
14 fancyopts,
16 simplemerge,
15 simplemerge,
17 ui as uimod,
16 ui as uimod,
18 util,
17 util,
19 )
18 )
20 from mercurial.utils import procutil, stringutil
19 from mercurial.utils import procutil, stringutil
21
20
22 options = [
21 options = [
23 (b'L', b'label', [], _(b'labels to use on conflict markers')),
22 (b'L', b'label', [], _(b'labels to use on conflict markers')),
24 (b'a', b'text', None, _(b'treat all files as text')),
23 (b'a', b'text', None, _(b'treat all files as text')),
25 (b'p', b'print', None, _(b'print results instead of overwriting LOCAL')),
24 (b'p', b'print', None, _(b'print results instead of overwriting LOCAL')),
26 (b'', b'no-minimal', None, _(b'no effect (DEPRECATED)')),
25 (b'', b'no-minimal', None, _(b'no effect (DEPRECATED)')),
27 (b'h', b'help', None, _(b'display help and exit')),
26 (b'h', b'help', None, _(b'display help and exit')),
28 (b'q', b'quiet', None, _(b'suppress output')),
27 (b'q', b'quiet', None, _(b'suppress output')),
29 ]
28 ]
30
29
31 usage = _(
30 usage = _(
32 b'''simplemerge [OPTS] LOCAL BASE OTHER
31 b'''simplemerge [OPTS] LOCAL BASE OTHER
33
32
34 Simple three-way file merge utility with a minimal feature set.
33 Simple three-way file merge utility with a minimal feature set.
35
34
36 Apply to LOCAL the changes necessary to go from BASE to OTHER.
35 Apply to LOCAL the changes necessary to go from BASE to OTHER.
37
36
38 By default, LOCAL is overwritten with the results of this operation.
37 By default, LOCAL is overwritten with the results of this operation.
39 '''
38 '''
40 )
39 )
41
40
42
41
43 class ParseError(Exception):
42 class ParseError(Exception):
44 """Exception raised on errors in parsing the command line."""
43 """Exception raised on errors in parsing the command line."""
45
44
46
45
47 def showhelp():
46 def showhelp():
48 procutil.stdout.write(usage)
47 procutil.stdout.write(usage)
49 procutil.stdout.write(b'\noptions:\n')
48 procutil.stdout.write(b'\noptions:\n')
50
49
51 out_opts = []
50 out_opts = []
52 for shortopt, longopt, default, desc in options:
51 for shortopt, longopt, default, desc in options:
53 out_opts.append(
52 out_opts.append(
54 (
53 (
55 b'%2s%s'
54 b'%2s%s'
56 % (
55 % (
57 shortopt and b'-%s' % shortopt,
56 shortopt and b'-%s' % shortopt,
58 longopt and b' --%s' % longopt,
57 longopt and b' --%s' % longopt,
59 ),
58 ),
60 b'%s' % desc,
59 b'%s' % desc,
61 )
60 )
62 )
61 )
63 opts_len = max([len(opt[0]) for opt in out_opts])
62 opts_len = max([len(opt[0]) for opt in out_opts])
64 for first, second in out_opts:
63 for first, second in out_opts:
65 procutil.stdout.write(b' %-*s %s\n' % (opts_len, first, second))
64 procutil.stdout.write(b' %-*s %s\n' % (opts_len, first, second))
66
65
67
66
68 def _verifytext(input, ui, quiet=False, allow_binary=False):
67 def _verifytext(input, ui, quiet=False, allow_binary=False):
69 """verifies that text is non-binary (unless opts[text] is passed,
68 """verifies that text is non-binary (unless opts[text] is passed,
70 then we just warn)"""
69 then we just warn)"""
71 if stringutil.binary(input.text()):
70 if stringutil.binary(input.text()):
72 msg = _(b"%s looks like a binary file.") % input.fctx.path()
71 msg = _(b"%s looks like a binary file.") % input.fctx.path()
73 if not quiet:
72 if not quiet:
74 ui.warn(_(b'warning: %s\n') % msg)
73 ui.warn(_(b'warning: %s\n') % msg)
75 if not allow_binary:
74 if not allow_binary:
76 sys.exit(1)
75 sys.exit(1)
77
76
78
77
79 try:
78 try:
80 for fp in (sys.stdin, procutil.stdout, sys.stderr):
79 for fp in (sys.stdin, procutil.stdout, sys.stderr):
81 procutil.setbinary(fp)
80 procutil.setbinary(fp)
82
81
83 opts = {}
82 opts = {}
84 try:
83 try:
85 bargv = [a.encode('utf8') for a in sys.argv[1:]]
84 bargv = [a.encode('utf8') for a in sys.argv[1:]]
86 args = fancyopts.fancyopts(bargv, options, opts)
85 args = fancyopts.fancyopts(bargv, options, opts)
87 except getopt.GetoptError as e:
86 except getopt.GetoptError as e:
88 raise ParseError(e)
87 raise ParseError(e)
89 if opts[b'help']:
88 if opts[b'help']:
90 showhelp()
89 showhelp()
91 sys.exit(0)
90 sys.exit(0)
92 if len(args) != 3:
91 if len(args) != 3:
93 raise ParseError(_(b'wrong number of arguments').decode('utf8'))
92 raise ParseError(_(b'wrong number of arguments').decode('utf8'))
94 mode = b'merge'
93 mode = b'merge'
95 if len(opts[b'label']) > 2:
94 if len(opts[b'label']) > 2:
96 mode = b'merge3'
95 mode = b'merge3'
97 local, base, other = args
96 local, base, other = args
98 overrides = opts[b'label']
97 overrides = opts[b'label']
99 if len(overrides) > 3:
98 if len(overrides) > 3:
100 raise error.InputError(b'can only specify three labels.')
99 raise error.InputError(b'can only specify three labels.')
101 labels = [local, other, base]
100 labels = [local, other, base]
102 labels[: len(overrides)] = overrides
101 labels[: len(overrides)] = overrides
103 local_input = simplemerge.MergeInput(
102 local_input = simplemerge.MergeInput(
104 context.arbitraryfilectx(local), labels[0]
103 context.arbitraryfilectx(local), labels[0]
105 )
104 )
106 other_input = simplemerge.MergeInput(
105 other_input = simplemerge.MergeInput(
107 context.arbitraryfilectx(other), labels[1]
106 context.arbitraryfilectx(other), labels[1]
108 )
107 )
109 base_input = simplemerge.MergeInput(
108 base_input = simplemerge.MergeInput(
110 context.arbitraryfilectx(base), labels[2]
109 context.arbitraryfilectx(base), labels[2]
111 )
110 )
112
111
113 quiet = opts.get(b'quiet')
112 quiet = opts.get(b'quiet')
114 allow_binary = opts.get(b'text')
113 allow_binary = opts.get(b'text')
115 ui = uimod.ui.load()
114 ui = uimod.ui.load()
116 _verifytext(local_input, ui, quiet=quiet, allow_binary=allow_binary)
115 _verifytext(local_input, ui, quiet=quiet, allow_binary=allow_binary)
117 _verifytext(base_input, ui, quiet=quiet, allow_binary=allow_binary)
116 _verifytext(base_input, ui, quiet=quiet, allow_binary=allow_binary)
118 _verifytext(other_input, ui, quiet=quiet, allow_binary=allow_binary)
117 _verifytext(other_input, ui, quiet=quiet, allow_binary=allow_binary)
119
118
120 merged_text, conflicts = simplemerge.simplemerge(
119 merged_text, conflicts = simplemerge.simplemerge(
121 local_input,
120 local_input,
122 base_input,
121 base_input,
123 other_input,
122 other_input,
124 mode,
123 mode,
125 allow_binary=allow_binary,
124 allow_binary=allow_binary,
126 )
125 )
127 if opts.get(b'print'):
126 if opts.get(b'print'):
128 ui.fout.write(merged_text)
127 ui.fout.write(merged_text)
129 else:
128 else:
130 util.writefile(local, merged_text)
129 util.writefile(local, merged_text)
131 sys.exit(1 if conflicts else 0)
130 sys.exit(1 if conflicts else 0)
132 except ParseError as e:
131 except ParseError as e:
133 e = stringutil.forcebytestr(e)
132 e = stringutil.forcebytestr(e)
134 procutil.stdout.write(b"%s: %s\n" % (sys.argv[0].encode('utf8'), e))
133 procutil.stdout.write(b"%s: %s\n" % (sys.argv[0].encode('utf8'), e))
135 showhelp()
134 showhelp()
136 sys.exit(1)
135 sys.exit(1)
137 except error.Abort as e:
136 except error.Abort as e:
138 procutil.stderr.write(b"abort: %s\n" % e)
137 procutil.stderr.write(b"abort: %s\n" % e)
139 sys.exit(255)
138 sys.exit(255)
140 except KeyboardInterrupt:
139 except KeyboardInterrupt:
141 sys.exit(255)
140 sys.exit(255)
@@ -1,565 +1,564 b''
1 # synthrepo.py - repo synthesis
1 # synthrepo.py - repo synthesis
2 #
2 #
3 # Copyright 2012 Facebook
3 # Copyright 2012 Facebook
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''synthesize structurally interesting change history
8 '''synthesize structurally interesting change history
9
9
10 This extension is useful for creating a repository with properties
10 This extension is useful for creating a repository with properties
11 that are statistically similar to an existing repository. During
11 that are statistically similar to an existing repository. During
12 analysis, a simple probability table is constructed from the history
12 analysis, a simple probability table is constructed from the history
13 of an existing repository. During synthesis, these properties are
13 of an existing repository. During synthesis, these properties are
14 reconstructed.
14 reconstructed.
15
15
16 Properties that are analyzed and synthesized include the following:
16 Properties that are analyzed and synthesized include the following:
17
17
18 - Lines added or removed when an existing file is modified
18 - Lines added or removed when an existing file is modified
19 - Number and sizes of files added
19 - Number and sizes of files added
20 - Number of files removed
20 - Number of files removed
21 - Line lengths
21 - Line lengths
22 - Topological distance to parent changeset(s)
22 - Topological distance to parent changeset(s)
23 - Probability of a commit being a merge
23 - Probability of a commit being a merge
24 - Probability of a newly added file being added to a new directory
24 - Probability of a newly added file being added to a new directory
25 - Interarrival time, and time zone, of commits
25 - Interarrival time, and time zone, of commits
26 - Number of files in each directory
26 - Number of files in each directory
27
27
28 A few obvious properties that are not currently handled realistically:
28 A few obvious properties that are not currently handled realistically:
29
29
30 - Merges are treated as regular commits with two parents, which is not
30 - Merges are treated as regular commits with two parents, which is not
31 realistic
31 realistic
32 - Modifications are not treated as operations on hunks of lines, but
32 - Modifications are not treated as operations on hunks of lines, but
33 as insertions and deletions of randomly chosen single lines
33 as insertions and deletions of randomly chosen single lines
34 - Committer ID (always random)
34 - Committer ID (always random)
35 - Executability of files
35 - Executability of files
36 - Symlinks and binary files are ignored
36 - Symlinks and binary files are ignored
37 '''
37 '''
38
38
39 from __future__ import absolute_import
40 import bisect
39 import bisect
41 import collections
40 import collections
42 import itertools
41 import itertools
43 import json
42 import json
44 import os
43 import os
45 import random
44 import random
46 import sys
45 import sys
47 import time
46 import time
48
47
49 from mercurial.i18n import _
48 from mercurial.i18n import _
50 from mercurial.node import (
49 from mercurial.node import (
51 nullid,
50 nullid,
52 nullrev,
51 nullrev,
53 short,
52 short,
54 )
53 )
55 from mercurial import (
54 from mercurial import (
56 context,
55 context,
57 diffutil,
56 diffutil,
58 error,
57 error,
59 hg,
58 hg,
60 logcmdutil,
59 logcmdutil,
61 patch,
60 patch,
62 pycompat,
61 pycompat,
63 registrar,
62 registrar,
64 )
63 )
65 from mercurial.utils import dateutil
64 from mercurial.utils import dateutil
66
65
67 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
66 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
68 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
67 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
69 # be specifying the version(s) of Mercurial they are tested with, or
68 # be specifying the version(s) of Mercurial they are tested with, or
70 # leave the attribute unspecified.
69 # leave the attribute unspecified.
71 testedwith = 'ships-with-hg-core'
70 testedwith = 'ships-with-hg-core'
72
71
73 cmdtable = {}
72 cmdtable = {}
74 command = registrar.command(cmdtable)
73 command = registrar.command(cmdtable)
75
74
76 newfile = {'new fi', 'rename', 'copy f', 'copy t'}
75 newfile = {'new fi', 'rename', 'copy f', 'copy t'}
77
76
78
77
79 def zerodict():
78 def zerodict():
80 return collections.defaultdict(lambda: 0)
79 return collections.defaultdict(lambda: 0)
81
80
82
81
83 def roundto(x, k):
82 def roundto(x, k):
84 if x > k * 2:
83 if x > k * 2:
85 return int(round(x / float(k)) * k)
84 return int(round(x / float(k)) * k)
86 return int(round(x))
85 return int(round(x))
87
86
88
87
89 def parsegitdiff(lines):
88 def parsegitdiff(lines):
90 filename, mar, lineadd, lineremove = None, None, zerodict(), 0
89 filename, mar, lineadd, lineremove = None, None, zerodict(), 0
91 binary = False
90 binary = False
92 for line in lines:
91 for line in lines:
93 start = line[:6]
92 start = line[:6]
94 if start == 'diff -':
93 if start == 'diff -':
95 if filename:
94 if filename:
96 yield filename, mar, lineadd, lineremove, binary
95 yield filename, mar, lineadd, lineremove, binary
97 mar, lineadd, lineremove, binary = 'm', zerodict(), 0, False
96 mar, lineadd, lineremove, binary = 'm', zerodict(), 0, False
98 filename = patch.gitre.match(line).group(1)
97 filename = patch.gitre.match(line).group(1)
99 elif start in newfile:
98 elif start in newfile:
100 mar = 'a'
99 mar = 'a'
101 elif start == 'GIT bi':
100 elif start == 'GIT bi':
102 binary = True
101 binary = True
103 elif start == 'delete':
102 elif start == 'delete':
104 mar = 'r'
103 mar = 'r'
105 elif start:
104 elif start:
106 s = start[0]
105 s = start[0]
107 if s == '-' and not line.startswith('--- '):
106 if s == '-' and not line.startswith('--- '):
108 lineremove += 1
107 lineremove += 1
109 elif s == '+' and not line.startswith('+++ '):
108 elif s == '+' and not line.startswith('+++ '):
110 lineadd[roundto(len(line) - 1, 5)] += 1
109 lineadd[roundto(len(line) - 1, 5)] += 1
111 if filename:
110 if filename:
112 yield filename, mar, lineadd, lineremove, binary
111 yield filename, mar, lineadd, lineremove, binary
113
112
114
113
115 @command(
114 @command(
116 'analyze',
115 'analyze',
117 [
116 [
118 ('o', 'output', '', _('write output to given file'), _('FILE')),
117 ('o', 'output', '', _('write output to given file'), _('FILE')),
119 ('r', 'rev', [], _('analyze specified revisions'), _('REV')),
118 ('r', 'rev', [], _('analyze specified revisions'), _('REV')),
120 ],
119 ],
121 _('hg analyze'),
120 _('hg analyze'),
122 optionalrepo=True,
121 optionalrepo=True,
123 )
122 )
124 def analyze(ui, repo, *revs, **opts):
123 def analyze(ui, repo, *revs, **opts):
125 """create a simple model of a repository to use for later synthesis
124 """create a simple model of a repository to use for later synthesis
126
125
127 This command examines every changeset in the given range (or all
126 This command examines every changeset in the given range (or all
128 of history if none are specified) and creates a simple statistical
127 of history if none are specified) and creates a simple statistical
129 model of the history of the repository. It also measures the directory
128 model of the history of the repository. It also measures the directory
130 structure of the repository as checked out.
129 structure of the repository as checked out.
131
130
132 The model is written out to a JSON file, and can be used by
131 The model is written out to a JSON file, and can be used by
133 :hg:`synthesize` to create or augment a repository with synthetic
132 :hg:`synthesize` to create or augment a repository with synthetic
134 commits that have a structure that is statistically similar to the
133 commits that have a structure that is statistically similar to the
135 analyzed repository.
134 analyzed repository.
136 """
135 """
137 root = repo.root
136 root = repo.root
138 if not root.endswith(os.path.sep):
137 if not root.endswith(os.path.sep):
139 root += os.path.sep
138 root += os.path.sep
140
139
141 revs = list(revs)
140 revs = list(revs)
142 revs.extend(opts['rev'])
141 revs.extend(opts['rev'])
143 if not revs:
142 if not revs:
144 revs = [':']
143 revs = [':']
145
144
146 output = opts['output']
145 output = opts['output']
147 if not output:
146 if not output:
148 output = os.path.basename(root) + '.json'
147 output = os.path.basename(root) + '.json'
149
148
150 if output == '-':
149 if output == '-':
151 fp = sys.stdout
150 fp = sys.stdout
152 else:
151 else:
153 fp = open(output, 'w')
152 fp = open(output, 'w')
154
153
155 # Always obtain file counts of each directory in the given root directory.
154 # Always obtain file counts of each directory in the given root directory.
156 def onerror(e):
155 def onerror(e):
157 ui.warn(_('error walking directory structure: %s\n') % e)
156 ui.warn(_('error walking directory structure: %s\n') % e)
158
157
159 dirs = {}
158 dirs = {}
160 rootprefixlen = len(root)
159 rootprefixlen = len(root)
161 for dirpath, dirnames, filenames in os.walk(root, onerror=onerror):
160 for dirpath, dirnames, filenames in os.walk(root, onerror=onerror):
162 dirpathfromroot = dirpath[rootprefixlen:]
161 dirpathfromroot = dirpath[rootprefixlen:]
163 dirs[dirpathfromroot] = len(filenames)
162 dirs[dirpathfromroot] = len(filenames)
164 if '.hg' in dirnames:
163 if '.hg' in dirnames:
165 dirnames.remove('.hg')
164 dirnames.remove('.hg')
166
165
167 lineschanged = zerodict()
166 lineschanged = zerodict()
168 children = zerodict()
167 children = zerodict()
169 p1distance = zerodict()
168 p1distance = zerodict()
170 p2distance = zerodict()
169 p2distance = zerodict()
171 linesinfilesadded = zerodict()
170 linesinfilesadded = zerodict()
172 fileschanged = zerodict()
171 fileschanged = zerodict()
173 filesadded = zerodict()
172 filesadded = zerodict()
174 filesremoved = zerodict()
173 filesremoved = zerodict()
175 linelengths = zerodict()
174 linelengths = zerodict()
176 interarrival = zerodict()
175 interarrival = zerodict()
177 parents = zerodict()
176 parents = zerodict()
178 dirsadded = zerodict()
177 dirsadded = zerodict()
179 tzoffset = zerodict()
178 tzoffset = zerodict()
180
179
181 # If a mercurial repo is available, also model the commit history.
180 # If a mercurial repo is available, also model the commit history.
182 if repo:
181 if repo:
183 revs = logcmdutil.revrange(repo, revs)
182 revs = logcmdutil.revrange(repo, revs)
184 revs.sort()
183 revs.sort()
185
184
186 progress = ui.makeprogress(
185 progress = ui.makeprogress(
187 _('analyzing'), unit=_('changesets'), total=len(revs)
186 _('analyzing'), unit=_('changesets'), total=len(revs)
188 )
187 )
189 for i, rev in enumerate(revs):
188 for i, rev in enumerate(revs):
190 progress.update(i)
189 progress.update(i)
191 ctx = repo[rev]
190 ctx = repo[rev]
192 pl = ctx.parents()
191 pl = ctx.parents()
193 pctx = pl[0]
192 pctx = pl[0]
194 prev = pctx.rev()
193 prev = pctx.rev()
195 children[prev] += 1
194 children[prev] += 1
196 p1distance[rev - prev] += 1
195 p1distance[rev - prev] += 1
197 parents[len(pl)] += 1
196 parents[len(pl)] += 1
198 tzoffset[ctx.date()[1]] += 1
197 tzoffset[ctx.date()[1]] += 1
199 if len(pl) > 1:
198 if len(pl) > 1:
200 p2distance[rev - pl[1].rev()] += 1
199 p2distance[rev - pl[1].rev()] += 1
201 if prev == rev - 1:
200 if prev == rev - 1:
202 lastctx = pctx
201 lastctx = pctx
203 else:
202 else:
204 lastctx = repo[rev - 1]
203 lastctx = repo[rev - 1]
205 if lastctx.rev() != nullrev:
204 if lastctx.rev() != nullrev:
206 timedelta = ctx.date()[0] - lastctx.date()[0]
205 timedelta = ctx.date()[0] - lastctx.date()[0]
207 interarrival[roundto(timedelta, 300)] += 1
206 interarrival[roundto(timedelta, 300)] += 1
208 diffopts = diffutil.diffallopts(ui, {'git': True})
207 diffopts = diffutil.diffallopts(ui, {'git': True})
209 diff = sum(
208 diff = sum(
210 (d.splitlines() for d in ctx.diff(pctx, opts=diffopts)), []
209 (d.splitlines() for d in ctx.diff(pctx, opts=diffopts)), []
211 )
210 )
212 fileadds, diradds, fileremoves, filechanges = 0, 0, 0, 0
211 fileadds, diradds, fileremoves, filechanges = 0, 0, 0, 0
213 for filename, mar, lineadd, lineremove, isbin in parsegitdiff(diff):
212 for filename, mar, lineadd, lineremove, isbin in parsegitdiff(diff):
214 if isbin:
213 if isbin:
215 continue
214 continue
216 added = sum(pycompat.itervalues(lineadd), 0)
215 added = sum(pycompat.itervalues(lineadd), 0)
217 if mar == 'm':
216 if mar == 'm':
218 if added and lineremove:
217 if added and lineremove:
219 lineschanged[
218 lineschanged[
220 roundto(added, 5), roundto(lineremove, 5)
219 roundto(added, 5), roundto(lineremove, 5)
221 ] += 1
220 ] += 1
222 filechanges += 1
221 filechanges += 1
223 elif mar == 'a':
222 elif mar == 'a':
224 fileadds += 1
223 fileadds += 1
225 if '/' in filename:
224 if '/' in filename:
226 filedir = filename.rsplit('/', 1)[0]
225 filedir = filename.rsplit('/', 1)[0]
227 if filedir not in pctx.dirs():
226 if filedir not in pctx.dirs():
228 diradds += 1
227 diradds += 1
229 linesinfilesadded[roundto(added, 5)] += 1
228 linesinfilesadded[roundto(added, 5)] += 1
230 elif mar == 'r':
229 elif mar == 'r':
231 fileremoves += 1
230 fileremoves += 1
232 for length, count in lineadd.iteritems():
231 for length, count in lineadd.iteritems():
233 linelengths[length] += count
232 linelengths[length] += count
234 fileschanged[filechanges] += 1
233 fileschanged[filechanges] += 1
235 filesadded[fileadds] += 1
234 filesadded[fileadds] += 1
236 dirsadded[diradds] += 1
235 dirsadded[diradds] += 1
237 filesremoved[fileremoves] += 1
236 filesremoved[fileremoves] += 1
238 progress.complete()
237 progress.complete()
239
238
240 invchildren = zerodict()
239 invchildren = zerodict()
241
240
242 for rev, count in children.iteritems():
241 for rev, count in children.iteritems():
243 invchildren[count] += 1
242 invchildren[count] += 1
244
243
245 if output != '-':
244 if output != '-':
246 ui.status(_('writing output to %s\n') % output)
245 ui.status(_('writing output to %s\n') % output)
247
246
248 def pronk(d):
247 def pronk(d):
249 return sorted(d.iteritems(), key=lambda x: x[1], reverse=True)
248 return sorted(d.iteritems(), key=lambda x: x[1], reverse=True)
250
249
251 json.dump(
250 json.dump(
252 {
251 {
253 'revs': len(revs),
252 'revs': len(revs),
254 'initdirs': pronk(dirs),
253 'initdirs': pronk(dirs),
255 'lineschanged': pronk(lineschanged),
254 'lineschanged': pronk(lineschanged),
256 'children': pronk(invchildren),
255 'children': pronk(invchildren),
257 'fileschanged': pronk(fileschanged),
256 'fileschanged': pronk(fileschanged),
258 'filesadded': pronk(filesadded),
257 'filesadded': pronk(filesadded),
259 'linesinfilesadded': pronk(linesinfilesadded),
258 'linesinfilesadded': pronk(linesinfilesadded),
260 'dirsadded': pronk(dirsadded),
259 'dirsadded': pronk(dirsadded),
261 'filesremoved': pronk(filesremoved),
260 'filesremoved': pronk(filesremoved),
262 'linelengths': pronk(linelengths),
261 'linelengths': pronk(linelengths),
263 'parents': pronk(parents),
262 'parents': pronk(parents),
264 'p1distance': pronk(p1distance),
263 'p1distance': pronk(p1distance),
265 'p2distance': pronk(p2distance),
264 'p2distance': pronk(p2distance),
266 'interarrival': pronk(interarrival),
265 'interarrival': pronk(interarrival),
267 'tzoffset': pronk(tzoffset),
266 'tzoffset': pronk(tzoffset),
268 },
267 },
269 fp,
268 fp,
270 )
269 )
271 fp.close()
270 fp.close()
272
271
273
272
274 @command(
273 @command(
275 'synthesize',
274 'synthesize',
276 [
275 [
277 ('c', 'count', 0, _('create given number of commits'), _('COUNT')),
276 ('c', 'count', 0, _('create given number of commits'), _('COUNT')),
278 ('', 'dict', '', _('path to a dictionary of words'), _('FILE')),
277 ('', 'dict', '', _('path to a dictionary of words'), _('FILE')),
279 ('', 'initfiles', 0, _('initial file count to create'), _('COUNT')),
278 ('', 'initfiles', 0, _('initial file count to create'), _('COUNT')),
280 ],
279 ],
281 _('hg synthesize [OPTION].. DESCFILE'),
280 _('hg synthesize [OPTION].. DESCFILE'),
282 )
281 )
283 def synthesize(ui, repo, descpath, **opts):
282 def synthesize(ui, repo, descpath, **opts):
284 """synthesize commits based on a model of an existing repository
283 """synthesize commits based on a model of an existing repository
285
284
286 The model must have been generated by :hg:`analyze`. Commits will
285 The model must have been generated by :hg:`analyze`. Commits will
287 be generated randomly according to the probabilities described in
286 be generated randomly according to the probabilities described in
288 the model. If --initfiles is set, the repository will be seeded with
287 the model. If --initfiles is set, the repository will be seeded with
289 the given number files following the modeled repository's directory
288 the given number files following the modeled repository's directory
290 structure.
289 structure.
291
290
292 When synthesizing new content, commit descriptions, and user
291 When synthesizing new content, commit descriptions, and user
293 names, words will be chosen randomly from a dictionary that is
292 names, words will be chosen randomly from a dictionary that is
294 presumed to contain one word per line. Use --dict to specify the
293 presumed to contain one word per line. Use --dict to specify the
295 path to an alternate dictionary to use.
294 path to an alternate dictionary to use.
296 """
295 """
297 try:
296 try:
298 fp = hg.openpath(ui, descpath)
297 fp = hg.openpath(ui, descpath)
299 except Exception as err:
298 except Exception as err:
300 raise error.Abort('%s: %s' % (descpath, err[0].strerror))
299 raise error.Abort('%s: %s' % (descpath, err[0].strerror))
301 desc = json.load(fp)
300 desc = json.load(fp)
302 fp.close()
301 fp.close()
303
302
304 def cdf(l):
303 def cdf(l):
305 if not l:
304 if not l:
306 return [], []
305 return [], []
307 vals, probs = zip(*sorted(l, key=lambda x: x[1], reverse=True))
306 vals, probs = zip(*sorted(l, key=lambda x: x[1], reverse=True))
308 t = float(sum(probs, 0))
307 t = float(sum(probs, 0))
309 s, cdfs = 0, []
308 s, cdfs = 0, []
310 for v in probs:
309 for v in probs:
311 s += v
310 s += v
312 cdfs.append(s / t)
311 cdfs.append(s / t)
313 return vals, cdfs
312 return vals, cdfs
314
313
315 lineschanged = cdf(desc['lineschanged'])
314 lineschanged = cdf(desc['lineschanged'])
316 fileschanged = cdf(desc['fileschanged'])
315 fileschanged = cdf(desc['fileschanged'])
317 filesadded = cdf(desc['filesadded'])
316 filesadded = cdf(desc['filesadded'])
318 dirsadded = cdf(desc['dirsadded'])
317 dirsadded = cdf(desc['dirsadded'])
319 filesremoved = cdf(desc['filesremoved'])
318 filesremoved = cdf(desc['filesremoved'])
320 linelengths = cdf(desc['linelengths'])
319 linelengths = cdf(desc['linelengths'])
321 parents = cdf(desc['parents'])
320 parents = cdf(desc['parents'])
322 p1distance = cdf(desc['p1distance'])
321 p1distance = cdf(desc['p1distance'])
323 p2distance = cdf(desc['p2distance'])
322 p2distance = cdf(desc['p2distance'])
324 interarrival = cdf(desc['interarrival'])
323 interarrival = cdf(desc['interarrival'])
325 linesinfilesadded = cdf(desc['linesinfilesadded'])
324 linesinfilesadded = cdf(desc['linesinfilesadded'])
326 tzoffset = cdf(desc['tzoffset'])
325 tzoffset = cdf(desc['tzoffset'])
327
326
328 dictfile = opts.get('dict') or '/usr/share/dict/words'
327 dictfile = opts.get('dict') or '/usr/share/dict/words'
329 try:
328 try:
330 fp = open(dictfile, 'rU')
329 fp = open(dictfile, 'rU')
331 except IOError as err:
330 except IOError as err:
332 raise error.Abort('%s: %s' % (dictfile, err.strerror))
331 raise error.Abort('%s: %s' % (dictfile, err.strerror))
333 words = fp.read().splitlines()
332 words = fp.read().splitlines()
334 fp.close()
333 fp.close()
335
334
336 initdirs = {}
335 initdirs = {}
337 if desc['initdirs']:
336 if desc['initdirs']:
338 for k, v in desc['initdirs']:
337 for k, v in desc['initdirs']:
339 initdirs[k.encode('utf-8').replace('.hg', '_hg')] = v
338 initdirs[k.encode('utf-8').replace('.hg', '_hg')] = v
340 initdirs = renamedirs(initdirs, words)
339 initdirs = renamedirs(initdirs, words)
341 initdirscdf = cdf(initdirs)
340 initdirscdf = cdf(initdirs)
342
341
343 def pick(cdf):
342 def pick(cdf):
344 return cdf[0][bisect.bisect_left(cdf[1], random.random())]
343 return cdf[0][bisect.bisect_left(cdf[1], random.random())]
345
344
346 def pickpath():
345 def pickpath():
347 return os.path.join(pick(initdirscdf), random.choice(words))
346 return os.path.join(pick(initdirscdf), random.choice(words))
348
347
349 def makeline(minimum=0):
348 def makeline(minimum=0):
350 total = max(minimum, pick(linelengths))
349 total = max(minimum, pick(linelengths))
351 c, l = 0, []
350 c, l = 0, []
352 while c < total:
351 while c < total:
353 w = random.choice(words)
352 w = random.choice(words)
354 c += len(w) + 1
353 c += len(w) + 1
355 l.append(w)
354 l.append(w)
356 return ' '.join(l)
355 return ' '.join(l)
357
356
358 wlock = repo.wlock()
357 wlock = repo.wlock()
359 lock = repo.lock()
358 lock = repo.lock()
360
359
361 nevertouch = {'.hgsub', '.hgignore', '.hgtags'}
360 nevertouch = {'.hgsub', '.hgignore', '.hgtags'}
362
361
363 _synthesizing = _('synthesizing')
362 _synthesizing = _('synthesizing')
364 _files = _('initial files')
363 _files = _('initial files')
365 _changesets = _('changesets')
364 _changesets = _('changesets')
366
365
367 # Synthesize a single initial revision adding files to the repo according
366 # Synthesize a single initial revision adding files to the repo according
368 # to the modeled directory structure.
367 # to the modeled directory structure.
369 initcount = int(opts['initfiles'])
368 initcount = int(opts['initfiles'])
370 if initcount and initdirs:
369 if initcount and initdirs:
371 pctx = repo['.']
370 pctx = repo['.']
372 dirs = set(pctx.dirs())
371 dirs = set(pctx.dirs())
373 files = {}
372 files = {}
374
373
375 def validpath(path):
374 def validpath(path):
376 # Don't pick filenames which are already directory names.
375 # Don't pick filenames which are already directory names.
377 if path in dirs:
376 if path in dirs:
378 return False
377 return False
379 # Don't pick directories which were used as file names.
378 # Don't pick directories which were used as file names.
380 while path:
379 while path:
381 if path in files:
380 if path in files:
382 return False
381 return False
383 path = os.path.dirname(path)
382 path = os.path.dirname(path)
384 return True
383 return True
385
384
386 progress = ui.makeprogress(_synthesizing, unit=_files, total=initcount)
385 progress = ui.makeprogress(_synthesizing, unit=_files, total=initcount)
387 for i in pycompat.xrange(0, initcount):
386 for i in pycompat.xrange(0, initcount):
388 progress.update(i)
387 progress.update(i)
389
388
390 path = pickpath()
389 path = pickpath()
391 while not validpath(path):
390 while not validpath(path):
392 path = pickpath()
391 path = pickpath()
393 data = '%s contents\n' % path
392 data = '%s contents\n' % path
394 files[path] = data
393 files[path] = data
395 dir = os.path.dirname(path)
394 dir = os.path.dirname(path)
396 while dir and dir not in dirs:
395 while dir and dir not in dirs:
397 dirs.add(dir)
396 dirs.add(dir)
398 dir = os.path.dirname(dir)
397 dir = os.path.dirname(dir)
399
398
400 def filectxfn(repo, memctx, path):
399 def filectxfn(repo, memctx, path):
401 return context.memfilectx(repo, memctx, path, files[path])
400 return context.memfilectx(repo, memctx, path, files[path])
402
401
403 progress.complete()
402 progress.complete()
404 message = 'synthesized wide repo with %d files' % (len(files),)
403 message = 'synthesized wide repo with %d files' % (len(files),)
405 mc = context.memctx(
404 mc = context.memctx(
406 repo,
405 repo,
407 [pctx.node(), nullid],
406 [pctx.node(), nullid],
408 message,
407 message,
409 files,
408 files,
410 filectxfn,
409 filectxfn,
411 ui.username(),
410 ui.username(),
412 '%d %d' % dateutil.makedate(),
411 '%d %d' % dateutil.makedate(),
413 )
412 )
414 initnode = mc.commit()
413 initnode = mc.commit()
415 if ui.debugflag:
414 if ui.debugflag:
416 hexfn = hex
415 hexfn = hex
417 else:
416 else:
418 hexfn = short
417 hexfn = short
419 ui.status(
418 ui.status(
420 _('added commit %s with %d files\n') % (hexfn(initnode), len(files))
419 _('added commit %s with %d files\n') % (hexfn(initnode), len(files))
421 )
420 )
422
421
423 # Synthesize incremental revisions to the repository, adding repo depth.
422 # Synthesize incremental revisions to the repository, adding repo depth.
424 count = int(opts['count'])
423 count = int(opts['count'])
425 heads = set(map(repo.changelog.rev, repo.heads()))
424 heads = set(map(repo.changelog.rev, repo.heads()))
426 progress = ui.makeprogress(_synthesizing, unit=_changesets, total=count)
425 progress = ui.makeprogress(_synthesizing, unit=_changesets, total=count)
427 for i in pycompat.xrange(count):
426 for i in pycompat.xrange(count):
428 progress.update(i)
427 progress.update(i)
429
428
430 node = repo.changelog.node
429 node = repo.changelog.node
431 revs = len(repo)
430 revs = len(repo)
432
431
433 def pickhead(heads, distance):
432 def pickhead(heads, distance):
434 if heads:
433 if heads:
435 lheads = sorted(heads)
434 lheads = sorted(heads)
436 rev = revs - min(pick(distance), revs)
435 rev = revs - min(pick(distance), revs)
437 if rev < lheads[-1]:
436 if rev < lheads[-1]:
438 rev = lheads[bisect.bisect_left(lheads, rev)]
437 rev = lheads[bisect.bisect_left(lheads, rev)]
439 else:
438 else:
440 rev = lheads[-1]
439 rev = lheads[-1]
441 return rev, node(rev)
440 return rev, node(rev)
442 return nullrev, nullid
441 return nullrev, nullid
443
442
444 r1 = revs - min(pick(p1distance), revs)
443 r1 = revs - min(pick(p1distance), revs)
445 p1 = node(r1)
444 p1 = node(r1)
446
445
447 # the number of heads will grow without bound if we use a pure
446 # the number of heads will grow without bound if we use a pure
448 # model, so artificially constrain their proliferation
447 # model, so artificially constrain their proliferation
449 toomanyheads = len(heads) > random.randint(1, 20)
448 toomanyheads = len(heads) > random.randint(1, 20)
450 if p2distance[0] and (pick(parents) == 2 or toomanyheads):
449 if p2distance[0] and (pick(parents) == 2 or toomanyheads):
451 r2, p2 = pickhead(heads.difference([r1]), p2distance)
450 r2, p2 = pickhead(heads.difference([r1]), p2distance)
452 else:
451 else:
453 r2, p2 = nullrev, nullid
452 r2, p2 = nullrev, nullid
454
453
455 pl = [p1, p2]
454 pl = [p1, p2]
456 pctx = repo[r1]
455 pctx = repo[r1]
457 mf = pctx.manifest()
456 mf = pctx.manifest()
458 mfk = mf.keys()
457 mfk = mf.keys()
459 changes = {}
458 changes = {}
460 if mfk:
459 if mfk:
461 for __ in pycompat.xrange(pick(fileschanged)):
460 for __ in pycompat.xrange(pick(fileschanged)):
462 for __ in pycompat.xrange(10):
461 for __ in pycompat.xrange(10):
463 fctx = pctx.filectx(random.choice(mfk))
462 fctx = pctx.filectx(random.choice(mfk))
464 path = fctx.path()
463 path = fctx.path()
465 if not (
464 if not (
466 path in nevertouch
465 path in nevertouch
467 or fctx.isbinary()
466 or fctx.isbinary()
468 or 'l' in fctx.flags()
467 or 'l' in fctx.flags()
469 ):
468 ):
470 break
469 break
471 lines = fctx.data().splitlines()
470 lines = fctx.data().splitlines()
472 add, remove = pick(lineschanged)
471 add, remove = pick(lineschanged)
473 for __ in pycompat.xrange(remove):
472 for __ in pycompat.xrange(remove):
474 if not lines:
473 if not lines:
475 break
474 break
476 del lines[random.randrange(0, len(lines))]
475 del lines[random.randrange(0, len(lines))]
477 for __ in pycompat.xrange(add):
476 for __ in pycompat.xrange(add):
478 lines.insert(random.randint(0, len(lines)), makeline())
477 lines.insert(random.randint(0, len(lines)), makeline())
479 path = fctx.path()
478 path = fctx.path()
480 changes[path] = '\n'.join(lines) + '\n'
479 changes[path] = '\n'.join(lines) + '\n'
481 for __ in pycompat.xrange(pick(filesremoved)):
480 for __ in pycompat.xrange(pick(filesremoved)):
482 for __ in pycompat.xrange(10):
481 for __ in pycompat.xrange(10):
483 path = random.choice(mfk)
482 path = random.choice(mfk)
484 if path not in changes:
483 if path not in changes:
485 break
484 break
486 if filesadded:
485 if filesadded:
487 dirs = list(pctx.dirs())
486 dirs = list(pctx.dirs())
488 dirs.insert(0, '')
487 dirs.insert(0, '')
489 for __ in pycompat.xrange(pick(filesadded)):
488 for __ in pycompat.xrange(pick(filesadded)):
490 pathstr = ''
489 pathstr = ''
491 while pathstr in dirs:
490 while pathstr in dirs:
492 path = [random.choice(dirs)]
491 path = [random.choice(dirs)]
493 if pick(dirsadded):
492 if pick(dirsadded):
494 path.append(random.choice(words))
493 path.append(random.choice(words))
495 path.append(random.choice(words))
494 path.append(random.choice(words))
496 pathstr = '/'.join(filter(None, path))
495 pathstr = '/'.join(filter(None, path))
497 data = (
496 data = (
498 '\n'.join(
497 '\n'.join(
499 makeline()
498 makeline()
500 for __ in pycompat.xrange(pick(linesinfilesadded))
499 for __ in pycompat.xrange(pick(linesinfilesadded))
501 )
500 )
502 + '\n'
501 + '\n'
503 )
502 )
504 changes[pathstr] = data
503 changes[pathstr] = data
505
504
506 def filectxfn(repo, memctx, path):
505 def filectxfn(repo, memctx, path):
507 if path not in changes:
506 if path not in changes:
508 return None
507 return None
509 return context.memfilectx(repo, memctx, path, changes[path])
508 return context.memfilectx(repo, memctx, path, changes[path])
510
509
511 if not changes:
510 if not changes:
512 continue
511 continue
513 if revs:
512 if revs:
514 date = repo['tip'].date()[0] + pick(interarrival)
513 date = repo['tip'].date()[0] + pick(interarrival)
515 else:
514 else:
516 date = time.time() - (86400 * count)
515 date = time.time() - (86400 * count)
517 # dates in mercurial must be positive, fit in 32-bit signed integers.
516 # dates in mercurial must be positive, fit in 32-bit signed integers.
518 date = min(0x7FFFFFFF, max(0, date))
517 date = min(0x7FFFFFFF, max(0, date))
519 user = random.choice(words) + '@' + random.choice(words)
518 user = random.choice(words) + '@' + random.choice(words)
520 mc = context.memctx(
519 mc = context.memctx(
521 repo,
520 repo,
522 pl,
521 pl,
523 makeline(minimum=2),
522 makeline(minimum=2),
524 sorted(changes),
523 sorted(changes),
525 filectxfn,
524 filectxfn,
526 user,
525 user,
527 '%d %d' % (date, pick(tzoffset)),
526 '%d %d' % (date, pick(tzoffset)),
528 )
527 )
529 newnode = mc.commit()
528 newnode = mc.commit()
530 heads.add(repo.changelog.rev(newnode))
529 heads.add(repo.changelog.rev(newnode))
531 heads.discard(r1)
530 heads.discard(r1)
532 heads.discard(r2)
531 heads.discard(r2)
533 progress.complete()
532 progress.complete()
534
533
535 lock.release()
534 lock.release()
536 wlock.release()
535 wlock.release()
537
536
538
537
539 def renamedirs(dirs, words):
538 def renamedirs(dirs, words):
540 '''Randomly rename the directory names in the per-dir file count dict.'''
539 '''Randomly rename the directory names in the per-dir file count dict.'''
541 wordgen = itertools.cycle(words)
540 wordgen = itertools.cycle(words)
542 replacements = {'': ''}
541 replacements = {'': ''}
543
542
544 def rename(dirpath):
543 def rename(dirpath):
545 """Recursively rename the directory and all path prefixes.
544 """Recursively rename the directory and all path prefixes.
546
545
547 The mapping from path to renamed path is stored for all path prefixes
546 The mapping from path to renamed path is stored for all path prefixes
548 as in dynamic programming, ensuring linear runtime and consistent
547 as in dynamic programming, ensuring linear runtime and consistent
549 renaming regardless of iteration order through the model.
548 renaming regardless of iteration order through the model.
550 """
549 """
551 if dirpath in replacements:
550 if dirpath in replacements:
552 return replacements[dirpath]
551 return replacements[dirpath]
553 head, _ = os.path.split(dirpath)
552 head, _ = os.path.split(dirpath)
554 if head:
553 if head:
555 head = rename(head)
554 head = rename(head)
556 else:
555 else:
557 head = ''
556 head = ''
558 renamed = os.path.join(head, next(wordgen))
557 renamed = os.path.join(head, next(wordgen))
559 replacements[dirpath] = renamed
558 replacements[dirpath] = renamed
560 return renamed
559 return renamed
561
560
562 result = []
561 result = []
563 for dirpath, count in dirs.iteritems():
562 for dirpath, count in dirs.iteritems():
564 result.append([rename(dirpath.lstrip(os.sep)), count])
563 result.append([rename(dirpath.lstrip(os.sep)), count])
565 return result
564 return result
@@ -1,668 +1,667 b''
1 # testparseutil.py - utilities to parse test script for check tools
1 # testparseutil.py - utilities to parse test script for check tools
2 #
2 #
3 # Copyright 2018 FUJIWARA Katsunori <foozy@lares.dti.ne.jp> and others
3 # Copyright 2018 FUJIWARA Katsunori <foozy@lares.dti.ne.jp> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import, print_function
9
8
10 import abc
9 import abc
11 import re
10 import re
12 import sys
11 import sys
13
12
14 ####################
13 ####################
15 # for Python3 compatibility (almost comes from mercurial/pycompat.py)
14 # for Python3 compatibility (almost comes from mercurial/pycompat.py)
16
15
17 ispy3 = sys.version_info[0] >= 3
16 ispy3 = sys.version_info[0] >= 3
18
17
19
18
20 def identity(a):
19 def identity(a):
21 return a
20 return a
22
21
23
22
24 def _rapply(f, xs):
23 def _rapply(f, xs):
25 if xs is None:
24 if xs is None:
26 # assume None means non-value of optional data
25 # assume None means non-value of optional data
27 return xs
26 return xs
28 if isinstance(xs, (list, set, tuple)):
27 if isinstance(xs, (list, set, tuple)):
29 return type(xs)(_rapply(f, x) for x in xs)
28 return type(xs)(_rapply(f, x) for x in xs)
30 if isinstance(xs, dict):
29 if isinstance(xs, dict):
31 return type(xs)((_rapply(f, k), _rapply(f, v)) for k, v in xs.items())
30 return type(xs)((_rapply(f, k), _rapply(f, v)) for k, v in xs.items())
32 return f(xs)
31 return f(xs)
33
32
34
33
35 def rapply(f, xs):
34 def rapply(f, xs):
36 if f is identity:
35 if f is identity:
37 # fast path mainly for py2
36 # fast path mainly for py2
38 return xs
37 return xs
39 return _rapply(f, xs)
38 return _rapply(f, xs)
40
39
41
40
42 if ispy3:
41 if ispy3:
43 import builtins
42 import builtins
44
43
45 def bytestr(s):
44 def bytestr(s):
46 # tiny version of pycompat.bytestr
45 # tiny version of pycompat.bytestr
47 return s.encode('latin1')
46 return s.encode('latin1')
48
47
49 def sysstr(s):
48 def sysstr(s):
50 if isinstance(s, builtins.str):
49 if isinstance(s, builtins.str):
51 return s
50 return s
52 return s.decode('latin-1')
51 return s.decode('latin-1')
53
52
54 def opentext(f):
53 def opentext(f):
55 return open(f, 'r')
54 return open(f, 'r')
56
55
57
56
58 else:
57 else:
59 bytestr = str
58 bytestr = str
60 sysstr = identity
59 sysstr = identity
61
60
62 opentext = open
61 opentext = open
63
62
64
63
65 def b2s(x):
64 def b2s(x):
66 # convert BYTES elements in "x" to SYSSTR recursively
65 # convert BYTES elements in "x" to SYSSTR recursively
67 return rapply(sysstr, x)
66 return rapply(sysstr, x)
68
67
69
68
70 def writeout(data):
69 def writeout(data):
71 # write "data" in BYTES into stdout
70 # write "data" in BYTES into stdout
72 sys.stdout.write(data)
71 sys.stdout.write(data)
73
72
74
73
75 def writeerr(data):
74 def writeerr(data):
76 # write "data" in BYTES into stderr
75 # write "data" in BYTES into stderr
77 sys.stderr.write(data)
76 sys.stderr.write(data)
78
77
79
78
80 ####################
79 ####################
81
80
82
81
83 class embeddedmatcher(object): # pytype: disable=ignored-metaclass
82 class embeddedmatcher(object): # pytype: disable=ignored-metaclass
84 """Base class to detect embedded code fragments in *.t test script"""
83 """Base class to detect embedded code fragments in *.t test script"""
85
84
86 __metaclass__ = abc.ABCMeta
85 __metaclass__ = abc.ABCMeta
87
86
88 def __init__(self, desc):
87 def __init__(self, desc):
89 self.desc = desc
88 self.desc = desc
90
89
91 @abc.abstractmethod
90 @abc.abstractmethod
92 def startsat(self, line):
91 def startsat(self, line):
93 """Examine whether embedded code starts at line
92 """Examine whether embedded code starts at line
94
93
95 This can return arbitrary object, and it is used as 'ctx' for
94 This can return arbitrary object, and it is used as 'ctx' for
96 subsequent method invocations.
95 subsequent method invocations.
97 """
96 """
98
97
99 @abc.abstractmethod
98 @abc.abstractmethod
100 def endsat(self, ctx, line):
99 def endsat(self, ctx, line):
101 """Examine whether embedded code ends at line"""
100 """Examine whether embedded code ends at line"""
102
101
103 @abc.abstractmethod
102 @abc.abstractmethod
104 def isinside(self, ctx, line):
103 def isinside(self, ctx, line):
105 """Examine whether line is inside embedded code, if not yet endsat"""
104 """Examine whether line is inside embedded code, if not yet endsat"""
106
105
107 @abc.abstractmethod
106 @abc.abstractmethod
108 def ignores(self, ctx):
107 def ignores(self, ctx):
109 """Examine whether detected embedded code should be ignored"""
108 """Examine whether detected embedded code should be ignored"""
110
109
111 @abc.abstractmethod
110 @abc.abstractmethod
112 def filename(self, ctx):
111 def filename(self, ctx):
113 """Return filename of embedded code
112 """Return filename of embedded code
114
113
115 If filename isn't specified for embedded code explicitly, this
114 If filename isn't specified for embedded code explicitly, this
116 returns None.
115 returns None.
117 """
116 """
118
117
119 @abc.abstractmethod
118 @abc.abstractmethod
120 def codeatstart(self, ctx, line):
119 def codeatstart(self, ctx, line):
121 """Return actual code at the start line of embedded code
120 """Return actual code at the start line of embedded code
122
121
123 This might return None, if the start line doesn't contain
122 This might return None, if the start line doesn't contain
124 actual code.
123 actual code.
125 """
124 """
126
125
127 @abc.abstractmethod
126 @abc.abstractmethod
128 def codeatend(self, ctx, line):
127 def codeatend(self, ctx, line):
129 """Return actual code at the end line of embedded code
128 """Return actual code at the end line of embedded code
130
129
131 This might return None, if the end line doesn't contain actual
130 This might return None, if the end line doesn't contain actual
132 code.
131 code.
133 """
132 """
134
133
135 @abc.abstractmethod
134 @abc.abstractmethod
136 def codeinside(self, ctx, line):
135 def codeinside(self, ctx, line):
137 """Return actual code at line inside embedded code"""
136 """Return actual code at line inside embedded code"""
138
137
139
138
140 def embedded(basefile, lines, errors, matchers):
139 def embedded(basefile, lines, errors, matchers):
141 """pick embedded code fragments up from given lines
140 """pick embedded code fragments up from given lines
142
141
143 This is common parsing logic, which examines specified matchers on
142 This is common parsing logic, which examines specified matchers on
144 given lines.
143 given lines.
145
144
146 :basefile: a name of a file, from which lines to be parsed come.
145 :basefile: a name of a file, from which lines to be parsed come.
147 :lines: to be parsed (might be a value returned by "open(basefile)")
146 :lines: to be parsed (might be a value returned by "open(basefile)")
148 :errors: an array, into which messages for detected error are stored
147 :errors: an array, into which messages for detected error are stored
149 :matchers: an array of embeddedmatcher objects
148 :matchers: an array of embeddedmatcher objects
150
149
151 This function yields '(filename, starts, ends, code)' tuple.
150 This function yields '(filename, starts, ends, code)' tuple.
152
151
153 :filename: a name of embedded code, if it is explicitly specified
152 :filename: a name of embedded code, if it is explicitly specified
154 (e.g. "foobar" of "cat >> foobar <<EOF").
153 (e.g. "foobar" of "cat >> foobar <<EOF").
155 Otherwise, this is None
154 Otherwise, this is None
156 :starts: line number (1-origin), at which embedded code starts (inclusive)
155 :starts: line number (1-origin), at which embedded code starts (inclusive)
157 :ends: line number (1-origin), at which embedded code ends (exclusive)
156 :ends: line number (1-origin), at which embedded code ends (exclusive)
158 :code: extracted embedded code, which is single-stringified
157 :code: extracted embedded code, which is single-stringified
159
158
160 >>> class ambigmatcher(object):
159 >>> class ambigmatcher(object):
161 ... # mock matcher class to examine implementation of
160 ... # mock matcher class to examine implementation of
162 ... # "ambiguous matching" corner case
161 ... # "ambiguous matching" corner case
163 ... def __init__(self, desc, matchfunc):
162 ... def __init__(self, desc, matchfunc):
164 ... self.desc = desc
163 ... self.desc = desc
165 ... self.matchfunc = matchfunc
164 ... self.matchfunc = matchfunc
166 ... def startsat(self, line):
165 ... def startsat(self, line):
167 ... return self.matchfunc(line)
166 ... return self.matchfunc(line)
168 >>> ambig1 = ambigmatcher('ambiguous #1',
167 >>> ambig1 = ambigmatcher('ambiguous #1',
169 ... lambda l: l.startswith(' $ cat '))
168 ... lambda l: l.startswith(' $ cat '))
170 >>> ambig2 = ambigmatcher('ambiguous #2',
169 >>> ambig2 = ambigmatcher('ambiguous #2',
171 ... lambda l: l.endswith('<< EOF\\n'))
170 ... lambda l: l.endswith('<< EOF\\n'))
172 >>> lines = [' $ cat > foo.py << EOF\\n']
171 >>> lines = [' $ cat > foo.py << EOF\\n']
173 >>> errors = []
172 >>> errors = []
174 >>> matchers = [ambig1, ambig2]
173 >>> matchers = [ambig1, ambig2]
175 >>> list(t for t in embedded('<dummy>', lines, errors, matchers))
174 >>> list(t for t in embedded('<dummy>', lines, errors, matchers))
176 []
175 []
177 >>> b2s(errors)
176 >>> b2s(errors)
178 ['<dummy>:1: ambiguous line for "ambiguous #1", "ambiguous #2"']
177 ['<dummy>:1: ambiguous line for "ambiguous #1", "ambiguous #2"']
179
178
180 """
179 """
181 matcher = None
180 matcher = None
182 ctx = filename = code = startline = None # for pyflakes
181 ctx = filename = code = startline = None # for pyflakes
183
182
184 for lineno, line in enumerate(lines, 1):
183 for lineno, line in enumerate(lines, 1):
185 if not line.endswith('\n'):
184 if not line.endswith('\n'):
186 line += '\n' # to normalize EOF line
185 line += '\n' # to normalize EOF line
187 if matcher: # now, inside embedded code
186 if matcher: # now, inside embedded code
188 if matcher.endsat(ctx, line):
187 if matcher.endsat(ctx, line):
189 codeatend = matcher.codeatend(ctx, line)
188 codeatend = matcher.codeatend(ctx, line)
190 if codeatend is not None:
189 if codeatend is not None:
191 code.append(codeatend)
190 code.append(codeatend)
192 if not matcher.ignores(ctx):
191 if not matcher.ignores(ctx):
193 yield (filename, startline, lineno, ''.join(code))
192 yield (filename, startline, lineno, ''.join(code))
194 matcher = None
193 matcher = None
195 # DO NOT "continue", because line might start next fragment
194 # DO NOT "continue", because line might start next fragment
196 elif not matcher.isinside(ctx, line):
195 elif not matcher.isinside(ctx, line):
197 # this is an error of basefile
196 # this is an error of basefile
198 # (if matchers are implemented correctly)
197 # (if matchers are implemented correctly)
199 errors.append(
198 errors.append(
200 '%s:%d: unexpected line for "%s"'
199 '%s:%d: unexpected line for "%s"'
201 % (basefile, lineno, matcher.desc)
200 % (basefile, lineno, matcher.desc)
202 )
201 )
203 # stop extracting embedded code by current 'matcher',
202 # stop extracting embedded code by current 'matcher',
204 # because appearance of unexpected line might mean
203 # because appearance of unexpected line might mean
205 # that expected end-of-embedded-code line might never
204 # that expected end-of-embedded-code line might never
206 # appear
205 # appear
207 matcher = None
206 matcher = None
208 # DO NOT "continue", because line might start next fragment
207 # DO NOT "continue", because line might start next fragment
209 else:
208 else:
210 code.append(matcher.codeinside(ctx, line))
209 code.append(matcher.codeinside(ctx, line))
211 continue
210 continue
212
211
213 # examine whether current line starts embedded code or not
212 # examine whether current line starts embedded code or not
214 assert not matcher
213 assert not matcher
215
214
216 matched = []
215 matched = []
217 for m in matchers:
216 for m in matchers:
218 ctx = m.startsat(line)
217 ctx = m.startsat(line)
219 if ctx:
218 if ctx:
220 matched.append((m, ctx))
219 matched.append((m, ctx))
221 if matched:
220 if matched:
222 if len(matched) > 1:
221 if len(matched) > 1:
223 # this is an error of matchers, maybe
222 # this is an error of matchers, maybe
224 errors.append(
223 errors.append(
225 '%s:%d: ambiguous line for %s'
224 '%s:%d: ambiguous line for %s'
226 % (
225 % (
227 basefile,
226 basefile,
228 lineno,
227 lineno,
229 ', '.join(['"%s"' % m.desc for m, c in matched]),
228 ', '.join(['"%s"' % m.desc for m, c in matched]),
230 )
229 )
231 )
230 )
232 # omit extracting embedded code, because choosing
231 # omit extracting embedded code, because choosing
233 # arbitrary matcher from matched ones might fail to
232 # arbitrary matcher from matched ones might fail to
234 # detect the end of embedded code as expected.
233 # detect the end of embedded code as expected.
235 continue
234 continue
236 matcher, ctx = matched[0]
235 matcher, ctx = matched[0]
237 filename = matcher.filename(ctx)
236 filename = matcher.filename(ctx)
238 code = []
237 code = []
239 codeatstart = matcher.codeatstart(ctx, line)
238 codeatstart = matcher.codeatstart(ctx, line)
240 if codeatstart is not None:
239 if codeatstart is not None:
241 code.append(codeatstart)
240 code.append(codeatstart)
242 startline = lineno
241 startline = lineno
243 else:
242 else:
244 startline = lineno + 1
243 startline = lineno + 1
245
244
246 if matcher:
245 if matcher:
247 # examine whether EOF ends embedded code, because embedded
246 # examine whether EOF ends embedded code, because embedded
248 # code isn't yet ended explicitly
247 # code isn't yet ended explicitly
249 if matcher.endsat(ctx, '\n'):
248 if matcher.endsat(ctx, '\n'):
250 codeatend = matcher.codeatend(ctx, '\n')
249 codeatend = matcher.codeatend(ctx, '\n')
251 if codeatend is not None:
250 if codeatend is not None:
252 code.append(codeatend)
251 code.append(codeatend)
253 if not matcher.ignores(ctx):
252 if not matcher.ignores(ctx):
254 yield (filename, startline, lineno + 1, ''.join(code))
253 yield (filename, startline, lineno + 1, ''.join(code))
255 else:
254 else:
256 # this is an error of basefile
255 # this is an error of basefile
257 # (if matchers are implemented correctly)
256 # (if matchers are implemented correctly)
258 errors.append(
257 errors.append(
259 '%s:%d: unexpected end of file for "%s"'
258 '%s:%d: unexpected end of file for "%s"'
260 % (basefile, lineno, matcher.desc)
259 % (basefile, lineno, matcher.desc)
261 )
260 )
262
261
263
262
264 # heredoc limit mark to ignore embedded code at check-code.py or so
263 # heredoc limit mark to ignore embedded code at check-code.py or so
265 heredocignorelimit = 'NO_CHECK_EOF'
264 heredocignorelimit = 'NO_CHECK_EOF'
266
265
267 # the pattern to match against cases below, and to return a limit mark
266 # the pattern to match against cases below, and to return a limit mark
268 # string as 'lname' group
267 # string as 'lname' group
269 #
268 #
270 # - << LIMITMARK
269 # - << LIMITMARK
271 # - << "LIMITMARK"
270 # - << "LIMITMARK"
272 # - << 'LIMITMARK'
271 # - << 'LIMITMARK'
273 heredoclimitpat = r'\s*<<\s*(?P<lquote>["\']?)(?P<limit>\w+)(?P=lquote)'
272 heredoclimitpat = r'\s*<<\s*(?P<lquote>["\']?)(?P<limit>\w+)(?P=lquote)'
274
273
275
274
276 class fileheredocmatcher(embeddedmatcher):
275 class fileheredocmatcher(embeddedmatcher):
277 """Detect "cat > FILE << LIMIT" style embedded code
276 """Detect "cat > FILE << LIMIT" style embedded code
278
277
279 >>> matcher = fileheredocmatcher('heredoc .py file', r'[^<]+\\.py')
278 >>> matcher = fileheredocmatcher('heredoc .py file', r'[^<]+\\.py')
280 >>> b2s(matcher.startsat(' $ cat > file.py << EOF\\n'))
279 >>> b2s(matcher.startsat(' $ cat > file.py << EOF\\n'))
281 ('file.py', ' > EOF\\n')
280 ('file.py', ' > EOF\\n')
282 >>> b2s(matcher.startsat(' $ cat >>file.py <<EOF\\n'))
281 >>> b2s(matcher.startsat(' $ cat >>file.py <<EOF\\n'))
283 ('file.py', ' > EOF\\n')
282 ('file.py', ' > EOF\\n')
284 >>> b2s(matcher.startsat(' $ cat> \\x27any file.py\\x27<< "EOF"\\n'))
283 >>> b2s(matcher.startsat(' $ cat> \\x27any file.py\\x27<< "EOF"\\n'))
285 ('any file.py', ' > EOF\\n')
284 ('any file.py', ' > EOF\\n')
286 >>> b2s(matcher.startsat(" $ cat > file.py << 'ANYLIMIT'\\n"))
285 >>> b2s(matcher.startsat(" $ cat > file.py << 'ANYLIMIT'\\n"))
287 ('file.py', ' > ANYLIMIT\\n')
286 ('file.py', ' > ANYLIMIT\\n')
288 >>> b2s(matcher.startsat(' $ cat<<ANYLIMIT>"file.py"\\n'))
287 >>> b2s(matcher.startsat(' $ cat<<ANYLIMIT>"file.py"\\n'))
289 ('file.py', ' > ANYLIMIT\\n')
288 ('file.py', ' > ANYLIMIT\\n')
290 >>> start = ' $ cat > file.py << EOF\\n'
289 >>> start = ' $ cat > file.py << EOF\\n'
291 >>> ctx = matcher.startsat(start)
290 >>> ctx = matcher.startsat(start)
292 >>> matcher.codeatstart(ctx, start)
291 >>> matcher.codeatstart(ctx, start)
293 >>> b2s(matcher.filename(ctx))
292 >>> b2s(matcher.filename(ctx))
294 'file.py'
293 'file.py'
295 >>> matcher.ignores(ctx)
294 >>> matcher.ignores(ctx)
296 False
295 False
297 >>> inside = ' > foo = 1\\n'
296 >>> inside = ' > foo = 1\\n'
298 >>> matcher.endsat(ctx, inside)
297 >>> matcher.endsat(ctx, inside)
299 False
298 False
300 >>> matcher.isinside(ctx, inside)
299 >>> matcher.isinside(ctx, inside)
301 True
300 True
302 >>> b2s(matcher.codeinside(ctx, inside))
301 >>> b2s(matcher.codeinside(ctx, inside))
303 'foo = 1\\n'
302 'foo = 1\\n'
304 >>> end = ' > EOF\\n'
303 >>> end = ' > EOF\\n'
305 >>> matcher.endsat(ctx, end)
304 >>> matcher.endsat(ctx, end)
306 True
305 True
307 >>> matcher.codeatend(ctx, end)
306 >>> matcher.codeatend(ctx, end)
308 >>> matcher.endsat(ctx, ' > EOFEOF\\n')
307 >>> matcher.endsat(ctx, ' > EOFEOF\\n')
309 False
308 False
310 >>> ctx = matcher.startsat(' $ cat > file.py << NO_CHECK_EOF\\n')
309 >>> ctx = matcher.startsat(' $ cat > file.py << NO_CHECK_EOF\\n')
311 >>> matcher.ignores(ctx)
310 >>> matcher.ignores(ctx)
312 True
311 True
313 """
312 """
314
313
315 _prefix = ' > '
314 _prefix = ' > '
316
315
317 def __init__(self, desc, namepat):
316 def __init__(self, desc, namepat):
318 super(fileheredocmatcher, self).__init__(desc)
317 super(fileheredocmatcher, self).__init__(desc)
319
318
320 # build the pattern to match against cases below (and ">>"
319 # build the pattern to match against cases below (and ">>"
321 # variants), and to return a target filename string as 'name'
320 # variants), and to return a target filename string as 'name'
322 # group
321 # group
323 #
322 #
324 # - > NAMEPAT
323 # - > NAMEPAT
325 # - > "NAMEPAT"
324 # - > "NAMEPAT"
326 # - > 'NAMEPAT'
325 # - > 'NAMEPAT'
327 namepat = (
326 namepat = (
328 r'\s*>>?\s*(?P<nquote>["\']?)(?P<name>%s)(?P=nquote)' % namepat
327 r'\s*>>?\s*(?P<nquote>["\']?)(?P<name>%s)(?P=nquote)' % namepat
329 )
328 )
330 self._fileres = [
329 self._fileres = [
331 # "cat > NAME << LIMIT" case
330 # "cat > NAME << LIMIT" case
332 re.compile(r' {2}\$ \s*cat' + namepat + heredoclimitpat),
331 re.compile(r' {2}\$ \s*cat' + namepat + heredoclimitpat),
333 # "cat << LIMIT > NAME" case
332 # "cat << LIMIT > NAME" case
334 re.compile(r' {2}\$ \s*cat' + heredoclimitpat + namepat),
333 re.compile(r' {2}\$ \s*cat' + heredoclimitpat + namepat),
335 ]
334 ]
336
335
337 def startsat(self, line):
336 def startsat(self, line):
338 # ctx is (filename, END-LINE-OF-EMBEDDED-CODE) tuple
337 # ctx is (filename, END-LINE-OF-EMBEDDED-CODE) tuple
339 for filere in self._fileres:
338 for filere in self._fileres:
340 matched = filere.match(line)
339 matched = filere.match(line)
341 if matched:
340 if matched:
342 return (
341 return (
343 matched.group('name'),
342 matched.group('name'),
344 ' > %s\n' % matched.group('limit'),
343 ' > %s\n' % matched.group('limit'),
345 )
344 )
346
345
347 def endsat(self, ctx, line):
346 def endsat(self, ctx, line):
348 return ctx[1] == line
347 return ctx[1] == line
349
348
350 def isinside(self, ctx, line):
349 def isinside(self, ctx, line):
351 return line.startswith(self._prefix)
350 return line.startswith(self._prefix)
352
351
353 def ignores(self, ctx):
352 def ignores(self, ctx):
354 return ' > %s\n' % heredocignorelimit == ctx[1]
353 return ' > %s\n' % heredocignorelimit == ctx[1]
355
354
356 def filename(self, ctx):
355 def filename(self, ctx):
357 return ctx[0]
356 return ctx[0]
358
357
359 def codeatstart(self, ctx, line):
358 def codeatstart(self, ctx, line):
360 return None # no embedded code at start line
359 return None # no embedded code at start line
361
360
362 def codeatend(self, ctx, line):
361 def codeatend(self, ctx, line):
363 return None # no embedded code at end line
362 return None # no embedded code at end line
364
363
365 def codeinside(self, ctx, line):
364 def codeinside(self, ctx, line):
366 return line[len(self._prefix) :] # strip prefix
365 return line[len(self._prefix) :] # strip prefix
367
366
368
367
369 ####
368 ####
370 # for embedded python script
369 # for embedded python script
371
370
372
371
373 class pydoctestmatcher(embeddedmatcher):
372 class pydoctestmatcher(embeddedmatcher):
374 """Detect ">>> code" style embedded python code
373 """Detect ">>> code" style embedded python code
375
374
376 >>> matcher = pydoctestmatcher()
375 >>> matcher = pydoctestmatcher()
377 >>> startline = ' >>> foo = 1\\n'
376 >>> startline = ' >>> foo = 1\\n'
378 >>> matcher.startsat(startline)
377 >>> matcher.startsat(startline)
379 True
378 True
380 >>> matcher.startsat(' ... foo = 1\\n')
379 >>> matcher.startsat(' ... foo = 1\\n')
381 False
380 False
382 >>> ctx = matcher.startsat(startline)
381 >>> ctx = matcher.startsat(startline)
383 >>> matcher.filename(ctx)
382 >>> matcher.filename(ctx)
384 >>> matcher.ignores(ctx)
383 >>> matcher.ignores(ctx)
385 False
384 False
386 >>> b2s(matcher.codeatstart(ctx, startline))
385 >>> b2s(matcher.codeatstart(ctx, startline))
387 'foo = 1\\n'
386 'foo = 1\\n'
388 >>> inside = ' >>> foo = 1\\n'
387 >>> inside = ' >>> foo = 1\\n'
389 >>> matcher.endsat(ctx, inside)
388 >>> matcher.endsat(ctx, inside)
390 False
389 False
391 >>> matcher.isinside(ctx, inside)
390 >>> matcher.isinside(ctx, inside)
392 True
391 True
393 >>> b2s(matcher.codeinside(ctx, inside))
392 >>> b2s(matcher.codeinside(ctx, inside))
394 'foo = 1\\n'
393 'foo = 1\\n'
395 >>> inside = ' ... foo = 1\\n'
394 >>> inside = ' ... foo = 1\\n'
396 >>> matcher.endsat(ctx, inside)
395 >>> matcher.endsat(ctx, inside)
397 False
396 False
398 >>> matcher.isinside(ctx, inside)
397 >>> matcher.isinside(ctx, inside)
399 True
398 True
400 >>> b2s(matcher.codeinside(ctx, inside))
399 >>> b2s(matcher.codeinside(ctx, inside))
401 'foo = 1\\n'
400 'foo = 1\\n'
402 >>> inside = ' expected output\\n'
401 >>> inside = ' expected output\\n'
403 >>> matcher.endsat(ctx, inside)
402 >>> matcher.endsat(ctx, inside)
404 False
403 False
405 >>> matcher.isinside(ctx, inside)
404 >>> matcher.isinside(ctx, inside)
406 True
405 True
407 >>> b2s(matcher.codeinside(ctx, inside))
406 >>> b2s(matcher.codeinside(ctx, inside))
408 '\\n'
407 '\\n'
409 >>> inside = ' \\n'
408 >>> inside = ' \\n'
410 >>> matcher.endsat(ctx, inside)
409 >>> matcher.endsat(ctx, inside)
411 False
410 False
412 >>> matcher.isinside(ctx, inside)
411 >>> matcher.isinside(ctx, inside)
413 True
412 True
414 >>> b2s(matcher.codeinside(ctx, inside))
413 >>> b2s(matcher.codeinside(ctx, inside))
415 '\\n'
414 '\\n'
416 >>> end = ' $ foo bar\\n'
415 >>> end = ' $ foo bar\\n'
417 >>> matcher.endsat(ctx, end)
416 >>> matcher.endsat(ctx, end)
418 True
417 True
419 >>> matcher.codeatend(ctx, end)
418 >>> matcher.codeatend(ctx, end)
420 >>> end = '\\n'
419 >>> end = '\\n'
421 >>> matcher.endsat(ctx, end)
420 >>> matcher.endsat(ctx, end)
422 True
421 True
423 >>> matcher.codeatend(ctx, end)
422 >>> matcher.codeatend(ctx, end)
424 """
423 """
425
424
426 _prefix = ' >>> '
425 _prefix = ' >>> '
427 _prefixre = re.compile(r' {2}(>>>|\.\.\.) ')
426 _prefixre = re.compile(r' {2}(>>>|\.\.\.) ')
428
427
429 # If a line matches against not _prefixre but _outputre, that line
428 # If a line matches against not _prefixre but _outputre, that line
430 # is "an expected output line" (= not a part of code fragment).
429 # is "an expected output line" (= not a part of code fragment).
431 #
430 #
432 # Strictly speaking, a line matching against "(#if|#else|#endif)"
431 # Strictly speaking, a line matching against "(#if|#else|#endif)"
433 # is also treated similarly in "inline python code" semantics by
432 # is also treated similarly in "inline python code" semantics by
434 # run-tests.py. But "directive line inside inline python code"
433 # run-tests.py. But "directive line inside inline python code"
435 # should be rejected by Mercurial reviewers. Therefore, this
434 # should be rejected by Mercurial reviewers. Therefore, this
436 # regexp does not matche against such directive lines.
435 # regexp does not matche against such directive lines.
437 _outputre = re.compile(r' {2}$| {2}[^$]')
436 _outputre = re.compile(r' {2}$| {2}[^$]')
438
437
439 def __init__(self):
438 def __init__(self):
440 super(pydoctestmatcher, self).__init__("doctest style python code")
439 super(pydoctestmatcher, self).__init__("doctest style python code")
441
440
442 def startsat(self, line):
441 def startsat(self, line):
443 # ctx is "True"
442 # ctx is "True"
444 return line.startswith(self._prefix)
443 return line.startswith(self._prefix)
445
444
446 def endsat(self, ctx, line):
445 def endsat(self, ctx, line):
447 return not (self._prefixre.match(line) or self._outputre.match(line))
446 return not (self._prefixre.match(line) or self._outputre.match(line))
448
447
449 def isinside(self, ctx, line):
448 def isinside(self, ctx, line):
450 return True # always true, if not yet ended
449 return True # always true, if not yet ended
451
450
452 def ignores(self, ctx):
451 def ignores(self, ctx):
453 return False # should be checked always
452 return False # should be checked always
454
453
455 def filename(self, ctx):
454 def filename(self, ctx):
456 return None # no filename
455 return None # no filename
457
456
458 def codeatstart(self, ctx, line):
457 def codeatstart(self, ctx, line):
459 return line[len(self._prefix) :] # strip prefix ' >>> '/' ... '
458 return line[len(self._prefix) :] # strip prefix ' >>> '/' ... '
460
459
461 def codeatend(self, ctx, line):
460 def codeatend(self, ctx, line):
462 return None # no embedded code at end line
461 return None # no embedded code at end line
463
462
464 def codeinside(self, ctx, line):
463 def codeinside(self, ctx, line):
465 if self._prefixre.match(line):
464 if self._prefixre.match(line):
466 return line[len(self._prefix) :] # strip prefix ' >>> '/' ... '
465 return line[len(self._prefix) :] # strip prefix ' >>> '/' ... '
467 return '\n' # an expected output line is treated as an empty line
466 return '\n' # an expected output line is treated as an empty line
468
467
469
468
470 class pyheredocmatcher(embeddedmatcher):
469 class pyheredocmatcher(embeddedmatcher):
471 """Detect "python << LIMIT" style embedded python code
470 """Detect "python << LIMIT" style embedded python code
472
471
473 >>> matcher = pyheredocmatcher()
472 >>> matcher = pyheredocmatcher()
474 >>> b2s(matcher.startsat(' $ python << EOF\\n'))
473 >>> b2s(matcher.startsat(' $ python << EOF\\n'))
475 ' > EOF\\n'
474 ' > EOF\\n'
476 >>> b2s(matcher.startsat(' $ $PYTHON <<EOF\\n'))
475 >>> b2s(matcher.startsat(' $ $PYTHON <<EOF\\n'))
477 ' > EOF\\n'
476 ' > EOF\\n'
478 >>> b2s(matcher.startsat(' $ "$PYTHON"<< "EOF"\\n'))
477 >>> b2s(matcher.startsat(' $ "$PYTHON"<< "EOF"\\n'))
479 ' > EOF\\n'
478 ' > EOF\\n'
480 >>> b2s(matcher.startsat(" $ $PYTHON << 'ANYLIMIT'\\n"))
479 >>> b2s(matcher.startsat(" $ $PYTHON << 'ANYLIMIT'\\n"))
481 ' > ANYLIMIT\\n'
480 ' > ANYLIMIT\\n'
482 >>> matcher.startsat(' $ "$PYTHON" < EOF\\n')
481 >>> matcher.startsat(' $ "$PYTHON" < EOF\\n')
483 >>> start = ' $ python << EOF\\n'
482 >>> start = ' $ python << EOF\\n'
484 >>> ctx = matcher.startsat(start)
483 >>> ctx = matcher.startsat(start)
485 >>> matcher.codeatstart(ctx, start)
484 >>> matcher.codeatstart(ctx, start)
486 >>> matcher.filename(ctx)
485 >>> matcher.filename(ctx)
487 >>> matcher.ignores(ctx)
486 >>> matcher.ignores(ctx)
488 False
487 False
489 >>> inside = ' > foo = 1\\n'
488 >>> inside = ' > foo = 1\\n'
490 >>> matcher.endsat(ctx, inside)
489 >>> matcher.endsat(ctx, inside)
491 False
490 False
492 >>> matcher.isinside(ctx, inside)
491 >>> matcher.isinside(ctx, inside)
493 True
492 True
494 >>> b2s(matcher.codeinside(ctx, inside))
493 >>> b2s(matcher.codeinside(ctx, inside))
495 'foo = 1\\n'
494 'foo = 1\\n'
496 >>> end = ' > EOF\\n'
495 >>> end = ' > EOF\\n'
497 >>> matcher.endsat(ctx, end)
496 >>> matcher.endsat(ctx, end)
498 True
497 True
499 >>> matcher.codeatend(ctx, end)
498 >>> matcher.codeatend(ctx, end)
500 >>> matcher.endsat(ctx, ' > EOFEOF\\n')
499 >>> matcher.endsat(ctx, ' > EOFEOF\\n')
501 False
500 False
502 >>> ctx = matcher.startsat(' $ python << NO_CHECK_EOF\\n')
501 >>> ctx = matcher.startsat(' $ python << NO_CHECK_EOF\\n')
503 >>> matcher.ignores(ctx)
502 >>> matcher.ignores(ctx)
504 True
503 True
505 """
504 """
506
505
507 _prefix = ' > '
506 _prefix = ' > '
508
507
509 _startre = re.compile(
508 _startre = re.compile(
510 r' {2}\$ (\$PYTHON|"\$PYTHON"|python).*' + heredoclimitpat
509 r' {2}\$ (\$PYTHON|"\$PYTHON"|python).*' + heredoclimitpat
511 )
510 )
512
511
513 def __init__(self):
512 def __init__(self):
514 super(pyheredocmatcher, self).__init__("heredoc python invocation")
513 super(pyheredocmatcher, self).__init__("heredoc python invocation")
515
514
516 def startsat(self, line):
515 def startsat(self, line):
517 # ctx is END-LINE-OF-EMBEDDED-CODE
516 # ctx is END-LINE-OF-EMBEDDED-CODE
518 matched = self._startre.match(line)
517 matched = self._startre.match(line)
519 if matched:
518 if matched:
520 return ' > %s\n' % matched.group('limit')
519 return ' > %s\n' % matched.group('limit')
521
520
522 def endsat(self, ctx, line):
521 def endsat(self, ctx, line):
523 return ctx == line
522 return ctx == line
524
523
525 def isinside(self, ctx, line):
524 def isinside(self, ctx, line):
526 return line.startswith(self._prefix)
525 return line.startswith(self._prefix)
527
526
528 def ignores(self, ctx):
527 def ignores(self, ctx):
529 return ' > %s\n' % heredocignorelimit == ctx
528 return ' > %s\n' % heredocignorelimit == ctx
530
529
531 def filename(self, ctx):
530 def filename(self, ctx):
532 return None # no filename
531 return None # no filename
533
532
534 def codeatstart(self, ctx, line):
533 def codeatstart(self, ctx, line):
535 return None # no embedded code at start line
534 return None # no embedded code at start line
536
535
537 def codeatend(self, ctx, line):
536 def codeatend(self, ctx, line):
538 return None # no embedded code at end line
537 return None # no embedded code at end line
539
538
540 def codeinside(self, ctx, line):
539 def codeinside(self, ctx, line):
541 return line[len(self._prefix) :] # strip prefix
540 return line[len(self._prefix) :] # strip prefix
542
541
543
542
544 _pymatchers = [
543 _pymatchers = [
545 pydoctestmatcher(),
544 pydoctestmatcher(),
546 pyheredocmatcher(),
545 pyheredocmatcher(),
547 # use '[^<]+' instead of '\S+', in order to match against
546 # use '[^<]+' instead of '\S+', in order to match against
548 # paths including whitespaces
547 # paths including whitespaces
549 fileheredocmatcher('heredoc .py file', r'[^<]+\.py'),
548 fileheredocmatcher('heredoc .py file', r'[^<]+\.py'),
550 ]
549 ]
551
550
552
551
553 def pyembedded(basefile, lines, errors):
552 def pyembedded(basefile, lines, errors):
554 return embedded(basefile, lines, errors, _pymatchers)
553 return embedded(basefile, lines, errors, _pymatchers)
555
554
556
555
557 ####
556 ####
558 # for embedded shell script
557 # for embedded shell script
559
558
560 _shmatchers = [
559 _shmatchers = [
561 # use '[^<]+' instead of '\S+', in order to match against
560 # use '[^<]+' instead of '\S+', in order to match against
562 # paths including whitespaces
561 # paths including whitespaces
563 fileheredocmatcher('heredoc .sh file', r'[^<]+\.sh'),
562 fileheredocmatcher('heredoc .sh file', r'[^<]+\.sh'),
564 ]
563 ]
565
564
566
565
567 def shembedded(basefile, lines, errors):
566 def shembedded(basefile, lines, errors):
568 return embedded(basefile, lines, errors, _shmatchers)
567 return embedded(basefile, lines, errors, _shmatchers)
569
568
570
569
571 ####
570 ####
572 # for embedded hgrc configuration
571 # for embedded hgrc configuration
573
572
574 _hgrcmatchers = [
573 _hgrcmatchers = [
575 # use '[^<]+' instead of '\S+', in order to match against
574 # use '[^<]+' instead of '\S+', in order to match against
576 # paths including whitespaces
575 # paths including whitespaces
577 fileheredocmatcher(
576 fileheredocmatcher(
578 'heredoc hgrc file', r'(([^/<]+/)+hgrc|\$HGRCPATH|\${HGRCPATH})'
577 'heredoc hgrc file', r'(([^/<]+/)+hgrc|\$HGRCPATH|\${HGRCPATH})'
579 ),
578 ),
580 ]
579 ]
581
580
582
581
583 def hgrcembedded(basefile, lines, errors):
582 def hgrcembedded(basefile, lines, errors):
584 return embedded(basefile, lines, errors, _hgrcmatchers)
583 return embedded(basefile, lines, errors, _hgrcmatchers)
585
584
586
585
587 ####
586 ####
588
587
589 if __name__ == "__main__":
588 if __name__ == "__main__":
590 import optparse
589 import optparse
591 import sys
590 import sys
592
591
593 def showembedded(basefile, lines, embeddedfunc, opts):
592 def showembedded(basefile, lines, embeddedfunc, opts):
594 errors = []
593 errors = []
595 for name, starts, ends, code in embeddedfunc(basefile, lines, errors):
594 for name, starts, ends, code in embeddedfunc(basefile, lines, errors):
596 if not name:
595 if not name:
597 name = '<anonymous>'
596 name = '<anonymous>'
598 writeout("%s:%d: %s starts\n" % (basefile, starts, name))
597 writeout("%s:%d: %s starts\n" % (basefile, starts, name))
599 if opts.verbose and code:
598 if opts.verbose and code:
600 writeout(" |%s\n" % "\n |".join(l for l in code.splitlines()))
599 writeout(" |%s\n" % "\n |".join(l for l in code.splitlines()))
601 writeout("%s:%d: %s ends\n" % (basefile, ends, name))
600 writeout("%s:%d: %s ends\n" % (basefile, ends, name))
602 for e in errors:
601 for e in errors:
603 writeerr("%s\n" % e)
602 writeerr("%s\n" % e)
604 return len(errors)
603 return len(errors)
605
604
606 def applyembedded(args, embeddedfunc, opts):
605 def applyembedded(args, embeddedfunc, opts):
607 ret = 0
606 ret = 0
608 if args:
607 if args:
609 for f in args:
608 for f in args:
610 with opentext(f) as fp:
609 with opentext(f) as fp:
611 if showembedded(f, fp, embeddedfunc, opts):
610 if showembedded(f, fp, embeddedfunc, opts):
612 ret = 1
611 ret = 1
613 else:
612 else:
614 lines = [l for l in sys.stdin.readlines()]
613 lines = [l for l in sys.stdin.readlines()]
615 if showembedded('<stdin>', lines, embeddedfunc, opts):
614 if showembedded('<stdin>', lines, embeddedfunc, opts):
616 ret = 1
615 ret = 1
617 return ret
616 return ret
618
617
619 commands = {}
618 commands = {}
620
619
621 def command(name, desc):
620 def command(name, desc):
622 def wrap(func):
621 def wrap(func):
623 commands[name] = (desc, func)
622 commands[name] = (desc, func)
624
623
625 return wrap
624 return wrap
626
625
627 @command("pyembedded", "detect embedded python script")
626 @command("pyembedded", "detect embedded python script")
628 def pyembeddedcmd(args, opts):
627 def pyembeddedcmd(args, opts):
629 return applyembedded(args, pyembedded, opts)
628 return applyembedded(args, pyembedded, opts)
630
629
631 @command("shembedded", "detect embedded shell script")
630 @command("shembedded", "detect embedded shell script")
632 def shembeddedcmd(args, opts):
631 def shembeddedcmd(args, opts):
633 return applyembedded(args, shembedded, opts)
632 return applyembedded(args, shembedded, opts)
634
633
635 @command("hgrcembedded", "detect embedded hgrc configuration")
634 @command("hgrcembedded", "detect embedded hgrc configuration")
636 def hgrcembeddedcmd(args, opts):
635 def hgrcembeddedcmd(args, opts):
637 return applyembedded(args, hgrcembedded, opts)
636 return applyembedded(args, hgrcembedded, opts)
638
637
639 availablecommands = "\n".join(
638 availablecommands = "\n".join(
640 [" - %s: %s" % (key, value[0]) for key, value in commands.items()]
639 [" - %s: %s" % (key, value[0]) for key, value in commands.items()]
641 )
640 )
642
641
643 parser = optparse.OptionParser(
642 parser = optparse.OptionParser(
644 """%prog COMMAND [file ...]
643 """%prog COMMAND [file ...]
645
644
646 Pick up embedded code fragments from given file(s) or stdin, and list
645 Pick up embedded code fragments from given file(s) or stdin, and list
647 up start/end lines of them in standard compiler format
646 up start/end lines of them in standard compiler format
648 ("FILENAME:LINENO:").
647 ("FILENAME:LINENO:").
649
648
650 Available commands are:
649 Available commands are:
651 """
650 """
652 + availablecommands
651 + availablecommands
653 + """
652 + """
654 """
653 """
655 )
654 )
656 parser.add_option(
655 parser.add_option(
657 "-v",
656 "-v",
658 "--verbose",
657 "--verbose",
659 help="enable additional output (e.g. actual code)",
658 help="enable additional output (e.g. actual code)",
660 action="store_true",
659 action="store_true",
661 )
660 )
662 (opts, args) = parser.parse_args()
661 (opts, args) = parser.parse_args()
663
662
664 if not args or args[0] not in commands:
663 if not args or args[0] not in commands:
665 parser.print_help()
664 parser.print_help()
666 sys.exit(255)
665 sys.exit(255)
667
666
668 sys.exit(commands[args[0]][1](args[1:], opts))
667 sys.exit(commands[args[0]][1](args[1:], opts))
@@ -1,57 +1,56 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 # Undump a dump from dumprevlog
2 # Undump a dump from dumprevlog
3 # $ hg init
3 # $ hg init
4 # $ undumprevlog < repo.dump
4 # $ undumprevlog < repo.dump
5
5
6 from __future__ import absolute_import, print_function
7
6
8 import sys
7 import sys
9 from mercurial.node import bin
8 from mercurial.node import bin
10 from mercurial import (
9 from mercurial import (
11 encoding,
10 encoding,
12 revlog,
11 revlog,
13 transaction,
12 transaction,
14 vfs as vfsmod,
13 vfs as vfsmod,
15 )
14 )
16 from mercurial.utils import procutil
15 from mercurial.utils import procutil
17
16
18 from mercurial.revlogutils import (
17 from mercurial.revlogutils import (
19 constants as revlog_constants,
18 constants as revlog_constants,
20 )
19 )
21
20
22 for fp in (sys.stdin, sys.stdout, sys.stderr):
21 for fp in (sys.stdin, sys.stdout, sys.stderr):
23 procutil.setbinary(fp)
22 procutil.setbinary(fp)
24
23
25 opener = vfsmod.vfs(b'.', False)
24 opener = vfsmod.vfs(b'.', False)
26 tr = transaction.transaction(
25 tr = transaction.transaction(
27 sys.stderr.write, opener, {b'store': opener}, b"undump.journal"
26 sys.stderr.write, opener, {b'store': opener}, b"undump.journal"
28 )
27 )
29 while True:
28 while True:
30 l = sys.stdin.readline()
29 l = sys.stdin.readline()
31 if not l:
30 if not l:
32 break
31 break
33 if l.startswith("file:"):
32 if l.startswith("file:"):
34 f = encoding.strtolocal(l[6:-1])
33 f = encoding.strtolocal(l[6:-1])
35 assert f.endswith(b'.i')
34 assert f.endswith(b'.i')
36 r = revlog.revlog(
35 r = revlog.revlog(
37 opener,
36 opener,
38 target=(revlog_constants.KIND_OTHER, b'undump-revlog'),
37 target=(revlog_constants.KIND_OTHER, b'undump-revlog'),
39 radix=f[:-2],
38 radix=f[:-2],
40 )
39 )
41 procutil.stdout.write(b'%s\n' % f)
40 procutil.stdout.write(b'%s\n' % f)
42 elif l.startswith("node:"):
41 elif l.startswith("node:"):
43 n = bin(l[6:-1])
42 n = bin(l[6:-1])
44 elif l.startswith("linkrev:"):
43 elif l.startswith("linkrev:"):
45 lr = int(l[9:-1])
44 lr = int(l[9:-1])
46 elif l.startswith("parents:"):
45 elif l.startswith("parents:"):
47 p = l[9:-1].split()
46 p = l[9:-1].split()
48 p1 = bin(p[0])
47 p1 = bin(p[0])
49 p2 = bin(p[1])
48 p2 = bin(p[1])
50 elif l.startswith("length:"):
49 elif l.startswith("length:"):
51 length = int(l[8:-1])
50 length = int(l[8:-1])
52 sys.stdin.readline() # start marker
51 sys.stdin.readline() # start marker
53 d = encoding.strtolocal(sys.stdin.read(length))
52 d = encoding.strtolocal(sys.stdin.read(length))
54 sys.stdin.readline() # end marker
53 sys.stdin.readline() # end marker
55 r.addrevision(d, tr, lr, p1, p2)
54 r.addrevision(d, tr, lr, p1, p2)
56
55
57 tr.close()
56 tr.close()
@@ -1,140 +1,139 b''
1 # An example WSGI script for IIS/isapi-wsgi to export multiple hgweb repos
1 # An example WSGI script for IIS/isapi-wsgi to export multiple hgweb repos
2 # Copyright 2010-2016 Sune Foldager <cyano@me.com>
2 # Copyright 2010-2016 Sune Foldager <cyano@me.com>
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6 #
6 #
7 # Requirements:
7 # Requirements:
8 # - Python 2.7, preferably 64 bit
8 # - Python 2.7, preferably 64 bit
9 # - Mercurial installed from source (python setup.py install) or download the
9 # - Mercurial installed from source (python setup.py install) or download the
10 # python module installer from https://www.mercurial-scm.org/wiki/Download
10 # python module installer from https://www.mercurial-scm.org/wiki/Download
11 # - IIS 7 or newer
11 # - IIS 7 or newer
12 #
12 #
13 #
13 #
14 # Installation and use:
14 # Installation and use:
15 #
15 #
16 # - Download or clone the isapi-wsgi source and run python setup.py install.
16 # - Download or clone the isapi-wsgi source and run python setup.py install.
17 # https://github.com/hexdump42/isapi-wsgi
17 # https://github.com/hexdump42/isapi-wsgi
18 #
18 #
19 # - Create a directory to hold the shim dll, config files etc. This can reside
19 # - Create a directory to hold the shim dll, config files etc. This can reside
20 # inside the standard IIS directory, C:\inetpub, or anywhere else. Copy this
20 # inside the standard IIS directory, C:\inetpub, or anywhere else. Copy this
21 # script there.
21 # script there.
22 #
22 #
23 # - Run this script (i.e. python hgwebdir_wsgi.py) to get a shim dll. The
23 # - Run this script (i.e. python hgwebdir_wsgi.py) to get a shim dll. The
24 # shim is identical for all scripts, so you can just copy and rename one
24 # shim is identical for all scripts, so you can just copy and rename one
25 # from an earlier run, if you wish. The shim needs to reside in the same
25 # from an earlier run, if you wish. The shim needs to reside in the same
26 # directory as this script.
26 # directory as this script.
27 #
27 #
28 # - Start IIS manager and create a new app pool:
28 # - Start IIS manager and create a new app pool:
29 # .NET CLR Version: No Managed Code
29 # .NET CLR Version: No Managed Code
30 # Advanced Settings: Enable 32 Bit Applications, if using 32 bit Python.
30 # Advanced Settings: Enable 32 Bit Applications, if using 32 bit Python.
31 # You can adjust the identity and maximum worker processes if you wish. This
31 # You can adjust the identity and maximum worker processes if you wish. This
32 # setup works fine with multiple worker processes.
32 # setup works fine with multiple worker processes.
33 #
33 #
34 # - Create an IIS application where your hgwebdir is to be served from.
34 # - Create an IIS application where your hgwebdir is to be served from.
35 # Assign it the app pool you just created and point its physical path to the
35 # Assign it the app pool you just created and point its physical path to the
36 # directory you created.
36 # directory you created.
37 #
37 #
38 # - In the application, remove all handler mappings and setup a wildcard script
38 # - In the application, remove all handler mappings and setup a wildcard script
39 # handler mapping of type IsapiModule with the shim dll as its executable.
39 # handler mapping of type IsapiModule with the shim dll as its executable.
40 # This file MUST reside in the same directory as the shim. The easiest way
40 # This file MUST reside in the same directory as the shim. The easiest way
41 # to do all this is to close IIS manager, place a web.config file in your
41 # to do all this is to close IIS manager, place a web.config file in your
42 # directory and start IIS manager again. The file should contain:
42 # directory and start IIS manager again. The file should contain:
43 #
43 #
44 # <?xml version="1.0" encoding="UTF-8"?>
44 # <?xml version="1.0" encoding="UTF-8"?>
45 # <configuration>
45 # <configuration>
46 # <system.webServer>
46 # <system.webServer>
47 # <handlers accessPolicy="Read, Script">
47 # <handlers accessPolicy="Read, Script">
48 # <clear />
48 # <clear />
49 # <add name="hgwebdir" path="*" verb="*" modules="IsapiModule"
49 # <add name="hgwebdir" path="*" verb="*" modules="IsapiModule"
50 # scriptProcessor="C:\your\directory\_hgwebdir_wsgi.dll"
50 # scriptProcessor="C:\your\directory\_hgwebdir_wsgi.dll"
51 # resourceType="Unspecified" requireAccess="None"
51 # resourceType="Unspecified" requireAccess="None"
52 # preCondition="bitness64" />
52 # preCondition="bitness64" />
53 # </handlers>
53 # </handlers>
54 # </system.webServer>
54 # </system.webServer>
55 # </configuration>
55 # </configuration>
56 #
56 #
57 # Where "bitness64" should be replaced with "bitness32" for 32 bit Python.
57 # Where "bitness64" should be replaced with "bitness32" for 32 bit Python.
58 #
58 #
59 # - Edit ISAPI And CGI Restrictions on the web server (global setting). Add a
59 # - Edit ISAPI And CGI Restrictions on the web server (global setting). Add a
60 # restriction pointing to your shim dll and allow it to run.
60 # restriction pointing to your shim dll and allow it to run.
61 #
61 #
62 # - Create a configuration file in your directory and adjust the configuration
62 # - Create a configuration file in your directory and adjust the configuration
63 # variables below to match your needs. Example configuration:
63 # variables below to match your needs. Example configuration:
64 #
64 #
65 # [web]
65 # [web]
66 # style = gitweb
66 # style = gitweb
67 # push_ssl = false
67 # push_ssl = false
68 # allow_push = *
68 # allow_push = *
69 # encoding = utf8
69 # encoding = utf8
70 #
70 #
71 # [server]
71 # [server]
72 # validate = true
72 # validate = true
73 #
73 #
74 # [paths]
74 # [paths]
75 # repo1 = c:\your\directory\repo1
75 # repo1 = c:\your\directory\repo1
76 # repo2 = c:\your\directory\repo2
76 # repo2 = c:\your\directory\repo2
77 #
77 #
78 # - Restart the web server and see if things are running.
78 # - Restart the web server and see if things are running.
79 #
79 #
80
80
81 from __future__ import absolute_import
82
81
83 # Configuration file location
82 # Configuration file location
84 hgweb_config = r'c:\your\directory\wsgi.config'
83 hgweb_config = r'c:\your\directory\wsgi.config'
85
84
86 # Global settings for IIS path translation
85 # Global settings for IIS path translation
87 path_strip = 0 # Strip this many path elements off (when using url rewrite)
86 path_strip = 0 # Strip this many path elements off (when using url rewrite)
88 path_prefix = 1 # This many path elements are prefixes (depends on the
87 path_prefix = 1 # This many path elements are prefixes (depends on the
89 # virtual path of the IIS application).
88 # virtual path of the IIS application).
90
89
91 import sys
90 import sys
92
91
93 # Adjust python path if this is not a system-wide install
92 # Adjust python path if this is not a system-wide install
94 # sys.path.insert(0, r'C:\your\custom\hg\build\lib.win32-2.7')
93 # sys.path.insert(0, r'C:\your\custom\hg\build\lib.win32-2.7')
95
94
96 # Enable tracing. Run 'python -m win32traceutil' to debug
95 # Enable tracing. Run 'python -m win32traceutil' to debug
97 if getattr(sys, 'isapidllhandle', None) is not None:
96 if getattr(sys, 'isapidllhandle', None) is not None:
98 import win32traceutil
97 import win32traceutil
99
98
100 win32traceutil.SetupForPrint # silence unused import warning
99 win32traceutil.SetupForPrint # silence unused import warning
101
100
102 import isapi_wsgi
101 import isapi_wsgi
103 from mercurial.hgweb.hgwebdir_mod import hgwebdir
102 from mercurial.hgweb.hgwebdir_mod import hgwebdir
104
103
105 # Example tweak: Replace isapi_wsgi's handler to provide better error message
104 # Example tweak: Replace isapi_wsgi's handler to provide better error message
106 # Other stuff could also be done here, like logging errors etc.
105 # Other stuff could also be done here, like logging errors etc.
107 class WsgiHandler(isapi_wsgi.IsapiWsgiHandler):
106 class WsgiHandler(isapi_wsgi.IsapiWsgiHandler):
108 error_status = '500 Internal Server Error' # less silly error message
107 error_status = '500 Internal Server Error' # less silly error message
109
108
110
109
111 isapi_wsgi.IsapiWsgiHandler = WsgiHandler
110 isapi_wsgi.IsapiWsgiHandler = WsgiHandler
112
111
113 # Only create the hgwebdir instance once
112 # Only create the hgwebdir instance once
114 application = hgwebdir(hgweb_config)
113 application = hgwebdir(hgweb_config)
115
114
116
115
117 def handler(environ, start_response):
116 def handler(environ, start_response):
118
117
119 # Translate IIS's weird URLs
118 # Translate IIS's weird URLs
120 url = environ['SCRIPT_NAME'] + environ['PATH_INFO']
119 url = environ['SCRIPT_NAME'] + environ['PATH_INFO']
121 paths = url[1:].split('/')[path_strip:]
120 paths = url[1:].split('/')[path_strip:]
122 script_name = '/' + '/'.join(paths[:path_prefix])
121 script_name = '/' + '/'.join(paths[:path_prefix])
123 path_info = '/'.join(paths[path_prefix:])
122 path_info = '/'.join(paths[path_prefix:])
124 if path_info:
123 if path_info:
125 path_info = '/' + path_info
124 path_info = '/' + path_info
126 environ['SCRIPT_NAME'] = script_name
125 environ['SCRIPT_NAME'] = script_name
127 environ['PATH_INFO'] = path_info
126 environ['PATH_INFO'] = path_info
128
127
129 return application(environ, start_response)
128 return application(environ, start_response)
130
129
131
130
132 def __ExtensionFactory__():
131 def __ExtensionFactory__():
133 return isapi_wsgi.ISAPISimpleHandler(handler)
132 return isapi_wsgi.ISAPISimpleHandler(handler)
134
133
135
134
136 if __name__ == '__main__':
135 if __name__ == '__main__':
137 from isapi.install import ISAPIParameters, HandleCommandLine
136 from isapi.install import ISAPIParameters, HandleCommandLine
138
137
139 params = ISAPIParameters()
138 params = ISAPIParameters()
140 HandleCommandLine(params)
139 HandleCommandLine(params)
@@ -1,233 +1,232 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 #
2 #
3 # checkseclevel - checking section title levels in each online help document
3 # checkseclevel - checking section title levels in each online help document
4
4
5 from __future__ import absolute_import
6
5
7 import optparse
6 import optparse
8 import os
7 import os
9 import sys
8 import sys
10
9
11 # import from the live mercurial repo
10 # import from the live mercurial repo
12 os.environ['HGMODULEPOLICY'] = 'py'
11 os.environ['HGMODULEPOLICY'] = 'py'
13 sys.path.insert(0, "..")
12 sys.path.insert(0, "..")
14 from mercurial import demandimport
13 from mercurial import demandimport
15
14
16 demandimport.enable()
15 demandimport.enable()
17 from mercurial import (
16 from mercurial import (
18 commands,
17 commands,
19 extensions,
18 extensions,
20 help,
19 help,
21 minirst,
20 minirst,
22 ui as uimod,
21 ui as uimod,
23 )
22 )
24
23
25 table = commands.table
24 table = commands.table
26 helptable = help.helptable
25 helptable = help.helptable
27
26
28 level2mark = [b'"', b'=', b'-', b'.', b'#']
27 level2mark = [b'"', b'=', b'-', b'.', b'#']
29 reservedmarks = [b'"']
28 reservedmarks = [b'"']
30
29
31 mark2level = {}
30 mark2level = {}
32 for m, l in zip(level2mark, range(len(level2mark))):
31 for m, l in zip(level2mark, range(len(level2mark))):
33 if m not in reservedmarks:
32 if m not in reservedmarks:
34 mark2level[m] = l
33 mark2level[m] = l
35
34
36 initlevel_topic = 0
35 initlevel_topic = 0
37 initlevel_cmd = 1
36 initlevel_cmd = 1
38 initlevel_ext = 1
37 initlevel_ext = 1
39 initlevel_ext_cmd = 3
38 initlevel_ext_cmd = 3
40
39
41
40
42 def showavailables(ui, initlevel):
41 def showavailables(ui, initlevel):
43 avail = ' available marks and order of them in this help: %s\n' % (
42 avail = ' available marks and order of them in this help: %s\n' % (
44 ', '.join(['%r' % (m * 4) for m in level2mark[initlevel + 1 :]])
43 ', '.join(['%r' % (m * 4) for m in level2mark[initlevel + 1 :]])
45 )
44 )
46 ui.warn(avail.encode('utf-8'))
45 ui.warn(avail.encode('utf-8'))
47
46
48
47
49 def checkseclevel(ui, doc, name, initlevel):
48 def checkseclevel(ui, doc, name, initlevel):
50 ui.notenoi18n('checking "%s"\n' % name)
49 ui.notenoi18n('checking "%s"\n' % name)
51 if not isinstance(doc, bytes):
50 if not isinstance(doc, bytes):
52 doc = doc.encode('utf-8')
51 doc = doc.encode('utf-8')
53 blocks, pruned = minirst.parse(doc, 0, ['verbose'])
52 blocks, pruned = minirst.parse(doc, 0, ['verbose'])
54 errorcnt = 0
53 errorcnt = 0
55 curlevel = initlevel
54 curlevel = initlevel
56 for block in blocks:
55 for block in blocks:
57 if block[b'type'] != b'section':
56 if block[b'type'] != b'section':
58 continue
57 continue
59 mark = block[b'underline']
58 mark = block[b'underline']
60 title = block[b'lines'][0]
59 title = block[b'lines'][0]
61 if (mark not in mark2level) or (mark2level[mark] <= initlevel):
60 if (mark not in mark2level) or (mark2level[mark] <= initlevel):
62 ui.warn(
61 ui.warn(
63 (
62 (
64 'invalid section mark %r for "%s" of %s\n'
63 'invalid section mark %r for "%s" of %s\n'
65 % (mark * 4, title, name)
64 % (mark * 4, title, name)
66 ).encode('utf-8')
65 ).encode('utf-8')
67 )
66 )
68 showavailables(ui, initlevel)
67 showavailables(ui, initlevel)
69 errorcnt += 1
68 errorcnt += 1
70 continue
69 continue
71 nextlevel = mark2level[mark]
70 nextlevel = mark2level[mark]
72 if curlevel < nextlevel and curlevel + 1 != nextlevel:
71 if curlevel < nextlevel and curlevel + 1 != nextlevel:
73 ui.warnnoi18n(
72 ui.warnnoi18n(
74 'gap of section level at "%s" of %s\n' % (title, name)
73 'gap of section level at "%s" of %s\n' % (title, name)
75 )
74 )
76 showavailables(ui, initlevel)
75 showavailables(ui, initlevel)
77 errorcnt += 1
76 errorcnt += 1
78 continue
77 continue
79 ui.notenoi18n(
78 ui.notenoi18n(
80 'appropriate section level for "%s %s"\n'
79 'appropriate section level for "%s %s"\n'
81 % (mark * (nextlevel * 2), title)
80 % (mark * (nextlevel * 2), title)
82 )
81 )
83 curlevel = nextlevel
82 curlevel = nextlevel
84
83
85 return errorcnt
84 return errorcnt
86
85
87
86
88 def checkcmdtable(ui, cmdtable, namefmt, initlevel):
87 def checkcmdtable(ui, cmdtable, namefmt, initlevel):
89 errorcnt = 0
88 errorcnt = 0
90 for k, entry in cmdtable.items():
89 for k, entry in cmdtable.items():
91 name = k.split(b"|")[0].lstrip(b"^")
90 name = k.split(b"|")[0].lstrip(b"^")
92 if not entry[0].__doc__:
91 if not entry[0].__doc__:
93 ui.notenoi18n(
92 ui.notenoi18n(
94 'skip checking %s: no help document\n' % (namefmt % name)
93 'skip checking %s: no help document\n' % (namefmt % name)
95 )
94 )
96 continue
95 continue
97 errorcnt += checkseclevel(
96 errorcnt += checkseclevel(
98 ui, entry[0].__doc__, namefmt % name, initlevel
97 ui, entry[0].__doc__, namefmt % name, initlevel
99 )
98 )
100 return errorcnt
99 return errorcnt
101
100
102
101
103 def checkhghelps(ui):
102 def checkhghelps(ui):
104 errorcnt = 0
103 errorcnt = 0
105 for h in helptable:
104 for h in helptable:
106 names, sec, doc = h[0:3]
105 names, sec, doc = h[0:3]
107 if callable(doc):
106 if callable(doc):
108 doc = doc(ui)
107 doc = doc(ui)
109 errorcnt += checkseclevel(
108 errorcnt += checkseclevel(
110 ui, doc, '%s help topic' % names[0], initlevel_topic
109 ui, doc, '%s help topic' % names[0], initlevel_topic
111 )
110 )
112
111
113 errorcnt += checkcmdtable(ui, table, '%s command', initlevel_cmd)
112 errorcnt += checkcmdtable(ui, table, '%s command', initlevel_cmd)
114
113
115 for name in sorted(
114 for name in sorted(
116 list(extensions.enabled()) + list(extensions.disabled())
115 list(extensions.enabled()) + list(extensions.disabled())
117 ):
116 ):
118 mod = extensions.load(ui, name, None)
117 mod = extensions.load(ui, name, None)
119 if not mod.__doc__:
118 if not mod.__doc__:
120 ui.notenoi18n(
119 ui.notenoi18n(
121 'skip checking %s extension: no help document\n' % name
120 'skip checking %s extension: no help document\n' % name
122 )
121 )
123 continue
122 continue
124 errorcnt += checkseclevel(
123 errorcnt += checkseclevel(
125 ui, mod.__doc__, '%s extension' % name, initlevel_ext
124 ui, mod.__doc__, '%s extension' % name, initlevel_ext
126 )
125 )
127
126
128 cmdtable = getattr(mod, 'cmdtable', None)
127 cmdtable = getattr(mod, 'cmdtable', None)
129 if cmdtable:
128 if cmdtable:
130 errorcnt += checkcmdtable(
129 errorcnt += checkcmdtable(
131 ui,
130 ui,
132 cmdtable,
131 cmdtable,
133 '%%s command of %s extension' % name,
132 '%%s command of %s extension' % name,
134 initlevel_ext_cmd,
133 initlevel_ext_cmd,
135 )
134 )
136 return errorcnt
135 return errorcnt
137
136
138
137
139 def checkfile(ui, filename, initlevel):
138 def checkfile(ui, filename, initlevel):
140 if filename == '-':
139 if filename == '-':
141 filename = 'stdin'
140 filename = 'stdin'
142 doc = sys.stdin.read()
141 doc = sys.stdin.read()
143 else:
142 else:
144 with open(filename) as fp:
143 with open(filename) as fp:
145 doc = fp.read()
144 doc = fp.read()
146
145
147 ui.notenoi18n(
146 ui.notenoi18n(
148 'checking input from %s with initlevel %d\n' % (filename, initlevel)
147 'checking input from %s with initlevel %d\n' % (filename, initlevel)
149 )
148 )
150 return checkseclevel(ui, doc, 'input from %s' % filename, initlevel)
149 return checkseclevel(ui, doc, 'input from %s' % filename, initlevel)
151
150
152
151
153 def main():
152 def main():
154 optparser = optparse.OptionParser(
153 optparser = optparse.OptionParser(
155 """%prog [options]
154 """%prog [options]
156
155
157 This checks all help documents of Mercurial (topics, commands,
156 This checks all help documents of Mercurial (topics, commands,
158 extensions and commands of them), if no file is specified by --file
157 extensions and commands of them), if no file is specified by --file
159 option.
158 option.
160 """
159 """
161 )
160 )
162 optparser.add_option(
161 optparser.add_option(
163 "-v", "--verbose", help="enable additional output", action="store_true"
162 "-v", "--verbose", help="enable additional output", action="store_true"
164 )
163 )
165 optparser.add_option(
164 optparser.add_option(
166 "-d", "--debug", help="debug mode", action="store_true"
165 "-d", "--debug", help="debug mode", action="store_true"
167 )
166 )
168 optparser.add_option(
167 optparser.add_option(
169 "-f",
168 "-f",
170 "--file",
169 "--file",
171 help="filename to read in (or '-' for stdin)",
170 help="filename to read in (or '-' for stdin)",
172 action="store",
171 action="store",
173 default="",
172 default="",
174 )
173 )
175
174
176 optparser.add_option(
175 optparser.add_option(
177 "-t",
176 "-t",
178 "--topic",
177 "--topic",
179 help="parse file as help topic",
178 help="parse file as help topic",
180 action="store_const",
179 action="store_const",
181 dest="initlevel",
180 dest="initlevel",
182 const=0,
181 const=0,
183 )
182 )
184 optparser.add_option(
183 optparser.add_option(
185 "-c",
184 "-c",
186 "--command",
185 "--command",
187 help="parse file as help of core command",
186 help="parse file as help of core command",
188 action="store_const",
187 action="store_const",
189 dest="initlevel",
188 dest="initlevel",
190 const=1,
189 const=1,
191 )
190 )
192 optparser.add_option(
191 optparser.add_option(
193 "-e",
192 "-e",
194 "--extension",
193 "--extension",
195 help="parse file as help of extension",
194 help="parse file as help of extension",
196 action="store_const",
195 action="store_const",
197 dest="initlevel",
196 dest="initlevel",
198 const=1,
197 const=1,
199 )
198 )
200 optparser.add_option(
199 optparser.add_option(
201 "-C",
200 "-C",
202 "--extension-command",
201 "--extension-command",
203 help="parse file as help of extension command",
202 help="parse file as help of extension command",
204 action="store_const",
203 action="store_const",
205 dest="initlevel",
204 dest="initlevel",
206 const=3,
205 const=3,
207 )
206 )
208
207
209 optparser.add_option(
208 optparser.add_option(
210 "-l",
209 "-l",
211 "--initlevel",
210 "--initlevel",
212 help="set initial section level manually",
211 help="set initial section level manually",
213 action="store",
212 action="store",
214 type="int",
213 type="int",
215 default=0,
214 default=0,
216 )
215 )
217
216
218 (options, args) = optparser.parse_args()
217 (options, args) = optparser.parse_args()
219
218
220 ui = uimod.ui.load()
219 ui = uimod.ui.load()
221 ui.setconfig(b'ui', b'verbose', options.verbose, b'--verbose')
220 ui.setconfig(b'ui', b'verbose', options.verbose, b'--verbose')
222 ui.setconfig(b'ui', b'debug', options.debug, b'--debug')
221 ui.setconfig(b'ui', b'debug', options.debug, b'--debug')
223
222
224 if options.file:
223 if options.file:
225 if checkfile(ui, options.file, options.initlevel):
224 if checkfile(ui, options.file, options.initlevel):
226 sys.exit(1)
225 sys.exit(1)
227 else:
226 else:
228 if checkhghelps(ui):
227 if checkhghelps(ui):
229 sys.exit(1)
228 sys.exit(1)
230
229
231
230
232 if __name__ == "__main__":
231 if __name__ == "__main__":
233 main()
232 main()
@@ -1,84 +1,83 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 #
2 #
3 # docchecker - look for problematic markup
3 # docchecker - look for problematic markup
4 #
4 #
5 # Copyright 2016 timeless <timeless@mozdev.org> and others
5 # Copyright 2016 timeless <timeless@mozdev.org> and others
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 from __future__ import absolute_import, print_function
11
10
12 import os
11 import os
13 import re
12 import re
14 import sys
13 import sys
15
14
16 try:
15 try:
17 import msvcrt
16 import msvcrt
18
17
19 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
18 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
20 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
19 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
21 except ImportError:
20 except ImportError:
22 pass
21 pass
23
22
24 stdout = getattr(sys.stdout, 'buffer', sys.stdout)
23 stdout = getattr(sys.stdout, 'buffer', sys.stdout)
25
24
26 leadingline = re.compile(br'(^\s*)(\S.*)$')
25 leadingline = re.compile(br'(^\s*)(\S.*)$')
27
26
28 checks = [
27 checks = [
29 (
28 (
30 br""":hg:`[^`]*'[^`]*`""",
29 br""":hg:`[^`]*'[^`]*`""",
31 b"""warning: please avoid nesting ' in :hg:`...`""",
30 b"""warning: please avoid nesting ' in :hg:`...`""",
32 ),
31 ),
33 (br'\w:hg:`', b'warning: please have a space before :hg:'),
32 (br'\w:hg:`', b'warning: please have a space before :hg:'),
34 (
33 (
35 br"""(?:[^a-z][^'.])hg ([^,;"`]*'(?!hg)){2}""",
34 br"""(?:[^a-z][^'.])hg ([^,;"`]*'(?!hg)){2}""",
36 b'''warning: please use " instead of ' for hg ... "..."''',
35 b'''warning: please use " instead of ' for hg ... "..."''',
37 ),
36 ),
38 ]
37 ]
39
38
40
39
41 def check(line):
40 def check(line):
42 messages = []
41 messages = []
43 for match, msg in checks:
42 for match, msg in checks:
44 if re.search(match, line):
43 if re.search(match, line):
45 messages.append(msg)
44 messages.append(msg)
46 if messages:
45 if messages:
47 stdout.write(b'%s\n' % line)
46 stdout.write(b'%s\n' % line)
48 for msg in messages:
47 for msg in messages:
49 stdout.write(b'%s\n' % msg)
48 stdout.write(b'%s\n' % msg)
50
49
51
50
52 def work(file):
51 def work(file):
53 (llead, lline) = (b'', b'')
52 (llead, lline) = (b'', b'')
54
53
55 for line in file:
54 for line in file:
56 # this section unwraps lines
55 # this section unwraps lines
57 match = leadingline.match(line)
56 match = leadingline.match(line)
58 if not match:
57 if not match:
59 check(lline)
58 check(lline)
60 (llead, lline) = (b'', b'')
59 (llead, lline) = (b'', b'')
61 continue
60 continue
62
61
63 lead, line = match.group(1), match.group(2)
62 lead, line = match.group(1), match.group(2)
64 if lead == llead:
63 if lead == llead:
65 if lline != b'':
64 if lline != b'':
66 lline += b' ' + line
65 lline += b' ' + line
67 else:
66 else:
68 lline = line
67 lline = line
69 else:
68 else:
70 check(lline)
69 check(lline)
71 (llead, lline) = (lead, line)
70 (llead, lline) = (lead, line)
72 check(lline)
71 check(lline)
73
72
74
73
75 def main():
74 def main():
76 for f in sys.argv[1:]:
75 for f in sys.argv[1:]:
77 try:
76 try:
78 with open(f, 'rb') as file:
77 with open(f, 'rb') as file:
79 work(file)
78 work(file)
80 except BaseException as e:
79 except BaseException as e:
81 sys.stdout.write(r"failed to process %s: %s\n" % (f, e))
80 sys.stdout.write(r"failed to process %s: %s\n" % (f, e))
82
81
83
82
84 main()
83 main()
@@ -1,346 +1,345 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 """usage: %s DOC ...
2 """usage: %s DOC ...
3
3
4 where DOC is the name of a document
4 where DOC is the name of a document
5 """
5 """
6
6
7 from __future__ import absolute_import
8
7
9 import os
8 import os
10 import sys
9 import sys
11 import textwrap
10 import textwrap
12
11
13 try:
12 try:
14 import msvcrt
13 import msvcrt
15
14
16 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
15 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
17 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
16 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
18 except ImportError:
17 except ImportError:
19 pass
18 pass
20
19
21 # This script is executed during installs and may not have C extensions
20 # This script is executed during installs and may not have C extensions
22 # available. Relax C module requirements.
21 # available. Relax C module requirements.
23 os.environ['HGMODULEPOLICY'] = 'allow'
22 os.environ['HGMODULEPOLICY'] = 'allow'
24 # import from the live mercurial repo
23 # import from the live mercurial repo
25 sys.path.insert(0, "..")
24 sys.path.insert(0, "..")
26 from mercurial import demandimport
25 from mercurial import demandimport
27
26
28 demandimport.enable()
27 demandimport.enable()
29
28
30 from mercurial import (
29 from mercurial import (
31 commands,
30 commands,
32 encoding,
31 encoding,
33 extensions,
32 extensions,
34 fancyopts,
33 fancyopts,
35 help,
34 help,
36 minirst,
35 minirst,
37 pycompat,
36 pycompat,
38 ui as uimod,
37 ui as uimod,
39 )
38 )
40 from mercurial.i18n import (
39 from mercurial.i18n import (
41 gettext,
40 gettext,
42 _,
41 _,
43 )
42 )
44 from mercurial.utils import stringutil
43 from mercurial.utils import stringutil
45
44
46 table = commands.table
45 table = commands.table
47 globalopts = commands.globalopts
46 globalopts = commands.globalopts
48 helptable = help.helptable
47 helptable = help.helptable
49 loaddoc = help.loaddoc
48 loaddoc = help.loaddoc
50
49
51
50
52 def get_desc(docstr):
51 def get_desc(docstr):
53 if not docstr:
52 if not docstr:
54 return b"", b""
53 return b"", b""
55 # sanitize
54 # sanitize
56 docstr = docstr.strip(b"\n")
55 docstr = docstr.strip(b"\n")
57 docstr = docstr.rstrip()
56 docstr = docstr.rstrip()
58 shortdesc = docstr.splitlines()[0].strip()
57 shortdesc = docstr.splitlines()[0].strip()
59
58
60 i = docstr.find(b"\n")
59 i = docstr.find(b"\n")
61 if i != -1:
60 if i != -1:
62 desc = docstr[i + 2 :]
61 desc = docstr[i + 2 :]
63 else:
62 else:
64 desc = shortdesc
63 desc = shortdesc
65
64
66 desc = textwrap.dedent(desc.decode('latin1')).encode('latin1')
65 desc = textwrap.dedent(desc.decode('latin1')).encode('latin1')
67
66
68 return (shortdesc, desc)
67 return (shortdesc, desc)
69
68
70
69
71 def get_opts(opts):
70 def get_opts(opts):
72 for opt in opts:
71 for opt in opts:
73 if len(opt) == 5:
72 if len(opt) == 5:
74 shortopt, longopt, default, desc, optlabel = opt
73 shortopt, longopt, default, desc, optlabel = opt
75 else:
74 else:
76 shortopt, longopt, default, desc = opt
75 shortopt, longopt, default, desc = opt
77 optlabel = _(b"VALUE")
76 optlabel = _(b"VALUE")
78 allopts = []
77 allopts = []
79 if shortopt:
78 if shortopt:
80 allopts.append(b"-%s" % shortopt)
79 allopts.append(b"-%s" % shortopt)
81 if longopt:
80 if longopt:
82 allopts.append(b"--%s" % longopt)
81 allopts.append(b"--%s" % longopt)
83 if isinstance(default, list):
82 if isinstance(default, list):
84 allopts[-1] += b" <%s[+]>" % optlabel
83 allopts[-1] += b" <%s[+]>" % optlabel
85 elif (default is not None) and not isinstance(default, bool):
84 elif (default is not None) and not isinstance(default, bool):
86 allopts[-1] += b" <%s>" % optlabel
85 allopts[-1] += b" <%s>" % optlabel
87 if b'\n' in desc:
86 if b'\n' in desc:
88 # only remove line breaks and indentation
87 # only remove line breaks and indentation
89 desc = b' '.join(l.lstrip() for l in desc.split(b'\n'))
88 desc = b' '.join(l.lstrip() for l in desc.split(b'\n'))
90 if isinstance(default, fancyopts.customopt):
89 if isinstance(default, fancyopts.customopt):
91 default = default.getdefaultvalue()
90 default = default.getdefaultvalue()
92 if default:
91 if default:
93 default = stringutil.forcebytestr(default)
92 default = stringutil.forcebytestr(default)
94 desc += _(b" (default: %s)") % default
93 desc += _(b" (default: %s)") % default
95 yield (b", ".join(allopts), desc)
94 yield (b", ".join(allopts), desc)
96
95
97
96
98 def get_cmd(cmd, cmdtable):
97 def get_cmd(cmd, cmdtable):
99 d = {}
98 d = {}
100 attr = cmdtable[cmd]
99 attr = cmdtable[cmd]
101 cmds = cmd.lstrip(b"^").split(b"|")
100 cmds = cmd.lstrip(b"^").split(b"|")
102
101
103 d[b'cmd'] = cmds[0]
102 d[b'cmd'] = cmds[0]
104 d[b'aliases'] = cmd.split(b"|")[1:]
103 d[b'aliases'] = cmd.split(b"|")[1:]
105 d[b'desc'] = get_desc(gettext(pycompat.getdoc(attr[0])))
104 d[b'desc'] = get_desc(gettext(pycompat.getdoc(attr[0])))
106 d[b'opts'] = list(get_opts(attr[1]))
105 d[b'opts'] = list(get_opts(attr[1]))
107
106
108 s = b'hg ' + cmds[0]
107 s = b'hg ' + cmds[0]
109 if len(attr) > 2:
108 if len(attr) > 2:
110 if not attr[2].startswith(b'hg'):
109 if not attr[2].startswith(b'hg'):
111 s += b' ' + attr[2]
110 s += b' ' + attr[2]
112 else:
111 else:
113 s = attr[2]
112 s = attr[2]
114 d[b'synopsis'] = s.strip()
113 d[b'synopsis'] = s.strip()
115
114
116 return d
115 return d
117
116
118
117
119 def showdoc(ui):
118 def showdoc(ui):
120 # print options
119 # print options
121 ui.write(minirst.section(_(b"Options")))
120 ui.write(minirst.section(_(b"Options")))
122 multioccur = False
121 multioccur = False
123 for optstr, desc in get_opts(globalopts):
122 for optstr, desc in get_opts(globalopts):
124 ui.write(b"%s\n %s\n\n" % (optstr, desc))
123 ui.write(b"%s\n %s\n\n" % (optstr, desc))
125 if optstr.endswith(b"[+]>"):
124 if optstr.endswith(b"[+]>"):
126 multioccur = True
125 multioccur = True
127 if multioccur:
126 if multioccur:
128 ui.write(_(b"\n[+] marked option can be specified multiple times\n"))
127 ui.write(_(b"\n[+] marked option can be specified multiple times\n"))
129 ui.write(b"\n")
128 ui.write(b"\n")
130
129
131 # print cmds
130 # print cmds
132 ui.write(minirst.section(_(b"Commands")))
131 ui.write(minirst.section(_(b"Commands")))
133 commandprinter(ui, table, minirst.subsection, minirst.subsubsection)
132 commandprinter(ui, table, minirst.subsection, minirst.subsubsection)
134
133
135 # print help topics
134 # print help topics
136 # The config help topic is included in the hgrc.5 man page.
135 # The config help topic is included in the hgrc.5 man page.
137 helpprinter(ui, helptable, minirst.section, exclude=[b'config'])
136 helpprinter(ui, helptable, minirst.section, exclude=[b'config'])
138
137
139 ui.write(minirst.section(_(b"Extensions")))
138 ui.write(minirst.section(_(b"Extensions")))
140 ui.write(
139 ui.write(
141 _(
140 _(
142 b"This section contains help for extensions that are "
141 b"This section contains help for extensions that are "
143 b"distributed together with Mercurial. Help for other "
142 b"distributed together with Mercurial. Help for other "
144 b"extensions is available in the help system."
143 b"extensions is available in the help system."
145 )
144 )
146 )
145 )
147 ui.write(
146 ui.write(
148 (
147 (
149 b"\n\n"
148 b"\n\n"
150 b".. contents::\n"
149 b".. contents::\n"
151 b" :class: htmlonly\n"
150 b" :class: htmlonly\n"
152 b" :local:\n"
151 b" :local:\n"
153 b" :depth: 1\n\n"
152 b" :depth: 1\n\n"
154 )
153 )
155 )
154 )
156
155
157 for extensionname in sorted(allextensionnames()):
156 for extensionname in sorted(allextensionnames()):
158 mod = extensions.load(ui, extensionname, None)
157 mod = extensions.load(ui, extensionname, None)
159 ui.write(minirst.subsection(extensionname))
158 ui.write(minirst.subsection(extensionname))
160 ui.write(b"%s\n\n" % gettext(pycompat.getdoc(mod)))
159 ui.write(b"%s\n\n" % gettext(pycompat.getdoc(mod)))
161 cmdtable = getattr(mod, 'cmdtable', None)
160 cmdtable = getattr(mod, 'cmdtable', None)
162 if cmdtable:
161 if cmdtable:
163 ui.write(minirst.subsubsection(_(b'Commands')))
162 ui.write(minirst.subsubsection(_(b'Commands')))
164 commandprinter(
163 commandprinter(
165 ui,
164 ui,
166 cmdtable,
165 cmdtable,
167 minirst.subsubsubsection,
166 minirst.subsubsubsection,
168 minirst.subsubsubsubsection,
167 minirst.subsubsubsubsection,
169 )
168 )
170
169
171
170
172 def showtopic(ui, topic):
171 def showtopic(ui, topic):
173 extrahelptable = [
172 extrahelptable = [
174 ([b"common"], b'', loaddoc(b'common'), help.TOPIC_CATEGORY_MISC),
173 ([b"common"], b'', loaddoc(b'common'), help.TOPIC_CATEGORY_MISC),
175 ([b"hg.1"], b'', loaddoc(b'hg.1'), help.TOPIC_CATEGORY_CONFIG),
174 ([b"hg.1"], b'', loaddoc(b'hg.1'), help.TOPIC_CATEGORY_CONFIG),
176 ([b"hg-ssh.8"], b'', loaddoc(b'hg-ssh.8'), help.TOPIC_CATEGORY_CONFIG),
175 ([b"hg-ssh.8"], b'', loaddoc(b'hg-ssh.8'), help.TOPIC_CATEGORY_CONFIG),
177 (
176 (
178 [b"hgignore.5"],
177 [b"hgignore.5"],
179 b'',
178 b'',
180 loaddoc(b'hgignore.5'),
179 loaddoc(b'hgignore.5'),
181 help.TOPIC_CATEGORY_CONFIG,
180 help.TOPIC_CATEGORY_CONFIG,
182 ),
181 ),
183 ([b"hgrc.5"], b'', loaddoc(b'hgrc.5'), help.TOPIC_CATEGORY_CONFIG),
182 ([b"hgrc.5"], b'', loaddoc(b'hgrc.5'), help.TOPIC_CATEGORY_CONFIG),
184 (
183 (
185 [b"hgignore.5.gendoc"],
184 [b"hgignore.5.gendoc"],
186 b'',
185 b'',
187 loaddoc(b'hgignore'),
186 loaddoc(b'hgignore'),
188 help.TOPIC_CATEGORY_CONFIG,
187 help.TOPIC_CATEGORY_CONFIG,
189 ),
188 ),
190 (
189 (
191 [b"hgrc.5.gendoc"],
190 [b"hgrc.5.gendoc"],
192 b'',
191 b'',
193 loaddoc(b'config'),
192 loaddoc(b'config'),
194 help.TOPIC_CATEGORY_CONFIG,
193 help.TOPIC_CATEGORY_CONFIG,
195 ),
194 ),
196 ]
195 ]
197 helpprinter(ui, helptable + extrahelptable, None, include=[topic])
196 helpprinter(ui, helptable + extrahelptable, None, include=[topic])
198
197
199
198
200 def helpprinter(ui, helptable, sectionfunc, include=[], exclude=[]):
199 def helpprinter(ui, helptable, sectionfunc, include=[], exclude=[]):
201 for h in helptable:
200 for h in helptable:
202 names, sec, doc = h[0:3]
201 names, sec, doc = h[0:3]
203 if exclude and names[0] in exclude:
202 if exclude and names[0] in exclude:
204 continue
203 continue
205 if include and names[0] not in include:
204 if include and names[0] not in include:
206 continue
205 continue
207 for name in names:
206 for name in names:
208 ui.write(b".. _%s:\n" % name)
207 ui.write(b".. _%s:\n" % name)
209 ui.write(b"\n")
208 ui.write(b"\n")
210 if sectionfunc:
209 if sectionfunc:
211 ui.write(sectionfunc(sec))
210 ui.write(sectionfunc(sec))
212 if callable(doc):
211 if callable(doc):
213 doc = doc(ui)
212 doc = doc(ui)
214 ui.write(doc)
213 ui.write(doc)
215 ui.write(b"\n")
214 ui.write(b"\n")
216
215
217
216
218 def commandprinter(ui, cmdtable, sectionfunc, subsectionfunc):
217 def commandprinter(ui, cmdtable, sectionfunc, subsectionfunc):
219 """Render restructuredtext describing a list of commands and their
218 """Render restructuredtext describing a list of commands and their
220 documentations, grouped by command category.
219 documentations, grouped by command category.
221
220
222 Args:
221 Args:
223 ui: UI object to write the output to
222 ui: UI object to write the output to
224 cmdtable: a dict that maps a string of the command name plus its aliases
223 cmdtable: a dict that maps a string of the command name plus its aliases
225 (separated with pipes) to a 3-tuple of (the command's function, a list
224 (separated with pipes) to a 3-tuple of (the command's function, a list
226 of its option descriptions, and a string summarizing available
225 of its option descriptions, and a string summarizing available
227 options). Example, with aliases added for demonstration purposes:
226 options). Example, with aliases added for demonstration purposes:
228
227
229 'phase|alias1|alias2': (
228 'phase|alias1|alias2': (
230 <function phase at 0x7f0816b05e60>,
229 <function phase at 0x7f0816b05e60>,
231 [ ('p', 'public', False, 'set changeset phase to public'),
230 [ ('p', 'public', False, 'set changeset phase to public'),
232 ...,
231 ...,
233 ('r', 'rev', [], 'target revision', 'REV')],
232 ('r', 'rev', [], 'target revision', 'REV')],
234 '[-p|-d|-s] [-f] [-r] [REV...]'
233 '[-p|-d|-s] [-f] [-r] [REV...]'
235 )
234 )
236 sectionfunc: minirst function to format command category headers
235 sectionfunc: minirst function to format command category headers
237 subsectionfunc: minirst function to format command headers
236 subsectionfunc: minirst function to format command headers
238 """
237 """
239 h = {}
238 h = {}
240 for c, attr in cmdtable.items():
239 for c, attr in cmdtable.items():
241 f = c.split(b"|")[0]
240 f = c.split(b"|")[0]
242 f = f.lstrip(b"^")
241 f = f.lstrip(b"^")
243 h[f] = c
242 h[f] = c
244 cmds = h.keys()
243 cmds = h.keys()
245
244
246 def helpcategory(cmd):
245 def helpcategory(cmd):
247 """Given a canonical command name from `cmds` (above), retrieve its
246 """Given a canonical command name from `cmds` (above), retrieve its
248 help category. If helpcategory is None, default to CATEGORY_NONE.
247 help category. If helpcategory is None, default to CATEGORY_NONE.
249 """
248 """
250 fullname = h[cmd]
249 fullname = h[cmd]
251 details = cmdtable[fullname]
250 details = cmdtable[fullname]
252 helpcategory = details[0].helpcategory
251 helpcategory = details[0].helpcategory
253 return helpcategory or help.registrar.command.CATEGORY_NONE
252 return helpcategory or help.registrar.command.CATEGORY_NONE
254
253
255 cmdsbycategory = {category: [] for category in help.CATEGORY_ORDER}
254 cmdsbycategory = {category: [] for category in help.CATEGORY_ORDER}
256 for cmd in cmds:
255 for cmd in cmds:
257 # If a command category wasn't registered, the command won't get
256 # If a command category wasn't registered, the command won't get
258 # rendered below, so we raise an AssertionError.
257 # rendered below, so we raise an AssertionError.
259 if helpcategory(cmd) not in cmdsbycategory:
258 if helpcategory(cmd) not in cmdsbycategory:
260 raise AssertionError(
259 raise AssertionError(
261 "The following command did not register its (category) in "
260 "The following command did not register its (category) in "
262 "help.CATEGORY_ORDER: %s (%s)" % (cmd, helpcategory(cmd))
261 "help.CATEGORY_ORDER: %s (%s)" % (cmd, helpcategory(cmd))
263 )
262 )
264 cmdsbycategory[helpcategory(cmd)].append(cmd)
263 cmdsbycategory[helpcategory(cmd)].append(cmd)
265
264
266 # Print the help for each command. We present the commands grouped by
265 # Print the help for each command. We present the commands grouped by
267 # category, and we use help.CATEGORY_ORDER as a guide for a helpful order
266 # category, and we use help.CATEGORY_ORDER as a guide for a helpful order
268 # in which to present the categories.
267 # in which to present the categories.
269 for category in help.CATEGORY_ORDER:
268 for category in help.CATEGORY_ORDER:
270 categorycmds = cmdsbycategory[category]
269 categorycmds = cmdsbycategory[category]
271 if not categorycmds:
270 if not categorycmds:
272 # Skip empty categories
271 # Skip empty categories
273 continue
272 continue
274 # Print a section header for the category.
273 # Print a section header for the category.
275 # For now, the category header is at the same level as the headers for
274 # For now, the category header is at the same level as the headers for
276 # the commands in the category; this is fixed in the next commit.
275 # the commands in the category; this is fixed in the next commit.
277 ui.write(sectionfunc(help.CATEGORY_NAMES[category]))
276 ui.write(sectionfunc(help.CATEGORY_NAMES[category]))
278 # Print each command in the category
277 # Print each command in the category
279 for f in sorted(categorycmds):
278 for f in sorted(categorycmds):
280 if f.startswith(b"debug"):
279 if f.startswith(b"debug"):
281 continue
280 continue
282 d = get_cmd(h[f], cmdtable)
281 d = get_cmd(h[f], cmdtable)
283 ui.write(subsectionfunc(d[b'cmd']))
282 ui.write(subsectionfunc(d[b'cmd']))
284 # short description
283 # short description
285 ui.write(d[b'desc'][0])
284 ui.write(d[b'desc'][0])
286 # synopsis
285 # synopsis
287 ui.write(b"::\n\n")
286 ui.write(b"::\n\n")
288 synopsislines = d[b'synopsis'].splitlines()
287 synopsislines = d[b'synopsis'].splitlines()
289 for line in synopsislines:
288 for line in synopsislines:
290 # some commands (such as rebase) have a multi-line
289 # some commands (such as rebase) have a multi-line
291 # synopsis
290 # synopsis
292 ui.write(b" %s\n" % line)
291 ui.write(b" %s\n" % line)
293 ui.write(b'\n')
292 ui.write(b'\n')
294 # description
293 # description
295 ui.write(b"%s\n\n" % d[b'desc'][1])
294 ui.write(b"%s\n\n" % d[b'desc'][1])
296 # options
295 # options
297 opt_output = list(d[b'opts'])
296 opt_output = list(d[b'opts'])
298 if opt_output:
297 if opt_output:
299 opts_len = max([len(line[0]) for line in opt_output])
298 opts_len = max([len(line[0]) for line in opt_output])
300 ui.write(_(b"Options:\n\n"))
299 ui.write(_(b"Options:\n\n"))
301 multioccur = False
300 multioccur = False
302 for optstr, desc in opt_output:
301 for optstr, desc in opt_output:
303 if desc:
302 if desc:
304 s = b"%-*s %s" % (opts_len, optstr, desc)
303 s = b"%-*s %s" % (opts_len, optstr, desc)
305 else:
304 else:
306 s = optstr
305 s = optstr
307 ui.write(b"%s\n" % s)
306 ui.write(b"%s\n" % s)
308 if optstr.endswith(b"[+]>"):
307 if optstr.endswith(b"[+]>"):
309 multioccur = True
308 multioccur = True
310 if multioccur:
309 if multioccur:
311 ui.write(
310 ui.write(
312 _(
311 _(
313 b"\n[+] marked option can be specified"
312 b"\n[+] marked option can be specified"
314 b" multiple times\n"
313 b" multiple times\n"
315 )
314 )
316 )
315 )
317 ui.write(b"\n")
316 ui.write(b"\n")
318 # aliases
317 # aliases
319 if d[b'aliases']:
318 if d[b'aliases']:
320 # Note the empty comment, this is required to separate this
319 # Note the empty comment, this is required to separate this
321 # (which should be a blockquote) from any preceding things (such
320 # (which should be a blockquote) from any preceding things (such
322 # as a definition list).
321 # as a definition list).
323 ui.write(
322 ui.write(
324 _(b"..\n\n aliases: %s\n\n") % b" ".join(d[b'aliases'])
323 _(b"..\n\n aliases: %s\n\n") % b" ".join(d[b'aliases'])
325 )
324 )
326
325
327
326
328 def allextensionnames():
327 def allextensionnames():
329 return set(extensions.enabled().keys()) | set(extensions.disabled().keys())
328 return set(extensions.enabled().keys()) | set(extensions.disabled().keys())
330
329
331
330
332 if __name__ == "__main__":
331 if __name__ == "__main__":
333 doc = b'hg.1.gendoc'
332 doc = b'hg.1.gendoc'
334 if len(sys.argv) > 1:
333 if len(sys.argv) > 1:
335 doc = encoding.strtolocal(sys.argv[1])
334 doc = encoding.strtolocal(sys.argv[1])
336
335
337 ui = uimod.ui.load()
336 ui = uimod.ui.load()
338 # Trigger extensions to load. This is disabled by default because it uses
337 # Trigger extensions to load. This is disabled by default because it uses
339 # the current user's configuration, which is often not what is wanted.
338 # the current user's configuration, which is often not what is wanted.
340 if encoding.environ.get(b'GENDOC_LOAD_CONFIGURED_EXTENSIONS', b'0') != b'0':
339 if encoding.environ.get(b'GENDOC_LOAD_CONFIGURED_EXTENSIONS', b'0') != b'0':
341 extensions.loadall(ui)
340 extensions.loadall(ui)
342
341
343 if doc == b'hg.1.gendoc':
342 if doc == b'hg.1.gendoc':
344 showdoc(ui)
343 showdoc(ui)
345 else:
344 else:
346 showtopic(ui, encoding.strtolocal(sys.argv[1]))
345 showtopic(ui, encoding.strtolocal(sys.argv[1]))
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now