##// END OF EJS Templates
bdiff: proxy through mdiff module...
Yuya Nishihara -
r32201:ded48ad5 default
parent child Browse files
Show More
@@ -1,100 +1,100
1 # Randomized torture test generation for bdiff
1 # Randomized torture test generation for bdiff
2
2
3 from __future__ import absolute_import, print_function
3 from __future__ import absolute_import, print_function
4 import random
4 import random
5 import sys
5 import sys
6
6
7 from mercurial import (
7 from mercurial import (
8 bdiff,
8 mdiff,
9 mpatch,
9 mpatch,
10 )
10 )
11
11
12 def reducetest(a, b):
12 def reducetest(a, b):
13 tries = 0
13 tries = 0
14 reductions = 0
14 reductions = 0
15 print("reducing...")
15 print("reducing...")
16 while tries < 1000:
16 while tries < 1000:
17 a2 = "\n".join(l for l in a.splitlines()
17 a2 = "\n".join(l for l in a.splitlines()
18 if random.randint(0, 100) > 0) + "\n"
18 if random.randint(0, 100) > 0) + "\n"
19 b2 = "\n".join(l for l in b.splitlines()
19 b2 = "\n".join(l for l in b.splitlines()
20 if random.randint(0, 100) > 0) + "\n"
20 if random.randint(0, 100) > 0) + "\n"
21 if a2 == a and b2 == b:
21 if a2 == a and b2 == b:
22 continue
22 continue
23 if a2 == b2:
23 if a2 == b2:
24 continue
24 continue
25 tries += 1
25 tries += 1
26
26
27 try:
27 try:
28 test1(a, b)
28 test1(a, b)
29 except Exception as inst:
29 except Exception as inst:
30 reductions += 1
30 reductions += 1
31 tries = 0
31 tries = 0
32 a = a2
32 a = a2
33 b = b2
33 b = b2
34
34
35 print("reduced:", reductions, len(a) + len(b),
35 print("reduced:", reductions, len(a) + len(b),
36 repr(a), repr(b))
36 repr(a), repr(b))
37 try:
37 try:
38 test1(a, b)
38 test1(a, b)
39 except Exception as inst:
39 except Exception as inst:
40 print("failed:", inst)
40 print("failed:", inst)
41
41
42 sys.exit(0)
42 sys.exit(0)
43
43
44 def test1(a, b):
44 def test1(a, b):
45 d = bdiff.bdiff(a, b)
45 d = mdiff.textdiff(a, b)
46 if not d:
46 if not d:
47 raise ValueError("empty")
47 raise ValueError("empty")
48 c = mpatch.patches(a, [d])
48 c = mpatch.patches(a, [d])
49 if c != b:
49 if c != b:
50 raise ValueError("bad")
50 raise ValueError("bad")
51
51
52 def testwrap(a, b):
52 def testwrap(a, b):
53 try:
53 try:
54 test1(a, b)
54 test1(a, b)
55 return
55 return
56 except Exception as inst:
56 except Exception as inst:
57 pass
57 pass
58 print("exception:", inst)
58 print("exception:", inst)
59 reducetest(a, b)
59 reducetest(a, b)
60
60
61 def test(a, b):
61 def test(a, b):
62 testwrap(a, b)
62 testwrap(a, b)
63 testwrap(b, a)
63 testwrap(b, a)
64
64
65 def rndtest(size, noise):
65 def rndtest(size, noise):
66 a = []
66 a = []
67 src = " aaaaaaaabbbbccd"
67 src = " aaaaaaaabbbbccd"
68 for x in xrange(size):
68 for x in xrange(size):
69 a.append(src[random.randint(0, len(src) - 1)])
69 a.append(src[random.randint(0, len(src) - 1)])
70
70
71 while True:
71 while True:
72 b = [c for c in a if random.randint(0, 99) > noise]
72 b = [c for c in a if random.randint(0, 99) > noise]
73 b2 = []
73 b2 = []
74 for c in b:
74 for c in b:
75 b2.append(c)
75 b2.append(c)
76 while random.randint(0, 99) < noise:
76 while random.randint(0, 99) < noise:
77 b2.append(src[random.randint(0, len(src) - 1)])
77 b2.append(src[random.randint(0, len(src) - 1)])
78 if b2 != a:
78 if b2 != a:
79 break
79 break
80
80
81 a = "\n".join(a) + "\n"
81 a = "\n".join(a) + "\n"
82 b = "\n".join(b2) + "\n"
82 b = "\n".join(b2) + "\n"
83
83
84 test(a, b)
84 test(a, b)
85
85
86 maxvol = 10000
86 maxvol = 10000
87 startsize = 2
87 startsize = 2
88 while True:
88 while True:
89 size = startsize
89 size = startsize
90 count = 0
90 count = 0
91 while size < maxvol:
91 while size < maxvol:
92 print(size)
92 print(size)
93 volume = 0
93 volume = 0
94 while volume < maxvol:
94 while volume < maxvol:
95 rndtest(size, 2)
95 rndtest(size, 2)
96 volume += size
96 volume += size
97 count += 2
97 count += 2
98 size *= 2
98 size *= 2
99 maxvol *= 4
99 maxvol *= 4
100 startsize *= 4
100 startsize *= 4
@@ -1,1309 +1,1308
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 # "historical portability" policy of perf.py:
4 # "historical portability" policy of perf.py:
5 #
5 #
6 # We have to do:
6 # We have to do:
7 # - make perf.py "loadable" with as wide Mercurial version as possible
7 # - make perf.py "loadable" with as wide Mercurial version as possible
8 # This doesn't mean that perf commands work correctly with that Mercurial.
8 # This doesn't mean that perf commands work correctly with that Mercurial.
9 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
9 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
10 # - make historical perf command work correctly with as wide Mercurial
10 # - make historical perf command work correctly with as wide Mercurial
11 # version as possible
11 # version as possible
12 #
12 #
13 # We have to do, if possible with reasonable cost:
13 # We have to do, if possible with reasonable cost:
14 # - make recent perf command for historical feature work correctly
14 # - make recent perf command for historical feature work correctly
15 # with early Mercurial
15 # with early Mercurial
16 #
16 #
17 # We don't have to do:
17 # We don't have to do:
18 # - make perf command for recent feature work correctly with early
18 # - make perf command for recent feature work correctly with early
19 # Mercurial
19 # Mercurial
20
20
21 from __future__ import absolute_import
21 from __future__ import absolute_import
22 import functools
22 import functools
23 import gc
23 import gc
24 import os
24 import os
25 import random
25 import random
26 import sys
26 import sys
27 import time
27 import time
28 from mercurial import (
28 from mercurial import (
29 bdiff,
30 changegroup,
29 changegroup,
31 cmdutil,
30 cmdutil,
32 commands,
31 commands,
33 copies,
32 copies,
34 error,
33 error,
35 extensions,
34 extensions,
36 mdiff,
35 mdiff,
37 merge,
36 merge,
38 util,
37 util,
39 )
38 )
40
39
41 # for "historical portability":
40 # for "historical portability":
42 # try to import modules separately (in dict order), and ignore
41 # try to import modules separately (in dict order), and ignore
43 # failure, because these aren't available with early Mercurial
42 # failure, because these aren't available with early Mercurial
44 try:
43 try:
45 from mercurial import branchmap # since 2.5 (or bcee63733aad)
44 from mercurial import branchmap # since 2.5 (or bcee63733aad)
46 except ImportError:
45 except ImportError:
47 pass
46 pass
48 try:
47 try:
49 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
48 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
50 except ImportError:
49 except ImportError:
51 pass
50 pass
52 try:
51 try:
53 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
52 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
54 except ImportError:
53 except ImportError:
55 pass
54 pass
56 try:
55 try:
57 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
56 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
58 except ImportError:
57 except ImportError:
59 pass
58 pass
60
59
61 # for "historical portability":
60 # for "historical portability":
62 # define util.safehasattr forcibly, because util.safehasattr has been
61 # define util.safehasattr forcibly, because util.safehasattr has been
63 # available since 1.9.3 (or 94b200a11cf7)
62 # available since 1.9.3 (or 94b200a11cf7)
64 _undefined = object()
63 _undefined = object()
65 def safehasattr(thing, attr):
64 def safehasattr(thing, attr):
66 return getattr(thing, attr, _undefined) is not _undefined
65 return getattr(thing, attr, _undefined) is not _undefined
67 setattr(util, 'safehasattr', safehasattr)
66 setattr(util, 'safehasattr', safehasattr)
68
67
69 # for "historical portability":
68 # for "historical portability":
70 # define util.timer forcibly, because util.timer has been available
69 # define util.timer forcibly, because util.timer has been available
71 # since ae5d60bb70c9
70 # since ae5d60bb70c9
72 if safehasattr(time, 'perf_counter'):
71 if safehasattr(time, 'perf_counter'):
73 util.timer = time.perf_counter
72 util.timer = time.perf_counter
74 elif os.name == 'nt':
73 elif os.name == 'nt':
75 util.timer = time.clock
74 util.timer = time.clock
76 else:
75 else:
77 util.timer = time.time
76 util.timer = time.time
78
77
79 # for "historical portability":
78 # for "historical portability":
80 # use locally defined empty option list, if formatteropts isn't
79 # use locally defined empty option list, if formatteropts isn't
81 # available, because commands.formatteropts has been available since
80 # available, because commands.formatteropts has been available since
82 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
81 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
83 # available since 2.2 (or ae5f92e154d3)
82 # available since 2.2 (or ae5f92e154d3)
84 formatteropts = getattr(commands, "formatteropts", [])
83 formatteropts = getattr(commands, "formatteropts", [])
85
84
86 # for "historical portability":
85 # for "historical portability":
87 # use locally defined option list, if debugrevlogopts isn't available,
86 # use locally defined option list, if debugrevlogopts isn't available,
88 # because commands.debugrevlogopts has been available since 3.7 (or
87 # because commands.debugrevlogopts has been available since 3.7 (or
89 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
88 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
90 # since 1.9 (or a79fea6b3e77).
89 # since 1.9 (or a79fea6b3e77).
91 revlogopts = getattr(commands, "debugrevlogopts", [
90 revlogopts = getattr(commands, "debugrevlogopts", [
92 ('c', 'changelog', False, ('open changelog')),
91 ('c', 'changelog', False, ('open changelog')),
93 ('m', 'manifest', False, ('open manifest')),
92 ('m', 'manifest', False, ('open manifest')),
94 ('', 'dir', False, ('open directory manifest')),
93 ('', 'dir', False, ('open directory manifest')),
95 ])
94 ])
96
95
97 cmdtable = {}
96 cmdtable = {}
98
97
99 # for "historical portability":
98 # for "historical portability":
100 # define parsealiases locally, because cmdutil.parsealiases has been
99 # define parsealiases locally, because cmdutil.parsealiases has been
101 # available since 1.5 (or 6252852b4332)
100 # available since 1.5 (or 6252852b4332)
102 def parsealiases(cmd):
101 def parsealiases(cmd):
103 return cmd.lstrip("^").split("|")
102 return cmd.lstrip("^").split("|")
104
103
105 if safehasattr(cmdutil, 'command'):
104 if safehasattr(cmdutil, 'command'):
106 import inspect
105 import inspect
107 command = cmdutil.command(cmdtable)
106 command = cmdutil.command(cmdtable)
108 if 'norepo' not in inspect.getargspec(command)[0]:
107 if 'norepo' not in inspect.getargspec(command)[0]:
109 # for "historical portability":
108 # for "historical portability":
110 # wrap original cmdutil.command, because "norepo" option has
109 # wrap original cmdutil.command, because "norepo" option has
111 # been available since 3.1 (or 75a96326cecb)
110 # been available since 3.1 (or 75a96326cecb)
112 _command = command
111 _command = command
113 def command(name, options=(), synopsis=None, norepo=False):
112 def command(name, options=(), synopsis=None, norepo=False):
114 if norepo:
113 if norepo:
115 commands.norepo += ' %s' % ' '.join(parsealiases(name))
114 commands.norepo += ' %s' % ' '.join(parsealiases(name))
116 return _command(name, list(options), synopsis)
115 return _command(name, list(options), synopsis)
117 else:
116 else:
118 # for "historical portability":
117 # for "historical portability":
119 # define "@command" annotation locally, because cmdutil.command
118 # define "@command" annotation locally, because cmdutil.command
120 # has been available since 1.9 (or 2daa5179e73f)
119 # has been available since 1.9 (or 2daa5179e73f)
121 def command(name, options=(), synopsis=None, norepo=False):
120 def command(name, options=(), synopsis=None, norepo=False):
122 def decorator(func):
121 def decorator(func):
123 if synopsis:
122 if synopsis:
124 cmdtable[name] = func, list(options), synopsis
123 cmdtable[name] = func, list(options), synopsis
125 else:
124 else:
126 cmdtable[name] = func, list(options)
125 cmdtable[name] = func, list(options)
127 if norepo:
126 if norepo:
128 commands.norepo += ' %s' % ' '.join(parsealiases(name))
127 commands.norepo += ' %s' % ' '.join(parsealiases(name))
129 return func
128 return func
130 return decorator
129 return decorator
131
130
132 def getlen(ui):
131 def getlen(ui):
133 if ui.configbool("perf", "stub"):
132 if ui.configbool("perf", "stub"):
134 return lambda x: 1
133 return lambda x: 1
135 return len
134 return len
136
135
137 def gettimer(ui, opts=None):
136 def gettimer(ui, opts=None):
138 """return a timer function and formatter: (timer, formatter)
137 """return a timer function and formatter: (timer, formatter)
139
138
140 This function exists to gather the creation of formatter in a single
139 This function exists to gather the creation of formatter in a single
141 place instead of duplicating it in all performance commands."""
140 place instead of duplicating it in all performance commands."""
142
141
143 # enforce an idle period before execution to counteract power management
142 # enforce an idle period before execution to counteract power management
144 # experimental config: perf.presleep
143 # experimental config: perf.presleep
145 time.sleep(getint(ui, "perf", "presleep", 1))
144 time.sleep(getint(ui, "perf", "presleep", 1))
146
145
147 if opts is None:
146 if opts is None:
148 opts = {}
147 opts = {}
149 # redirect all to stderr unless buffer api is in use
148 # redirect all to stderr unless buffer api is in use
150 if not ui._buffers:
149 if not ui._buffers:
151 ui = ui.copy()
150 ui = ui.copy()
152 uifout = safeattrsetter(ui, 'fout', ignoremissing=True)
151 uifout = safeattrsetter(ui, 'fout', ignoremissing=True)
153 if uifout:
152 if uifout:
154 # for "historical portability":
153 # for "historical portability":
155 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
154 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
156 uifout.set(ui.ferr)
155 uifout.set(ui.ferr)
157
156
158 # get a formatter
157 # get a formatter
159 uiformatter = getattr(ui, 'formatter', None)
158 uiformatter = getattr(ui, 'formatter', None)
160 if uiformatter:
159 if uiformatter:
161 fm = uiformatter('perf', opts)
160 fm = uiformatter('perf', opts)
162 else:
161 else:
163 # for "historical portability":
162 # for "historical portability":
164 # define formatter locally, because ui.formatter has been
163 # define formatter locally, because ui.formatter has been
165 # available since 2.2 (or ae5f92e154d3)
164 # available since 2.2 (or ae5f92e154d3)
166 from mercurial import node
165 from mercurial import node
167 class defaultformatter(object):
166 class defaultformatter(object):
168 """Minimized composition of baseformatter and plainformatter
167 """Minimized composition of baseformatter and plainformatter
169 """
168 """
170 def __init__(self, ui, topic, opts):
169 def __init__(self, ui, topic, opts):
171 self._ui = ui
170 self._ui = ui
172 if ui.debugflag:
171 if ui.debugflag:
173 self.hexfunc = node.hex
172 self.hexfunc = node.hex
174 else:
173 else:
175 self.hexfunc = node.short
174 self.hexfunc = node.short
176 def __nonzero__(self):
175 def __nonzero__(self):
177 return False
176 return False
178 __bool__ = __nonzero__
177 __bool__ = __nonzero__
179 def startitem(self):
178 def startitem(self):
180 pass
179 pass
181 def data(self, **data):
180 def data(self, **data):
182 pass
181 pass
183 def write(self, fields, deftext, *fielddata, **opts):
182 def write(self, fields, deftext, *fielddata, **opts):
184 self._ui.write(deftext % fielddata, **opts)
183 self._ui.write(deftext % fielddata, **opts)
185 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
184 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
186 if cond:
185 if cond:
187 self._ui.write(deftext % fielddata, **opts)
186 self._ui.write(deftext % fielddata, **opts)
188 def plain(self, text, **opts):
187 def plain(self, text, **opts):
189 self._ui.write(text, **opts)
188 self._ui.write(text, **opts)
190 def end(self):
189 def end(self):
191 pass
190 pass
192 fm = defaultformatter(ui, 'perf', opts)
191 fm = defaultformatter(ui, 'perf', opts)
193
192
194 # stub function, runs code only once instead of in a loop
193 # stub function, runs code only once instead of in a loop
195 # experimental config: perf.stub
194 # experimental config: perf.stub
196 if ui.configbool("perf", "stub"):
195 if ui.configbool("perf", "stub"):
197 return functools.partial(stub_timer, fm), fm
196 return functools.partial(stub_timer, fm), fm
198 return functools.partial(_timer, fm), fm
197 return functools.partial(_timer, fm), fm
199
198
200 def stub_timer(fm, func, title=None):
199 def stub_timer(fm, func, title=None):
201 func()
200 func()
202
201
203 def _timer(fm, func, title=None):
202 def _timer(fm, func, title=None):
204 gc.collect()
203 gc.collect()
205 results = []
204 results = []
206 begin = util.timer()
205 begin = util.timer()
207 count = 0
206 count = 0
208 while True:
207 while True:
209 ostart = os.times()
208 ostart = os.times()
210 cstart = util.timer()
209 cstart = util.timer()
211 r = func()
210 r = func()
212 cstop = util.timer()
211 cstop = util.timer()
213 ostop = os.times()
212 ostop = os.times()
214 count += 1
213 count += 1
215 a, b = ostart, ostop
214 a, b = ostart, ostop
216 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
215 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
217 if cstop - begin > 3 and count >= 100:
216 if cstop - begin > 3 and count >= 100:
218 break
217 break
219 if cstop - begin > 10 and count >= 3:
218 if cstop - begin > 10 and count >= 3:
220 break
219 break
221
220
222 fm.startitem()
221 fm.startitem()
223
222
224 if title:
223 if title:
225 fm.write('title', '! %s\n', title)
224 fm.write('title', '! %s\n', title)
226 if r:
225 if r:
227 fm.write('result', '! result: %s\n', r)
226 fm.write('result', '! result: %s\n', r)
228 m = min(results)
227 m = min(results)
229 fm.plain('!')
228 fm.plain('!')
230 fm.write('wall', ' wall %f', m[0])
229 fm.write('wall', ' wall %f', m[0])
231 fm.write('comb', ' comb %f', m[1] + m[2])
230 fm.write('comb', ' comb %f', m[1] + m[2])
232 fm.write('user', ' user %f', m[1])
231 fm.write('user', ' user %f', m[1])
233 fm.write('sys', ' sys %f', m[2])
232 fm.write('sys', ' sys %f', m[2])
234 fm.write('count', ' (best of %d)', count)
233 fm.write('count', ' (best of %d)', count)
235 fm.plain('\n')
234 fm.plain('\n')
236
235
237 # utilities for historical portability
236 # utilities for historical portability
238
237
239 def getint(ui, section, name, default):
238 def getint(ui, section, name, default):
240 # for "historical portability":
239 # for "historical portability":
241 # ui.configint has been available since 1.9 (or fa2b596db182)
240 # ui.configint has been available since 1.9 (or fa2b596db182)
242 v = ui.config(section, name, None)
241 v = ui.config(section, name, None)
243 if v is None:
242 if v is None:
244 return default
243 return default
245 try:
244 try:
246 return int(v)
245 return int(v)
247 except ValueError:
246 except ValueError:
248 raise error.ConfigError(("%s.%s is not an integer ('%s')")
247 raise error.ConfigError(("%s.%s is not an integer ('%s')")
249 % (section, name, v))
248 % (section, name, v))
250
249
251 def safeattrsetter(obj, name, ignoremissing=False):
250 def safeattrsetter(obj, name, ignoremissing=False):
252 """Ensure that 'obj' has 'name' attribute before subsequent setattr
251 """Ensure that 'obj' has 'name' attribute before subsequent setattr
253
252
254 This function is aborted, if 'obj' doesn't have 'name' attribute
253 This function is aborted, if 'obj' doesn't have 'name' attribute
255 at runtime. This avoids overlooking removal of an attribute, which
254 at runtime. This avoids overlooking removal of an attribute, which
256 breaks assumption of performance measurement, in the future.
255 breaks assumption of performance measurement, in the future.
257
256
258 This function returns the object to (1) assign a new value, and
257 This function returns the object to (1) assign a new value, and
259 (2) restore an original value to the attribute.
258 (2) restore an original value to the attribute.
260
259
261 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
260 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
262 abortion, and this function returns None. This is useful to
261 abortion, and this function returns None. This is useful to
263 examine an attribute, which isn't ensured in all Mercurial
262 examine an attribute, which isn't ensured in all Mercurial
264 versions.
263 versions.
265 """
264 """
266 if not util.safehasattr(obj, name):
265 if not util.safehasattr(obj, name):
267 if ignoremissing:
266 if ignoremissing:
268 return None
267 return None
269 raise error.Abort(("missing attribute %s of %s might break assumption"
268 raise error.Abort(("missing attribute %s of %s might break assumption"
270 " of performance measurement") % (name, obj))
269 " of performance measurement") % (name, obj))
271
270
272 origvalue = getattr(obj, name)
271 origvalue = getattr(obj, name)
273 class attrutil(object):
272 class attrutil(object):
274 def set(self, newvalue):
273 def set(self, newvalue):
275 setattr(obj, name, newvalue)
274 setattr(obj, name, newvalue)
276 def restore(self):
275 def restore(self):
277 setattr(obj, name, origvalue)
276 setattr(obj, name, origvalue)
278
277
279 return attrutil()
278 return attrutil()
280
279
281 # utilities to examine each internal API changes
280 # utilities to examine each internal API changes
282
281
283 def getbranchmapsubsettable():
282 def getbranchmapsubsettable():
284 # for "historical portability":
283 # for "historical portability":
285 # subsettable is defined in:
284 # subsettable is defined in:
286 # - branchmap since 2.9 (or 175c6fd8cacc)
285 # - branchmap since 2.9 (or 175c6fd8cacc)
287 # - repoview since 2.5 (or 59a9f18d4587)
286 # - repoview since 2.5 (or 59a9f18d4587)
288 for mod in (branchmap, repoview):
287 for mod in (branchmap, repoview):
289 subsettable = getattr(mod, 'subsettable', None)
288 subsettable = getattr(mod, 'subsettable', None)
290 if subsettable:
289 if subsettable:
291 return subsettable
290 return subsettable
292
291
293 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
292 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
294 # branchmap and repoview modules exist, but subsettable attribute
293 # branchmap and repoview modules exist, but subsettable attribute
295 # doesn't)
294 # doesn't)
296 raise error.Abort(("perfbranchmap not available with this Mercurial"),
295 raise error.Abort(("perfbranchmap not available with this Mercurial"),
297 hint="use 2.5 or later")
296 hint="use 2.5 or later")
298
297
299 def getsvfs(repo):
298 def getsvfs(repo):
300 """Return appropriate object to access files under .hg/store
299 """Return appropriate object to access files under .hg/store
301 """
300 """
302 # for "historical portability":
301 # for "historical portability":
303 # repo.svfs has been available since 2.3 (or 7034365089bf)
302 # repo.svfs has been available since 2.3 (or 7034365089bf)
304 svfs = getattr(repo, 'svfs', None)
303 svfs = getattr(repo, 'svfs', None)
305 if svfs:
304 if svfs:
306 return svfs
305 return svfs
307 else:
306 else:
308 return getattr(repo, 'sopener')
307 return getattr(repo, 'sopener')
309
308
310 def getvfs(repo):
309 def getvfs(repo):
311 """Return appropriate object to access files under .hg
310 """Return appropriate object to access files under .hg
312 """
311 """
313 # for "historical portability":
312 # for "historical portability":
314 # repo.vfs has been available since 2.3 (or 7034365089bf)
313 # repo.vfs has been available since 2.3 (or 7034365089bf)
315 vfs = getattr(repo, 'vfs', None)
314 vfs = getattr(repo, 'vfs', None)
316 if vfs:
315 if vfs:
317 return vfs
316 return vfs
318 else:
317 else:
319 return getattr(repo, 'opener')
318 return getattr(repo, 'opener')
320
319
321 def repocleartagscachefunc(repo):
320 def repocleartagscachefunc(repo):
322 """Return the function to clear tags cache according to repo internal API
321 """Return the function to clear tags cache according to repo internal API
323 """
322 """
324 if util.safehasattr(repo, '_tagscache'): # since 2.0 (or 9dca7653b525)
323 if util.safehasattr(repo, '_tagscache'): # since 2.0 (or 9dca7653b525)
325 # in this case, setattr(repo, '_tagscache', None) or so isn't
324 # in this case, setattr(repo, '_tagscache', None) or so isn't
326 # correct way to clear tags cache, because existing code paths
325 # correct way to clear tags cache, because existing code paths
327 # expect _tagscache to be a structured object.
326 # expect _tagscache to be a structured object.
328 def clearcache():
327 def clearcache():
329 # _tagscache has been filteredpropertycache since 2.5 (or
328 # _tagscache has been filteredpropertycache since 2.5 (or
330 # 98c867ac1330), and delattr() can't work in such case
329 # 98c867ac1330), and delattr() can't work in such case
331 if '_tagscache' in vars(repo):
330 if '_tagscache' in vars(repo):
332 del repo.__dict__['_tagscache']
331 del repo.__dict__['_tagscache']
333 return clearcache
332 return clearcache
334
333
335 repotags = safeattrsetter(repo, '_tags', ignoremissing=True)
334 repotags = safeattrsetter(repo, '_tags', ignoremissing=True)
336 if repotags: # since 1.4 (or 5614a628d173)
335 if repotags: # since 1.4 (or 5614a628d173)
337 return lambda : repotags.set(None)
336 return lambda : repotags.set(None)
338
337
339 repotagscache = safeattrsetter(repo, 'tagscache', ignoremissing=True)
338 repotagscache = safeattrsetter(repo, 'tagscache', ignoremissing=True)
340 if repotagscache: # since 0.6 (or d7df759d0e97)
339 if repotagscache: # since 0.6 (or d7df759d0e97)
341 return lambda : repotagscache.set(None)
340 return lambda : repotagscache.set(None)
342
341
343 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
342 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
344 # this point, but it isn't so problematic, because:
343 # this point, but it isn't so problematic, because:
345 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
344 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
346 # in perftags() causes failure soon
345 # in perftags() causes failure soon
347 # - perf.py itself has been available since 1.1 (or eb240755386d)
346 # - perf.py itself has been available since 1.1 (or eb240755386d)
348 raise error.Abort(("tags API of this hg command is unknown"))
347 raise error.Abort(("tags API of this hg command is unknown"))
349
348
350 # perf commands
349 # perf commands
351
350
352 @command('perfwalk', formatteropts)
351 @command('perfwalk', formatteropts)
353 def perfwalk(ui, repo, *pats, **opts):
352 def perfwalk(ui, repo, *pats, **opts):
354 timer, fm = gettimer(ui, opts)
353 timer, fm = gettimer(ui, opts)
355 try:
354 try:
356 m = scmutil.match(repo[None], pats, {})
355 m = scmutil.match(repo[None], pats, {})
357 timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
356 timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
358 except Exception:
357 except Exception:
359 try:
358 try:
360 m = scmutil.match(repo[None], pats, {})
359 m = scmutil.match(repo[None], pats, {})
361 timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
360 timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
362 except Exception:
361 except Exception:
363 timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
362 timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
364 fm.end()
363 fm.end()
365
364
366 @command('perfannotate', formatteropts)
365 @command('perfannotate', formatteropts)
367 def perfannotate(ui, repo, f, **opts):
366 def perfannotate(ui, repo, f, **opts):
368 timer, fm = gettimer(ui, opts)
367 timer, fm = gettimer(ui, opts)
369 fc = repo['.'][f]
368 fc = repo['.'][f]
370 timer(lambda: len(fc.annotate(True)))
369 timer(lambda: len(fc.annotate(True)))
371 fm.end()
370 fm.end()
372
371
373 @command('perfstatus',
372 @command('perfstatus',
374 [('u', 'unknown', False,
373 [('u', 'unknown', False,
375 'ask status to look for unknown files')] + formatteropts)
374 'ask status to look for unknown files')] + formatteropts)
376 def perfstatus(ui, repo, **opts):
375 def perfstatus(ui, repo, **opts):
377 #m = match.always(repo.root, repo.getcwd())
376 #m = match.always(repo.root, repo.getcwd())
378 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
377 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
379 # False))))
378 # False))))
380 timer, fm = gettimer(ui, opts)
379 timer, fm = gettimer(ui, opts)
381 timer(lambda: sum(map(len, repo.status(unknown=opts['unknown']))))
380 timer(lambda: sum(map(len, repo.status(unknown=opts['unknown']))))
382 fm.end()
381 fm.end()
383
382
384 @command('perfaddremove', formatteropts)
383 @command('perfaddremove', formatteropts)
385 def perfaddremove(ui, repo, **opts):
384 def perfaddremove(ui, repo, **opts):
386 timer, fm = gettimer(ui, opts)
385 timer, fm = gettimer(ui, opts)
387 try:
386 try:
388 oldquiet = repo.ui.quiet
387 oldquiet = repo.ui.quiet
389 repo.ui.quiet = True
388 repo.ui.quiet = True
390 matcher = scmutil.match(repo[None])
389 matcher = scmutil.match(repo[None])
391 timer(lambda: scmutil.addremove(repo, matcher, "", dry_run=True))
390 timer(lambda: scmutil.addremove(repo, matcher, "", dry_run=True))
392 finally:
391 finally:
393 repo.ui.quiet = oldquiet
392 repo.ui.quiet = oldquiet
394 fm.end()
393 fm.end()
395
394
396 def clearcaches(cl):
395 def clearcaches(cl):
397 # behave somewhat consistently across internal API changes
396 # behave somewhat consistently across internal API changes
398 if util.safehasattr(cl, 'clearcaches'):
397 if util.safehasattr(cl, 'clearcaches'):
399 cl.clearcaches()
398 cl.clearcaches()
400 elif util.safehasattr(cl, '_nodecache'):
399 elif util.safehasattr(cl, '_nodecache'):
401 from mercurial.node import nullid, nullrev
400 from mercurial.node import nullid, nullrev
402 cl._nodecache = {nullid: nullrev}
401 cl._nodecache = {nullid: nullrev}
403 cl._nodepos = None
402 cl._nodepos = None
404
403
405 @command('perfheads', formatteropts)
404 @command('perfheads', formatteropts)
406 def perfheads(ui, repo, **opts):
405 def perfheads(ui, repo, **opts):
407 timer, fm = gettimer(ui, opts)
406 timer, fm = gettimer(ui, opts)
408 cl = repo.changelog
407 cl = repo.changelog
409 def d():
408 def d():
410 len(cl.headrevs())
409 len(cl.headrevs())
411 clearcaches(cl)
410 clearcaches(cl)
412 timer(d)
411 timer(d)
413 fm.end()
412 fm.end()
414
413
415 @command('perftags', formatteropts)
414 @command('perftags', formatteropts)
416 def perftags(ui, repo, **opts):
415 def perftags(ui, repo, **opts):
417 import mercurial.changelog
416 import mercurial.changelog
418 import mercurial.manifest
417 import mercurial.manifest
419 timer, fm = gettimer(ui, opts)
418 timer, fm = gettimer(ui, opts)
420 svfs = getsvfs(repo)
419 svfs = getsvfs(repo)
421 repocleartagscache = repocleartagscachefunc(repo)
420 repocleartagscache = repocleartagscachefunc(repo)
422 def t():
421 def t():
423 repo.changelog = mercurial.changelog.changelog(svfs)
422 repo.changelog = mercurial.changelog.changelog(svfs)
424 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo)
423 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo)
425 repocleartagscache()
424 repocleartagscache()
426 return len(repo.tags())
425 return len(repo.tags())
427 timer(t)
426 timer(t)
428 fm.end()
427 fm.end()
429
428
430 @command('perfancestors', formatteropts)
429 @command('perfancestors', formatteropts)
431 def perfancestors(ui, repo, **opts):
430 def perfancestors(ui, repo, **opts):
432 timer, fm = gettimer(ui, opts)
431 timer, fm = gettimer(ui, opts)
433 heads = repo.changelog.headrevs()
432 heads = repo.changelog.headrevs()
434 def d():
433 def d():
435 for a in repo.changelog.ancestors(heads):
434 for a in repo.changelog.ancestors(heads):
436 pass
435 pass
437 timer(d)
436 timer(d)
438 fm.end()
437 fm.end()
439
438
440 @command('perfancestorset', formatteropts)
439 @command('perfancestorset', formatteropts)
441 def perfancestorset(ui, repo, revset, **opts):
440 def perfancestorset(ui, repo, revset, **opts):
442 timer, fm = gettimer(ui, opts)
441 timer, fm = gettimer(ui, opts)
443 revs = repo.revs(revset)
442 revs = repo.revs(revset)
444 heads = repo.changelog.headrevs()
443 heads = repo.changelog.headrevs()
445 def d():
444 def d():
446 s = repo.changelog.ancestors(heads)
445 s = repo.changelog.ancestors(heads)
447 for rev in revs:
446 for rev in revs:
448 rev in s
447 rev in s
449 timer(d)
448 timer(d)
450 fm.end()
449 fm.end()
451
450
452 @command('perfchangegroupchangelog', formatteropts +
451 @command('perfchangegroupchangelog', formatteropts +
453 [('', 'version', '02', 'changegroup version'),
452 [('', 'version', '02', 'changegroup version'),
454 ('r', 'rev', '', 'revisions to add to changegroup')])
453 ('r', 'rev', '', 'revisions to add to changegroup')])
455 def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
454 def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
456 """Benchmark producing a changelog group for a changegroup.
455 """Benchmark producing a changelog group for a changegroup.
457
456
458 This measures the time spent processing the changelog during a
457 This measures the time spent processing the changelog during a
459 bundle operation. This occurs during `hg bundle` and on a server
458 bundle operation. This occurs during `hg bundle` and on a server
460 processing a `getbundle` wire protocol request (handles clones
459 processing a `getbundle` wire protocol request (handles clones
461 and pull requests).
460 and pull requests).
462
461
463 By default, all revisions are added to the changegroup.
462 By default, all revisions are added to the changegroup.
464 """
463 """
465 cl = repo.changelog
464 cl = repo.changelog
466 revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
465 revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
467 bundler = changegroup.getbundler(version, repo)
466 bundler = changegroup.getbundler(version, repo)
468
467
469 def lookup(node):
468 def lookup(node):
470 # The real bundler reads the revision in order to access the
469 # The real bundler reads the revision in order to access the
471 # manifest node and files list. Do that here.
470 # manifest node and files list. Do that here.
472 cl.read(node)
471 cl.read(node)
473 return node
472 return node
474
473
475 def d():
474 def d():
476 for chunk in bundler.group(revs, cl, lookup):
475 for chunk in bundler.group(revs, cl, lookup):
477 pass
476 pass
478
477
479 timer, fm = gettimer(ui, opts)
478 timer, fm = gettimer(ui, opts)
480 timer(d)
479 timer(d)
481 fm.end()
480 fm.end()
482
481
483 @command('perfdirs', formatteropts)
482 @command('perfdirs', formatteropts)
484 def perfdirs(ui, repo, **opts):
483 def perfdirs(ui, repo, **opts):
485 timer, fm = gettimer(ui, opts)
484 timer, fm = gettimer(ui, opts)
486 dirstate = repo.dirstate
485 dirstate = repo.dirstate
487 'a' in dirstate
486 'a' in dirstate
488 def d():
487 def d():
489 dirstate.dirs()
488 dirstate.dirs()
490 del dirstate._dirs
489 del dirstate._dirs
491 timer(d)
490 timer(d)
492 fm.end()
491 fm.end()
493
492
494 @command('perfdirstate', formatteropts)
493 @command('perfdirstate', formatteropts)
495 def perfdirstate(ui, repo, **opts):
494 def perfdirstate(ui, repo, **opts):
496 timer, fm = gettimer(ui, opts)
495 timer, fm = gettimer(ui, opts)
497 "a" in repo.dirstate
496 "a" in repo.dirstate
498 def d():
497 def d():
499 repo.dirstate.invalidate()
498 repo.dirstate.invalidate()
500 "a" in repo.dirstate
499 "a" in repo.dirstate
501 timer(d)
500 timer(d)
502 fm.end()
501 fm.end()
503
502
504 @command('perfdirstatedirs', formatteropts)
503 @command('perfdirstatedirs', formatteropts)
505 def perfdirstatedirs(ui, repo, **opts):
504 def perfdirstatedirs(ui, repo, **opts):
506 timer, fm = gettimer(ui, opts)
505 timer, fm = gettimer(ui, opts)
507 "a" in repo.dirstate
506 "a" in repo.dirstate
508 def d():
507 def d():
509 "a" in repo.dirstate._dirs
508 "a" in repo.dirstate._dirs
510 del repo.dirstate._dirs
509 del repo.dirstate._dirs
511 timer(d)
510 timer(d)
512 fm.end()
511 fm.end()
513
512
514 @command('perfdirstatefoldmap', formatteropts)
513 @command('perfdirstatefoldmap', formatteropts)
515 def perfdirstatefoldmap(ui, repo, **opts):
514 def perfdirstatefoldmap(ui, repo, **opts):
516 timer, fm = gettimer(ui, opts)
515 timer, fm = gettimer(ui, opts)
517 dirstate = repo.dirstate
516 dirstate = repo.dirstate
518 'a' in dirstate
517 'a' in dirstate
519 def d():
518 def d():
520 dirstate._filefoldmap.get('a')
519 dirstate._filefoldmap.get('a')
521 del dirstate._filefoldmap
520 del dirstate._filefoldmap
522 timer(d)
521 timer(d)
523 fm.end()
522 fm.end()
524
523
525 @command('perfdirfoldmap', formatteropts)
524 @command('perfdirfoldmap', formatteropts)
526 def perfdirfoldmap(ui, repo, **opts):
525 def perfdirfoldmap(ui, repo, **opts):
527 timer, fm = gettimer(ui, opts)
526 timer, fm = gettimer(ui, opts)
528 dirstate = repo.dirstate
527 dirstate = repo.dirstate
529 'a' in dirstate
528 'a' in dirstate
530 def d():
529 def d():
531 dirstate._dirfoldmap.get('a')
530 dirstate._dirfoldmap.get('a')
532 del dirstate._dirfoldmap
531 del dirstate._dirfoldmap
533 del dirstate._dirs
532 del dirstate._dirs
534 timer(d)
533 timer(d)
535 fm.end()
534 fm.end()
536
535
537 @command('perfdirstatewrite', formatteropts)
536 @command('perfdirstatewrite', formatteropts)
538 def perfdirstatewrite(ui, repo, **opts):
537 def perfdirstatewrite(ui, repo, **opts):
539 timer, fm = gettimer(ui, opts)
538 timer, fm = gettimer(ui, opts)
540 ds = repo.dirstate
539 ds = repo.dirstate
541 "a" in ds
540 "a" in ds
542 def d():
541 def d():
543 ds._dirty = True
542 ds._dirty = True
544 ds.write(repo.currenttransaction())
543 ds.write(repo.currenttransaction())
545 timer(d)
544 timer(d)
546 fm.end()
545 fm.end()
547
546
548 @command('perfmergecalculate',
547 @command('perfmergecalculate',
549 [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
548 [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
550 def perfmergecalculate(ui, repo, rev, **opts):
549 def perfmergecalculate(ui, repo, rev, **opts):
551 timer, fm = gettimer(ui, opts)
550 timer, fm = gettimer(ui, opts)
552 wctx = repo[None]
551 wctx = repo[None]
553 rctx = scmutil.revsingle(repo, rev, rev)
552 rctx = scmutil.revsingle(repo, rev, rev)
554 ancestor = wctx.ancestor(rctx)
553 ancestor = wctx.ancestor(rctx)
555 # we don't want working dir files to be stat'd in the benchmark, so prime
554 # we don't want working dir files to be stat'd in the benchmark, so prime
556 # that cache
555 # that cache
557 wctx.dirty()
556 wctx.dirty()
558 def d():
557 def d():
559 # acceptremote is True because we don't want prompts in the middle of
558 # acceptremote is True because we don't want prompts in the middle of
560 # our benchmark
559 # our benchmark
561 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
560 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
562 acceptremote=True, followcopies=True)
561 acceptremote=True, followcopies=True)
563 timer(d)
562 timer(d)
564 fm.end()
563 fm.end()
565
564
566 @command('perfpathcopies', [], "REV REV")
565 @command('perfpathcopies', [], "REV REV")
567 def perfpathcopies(ui, repo, rev1, rev2, **opts):
566 def perfpathcopies(ui, repo, rev1, rev2, **opts):
568 timer, fm = gettimer(ui, opts)
567 timer, fm = gettimer(ui, opts)
569 ctx1 = scmutil.revsingle(repo, rev1, rev1)
568 ctx1 = scmutil.revsingle(repo, rev1, rev1)
570 ctx2 = scmutil.revsingle(repo, rev2, rev2)
569 ctx2 = scmutil.revsingle(repo, rev2, rev2)
571 def d():
570 def d():
572 copies.pathcopies(ctx1, ctx2)
571 copies.pathcopies(ctx1, ctx2)
573 timer(d)
572 timer(d)
574 fm.end()
573 fm.end()
575
574
576 @command('perfmanifest', [], 'REV')
575 @command('perfmanifest', [], 'REV')
577 def perfmanifest(ui, repo, rev, **opts):
576 def perfmanifest(ui, repo, rev, **opts):
578 timer, fm = gettimer(ui, opts)
577 timer, fm = gettimer(ui, opts)
579 ctx = scmutil.revsingle(repo, rev, rev)
578 ctx = scmutil.revsingle(repo, rev, rev)
580 t = ctx.manifestnode()
579 t = ctx.manifestnode()
581 def d():
580 def d():
582 repo.manifestlog.clearcaches()
581 repo.manifestlog.clearcaches()
583 repo.manifestlog[t].read()
582 repo.manifestlog[t].read()
584 timer(d)
583 timer(d)
585 fm.end()
584 fm.end()
586
585
587 @command('perfchangeset', formatteropts)
586 @command('perfchangeset', formatteropts)
588 def perfchangeset(ui, repo, rev, **opts):
587 def perfchangeset(ui, repo, rev, **opts):
589 timer, fm = gettimer(ui, opts)
588 timer, fm = gettimer(ui, opts)
590 n = repo[rev].node()
589 n = repo[rev].node()
591 def d():
590 def d():
592 repo.changelog.read(n)
591 repo.changelog.read(n)
593 #repo.changelog._cache = None
592 #repo.changelog._cache = None
594 timer(d)
593 timer(d)
595 fm.end()
594 fm.end()
596
595
597 @command('perfindex', formatteropts)
596 @command('perfindex', formatteropts)
598 def perfindex(ui, repo, **opts):
597 def perfindex(ui, repo, **opts):
599 import mercurial.revlog
598 import mercurial.revlog
600 timer, fm = gettimer(ui, opts)
599 timer, fm = gettimer(ui, opts)
601 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
600 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
602 n = repo["tip"].node()
601 n = repo["tip"].node()
603 svfs = getsvfs(repo)
602 svfs = getsvfs(repo)
604 def d():
603 def d():
605 cl = mercurial.revlog.revlog(svfs, "00changelog.i")
604 cl = mercurial.revlog.revlog(svfs, "00changelog.i")
606 cl.rev(n)
605 cl.rev(n)
607 timer(d)
606 timer(d)
608 fm.end()
607 fm.end()
609
608
610 @command('perfstartup', formatteropts)
609 @command('perfstartup', formatteropts)
611 def perfstartup(ui, repo, **opts):
610 def perfstartup(ui, repo, **opts):
612 timer, fm = gettimer(ui, opts)
611 timer, fm = gettimer(ui, opts)
613 cmd = sys.argv[0]
612 cmd = sys.argv[0]
614 def d():
613 def d():
615 if os.name != 'nt':
614 if os.name != 'nt':
616 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
615 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
617 else:
616 else:
618 os.environ['HGRCPATH'] = ''
617 os.environ['HGRCPATH'] = ''
619 os.system("%s version -q > NUL" % cmd)
618 os.system("%s version -q > NUL" % cmd)
620 timer(d)
619 timer(d)
621 fm.end()
620 fm.end()
622
621
623 @command('perfparents', formatteropts)
622 @command('perfparents', formatteropts)
624 def perfparents(ui, repo, **opts):
623 def perfparents(ui, repo, **opts):
625 timer, fm = gettimer(ui, opts)
624 timer, fm = gettimer(ui, opts)
626 # control the number of commits perfparents iterates over
625 # control the number of commits perfparents iterates over
627 # experimental config: perf.parentscount
626 # experimental config: perf.parentscount
628 count = getint(ui, "perf", "parentscount", 1000)
627 count = getint(ui, "perf", "parentscount", 1000)
629 if len(repo.changelog) < count:
628 if len(repo.changelog) < count:
630 raise error.Abort("repo needs %d commits for this test" % count)
629 raise error.Abort("repo needs %d commits for this test" % count)
631 repo = repo.unfiltered()
630 repo = repo.unfiltered()
632 nl = [repo.changelog.node(i) for i in xrange(count)]
631 nl = [repo.changelog.node(i) for i in xrange(count)]
633 def d():
632 def d():
634 for n in nl:
633 for n in nl:
635 repo.changelog.parents(n)
634 repo.changelog.parents(n)
636 timer(d)
635 timer(d)
637 fm.end()
636 fm.end()
638
637
639 @command('perfctxfiles', formatteropts)
638 @command('perfctxfiles', formatteropts)
640 def perfctxfiles(ui, repo, x, **opts):
639 def perfctxfiles(ui, repo, x, **opts):
641 x = int(x)
640 x = int(x)
642 timer, fm = gettimer(ui, opts)
641 timer, fm = gettimer(ui, opts)
643 def d():
642 def d():
644 len(repo[x].files())
643 len(repo[x].files())
645 timer(d)
644 timer(d)
646 fm.end()
645 fm.end()
647
646
648 @command('perfrawfiles', formatteropts)
647 @command('perfrawfiles', formatteropts)
649 def perfrawfiles(ui, repo, x, **opts):
648 def perfrawfiles(ui, repo, x, **opts):
650 x = int(x)
649 x = int(x)
651 timer, fm = gettimer(ui, opts)
650 timer, fm = gettimer(ui, opts)
652 cl = repo.changelog
651 cl = repo.changelog
653 def d():
652 def d():
654 len(cl.read(x)[3])
653 len(cl.read(x)[3])
655 timer(d)
654 timer(d)
656 fm.end()
655 fm.end()
657
656
658 @command('perflookup', formatteropts)
657 @command('perflookup', formatteropts)
659 def perflookup(ui, repo, rev, **opts):
658 def perflookup(ui, repo, rev, **opts):
660 timer, fm = gettimer(ui, opts)
659 timer, fm = gettimer(ui, opts)
661 timer(lambda: len(repo.lookup(rev)))
660 timer(lambda: len(repo.lookup(rev)))
662 fm.end()
661 fm.end()
663
662
664 @command('perfrevrange', formatteropts)
663 @command('perfrevrange', formatteropts)
665 def perfrevrange(ui, repo, *specs, **opts):
664 def perfrevrange(ui, repo, *specs, **opts):
666 timer, fm = gettimer(ui, opts)
665 timer, fm = gettimer(ui, opts)
667 revrange = scmutil.revrange
666 revrange = scmutil.revrange
668 timer(lambda: len(revrange(repo, specs)))
667 timer(lambda: len(revrange(repo, specs)))
669 fm.end()
668 fm.end()
670
669
671 @command('perfnodelookup', formatteropts)
670 @command('perfnodelookup', formatteropts)
672 def perfnodelookup(ui, repo, rev, **opts):
671 def perfnodelookup(ui, repo, rev, **opts):
673 timer, fm = gettimer(ui, opts)
672 timer, fm = gettimer(ui, opts)
674 import mercurial.revlog
673 import mercurial.revlog
675 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
674 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
676 n = repo[rev].node()
675 n = repo[rev].node()
677 cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i")
676 cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i")
678 def d():
677 def d():
679 cl.rev(n)
678 cl.rev(n)
680 clearcaches(cl)
679 clearcaches(cl)
681 timer(d)
680 timer(d)
682 fm.end()
681 fm.end()
683
682
684 @command('perflog',
683 @command('perflog',
685 [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
684 [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
686 def perflog(ui, repo, rev=None, **opts):
685 def perflog(ui, repo, rev=None, **opts):
687 if rev is None:
686 if rev is None:
688 rev=[]
687 rev=[]
689 timer, fm = gettimer(ui, opts)
688 timer, fm = gettimer(ui, opts)
690 ui.pushbuffer()
689 ui.pushbuffer()
691 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
690 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
692 copies=opts.get('rename')))
691 copies=opts.get('rename')))
693 ui.popbuffer()
692 ui.popbuffer()
694 fm.end()
693 fm.end()
695
694
696 @command('perfmoonwalk', formatteropts)
695 @command('perfmoonwalk', formatteropts)
697 def perfmoonwalk(ui, repo, **opts):
696 def perfmoonwalk(ui, repo, **opts):
698 """benchmark walking the changelog backwards
697 """benchmark walking the changelog backwards
699
698
700 This also loads the changelog data for each revision in the changelog.
699 This also loads the changelog data for each revision in the changelog.
701 """
700 """
702 timer, fm = gettimer(ui, opts)
701 timer, fm = gettimer(ui, opts)
703 def moonwalk():
702 def moonwalk():
704 for i in xrange(len(repo), -1, -1):
703 for i in xrange(len(repo), -1, -1):
705 ctx = repo[i]
704 ctx = repo[i]
706 ctx.branch() # read changelog data (in addition to the index)
705 ctx.branch() # read changelog data (in addition to the index)
707 timer(moonwalk)
706 timer(moonwalk)
708 fm.end()
707 fm.end()
709
708
710 @command('perftemplating', formatteropts)
709 @command('perftemplating', formatteropts)
711 def perftemplating(ui, repo, rev=None, **opts):
710 def perftemplating(ui, repo, rev=None, **opts):
712 if rev is None:
711 if rev is None:
713 rev=[]
712 rev=[]
714 timer, fm = gettimer(ui, opts)
713 timer, fm = gettimer(ui, opts)
715 ui.pushbuffer()
714 ui.pushbuffer()
716 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
715 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
717 template='{date|shortdate} [{rev}:{node|short}]'
716 template='{date|shortdate} [{rev}:{node|short}]'
718 ' {author|person}: {desc|firstline}\n'))
717 ' {author|person}: {desc|firstline}\n'))
719 ui.popbuffer()
718 ui.popbuffer()
720 fm.end()
719 fm.end()
721
720
722 @command('perfcca', formatteropts)
721 @command('perfcca', formatteropts)
723 def perfcca(ui, repo, **opts):
722 def perfcca(ui, repo, **opts):
724 timer, fm = gettimer(ui, opts)
723 timer, fm = gettimer(ui, opts)
725 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
724 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
726 fm.end()
725 fm.end()
727
726
728 @command('perffncacheload', formatteropts)
727 @command('perffncacheload', formatteropts)
729 def perffncacheload(ui, repo, **opts):
728 def perffncacheload(ui, repo, **opts):
730 timer, fm = gettimer(ui, opts)
729 timer, fm = gettimer(ui, opts)
731 s = repo.store
730 s = repo.store
732 def d():
731 def d():
733 s.fncache._load()
732 s.fncache._load()
734 timer(d)
733 timer(d)
735 fm.end()
734 fm.end()
736
735
737 @command('perffncachewrite', formatteropts)
736 @command('perffncachewrite', formatteropts)
738 def perffncachewrite(ui, repo, **opts):
737 def perffncachewrite(ui, repo, **opts):
739 timer, fm = gettimer(ui, opts)
738 timer, fm = gettimer(ui, opts)
740 s = repo.store
739 s = repo.store
741 s.fncache._load()
740 s.fncache._load()
742 lock = repo.lock()
741 lock = repo.lock()
743 tr = repo.transaction('perffncachewrite')
742 tr = repo.transaction('perffncachewrite')
744 def d():
743 def d():
745 s.fncache._dirty = True
744 s.fncache._dirty = True
746 s.fncache.write(tr)
745 s.fncache.write(tr)
747 timer(d)
746 timer(d)
748 tr.close()
747 tr.close()
749 lock.release()
748 lock.release()
750 fm.end()
749 fm.end()
751
750
752 @command('perffncacheencode', formatteropts)
751 @command('perffncacheencode', formatteropts)
753 def perffncacheencode(ui, repo, **opts):
752 def perffncacheencode(ui, repo, **opts):
754 timer, fm = gettimer(ui, opts)
753 timer, fm = gettimer(ui, opts)
755 s = repo.store
754 s = repo.store
756 s.fncache._load()
755 s.fncache._load()
757 def d():
756 def d():
758 for p in s.fncache.entries:
757 for p in s.fncache.entries:
759 s.encode(p)
758 s.encode(p)
760 timer(d)
759 timer(d)
761 fm.end()
760 fm.end()
762
761
763 @command('perfbdiff', revlogopts + formatteropts + [
762 @command('perfbdiff', revlogopts + formatteropts + [
764 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
763 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
765 ('', 'alldata', False, 'test bdiffs for all associated revisions')],
764 ('', 'alldata', False, 'test bdiffs for all associated revisions')],
766 '-c|-m|FILE REV')
765 '-c|-m|FILE REV')
767 def perfbdiff(ui, repo, file_, rev=None, count=None, **opts):
766 def perfbdiff(ui, repo, file_, rev=None, count=None, **opts):
768 """benchmark a bdiff between revisions
767 """benchmark a bdiff between revisions
769
768
770 By default, benchmark a bdiff between its delta parent and itself.
769 By default, benchmark a bdiff between its delta parent and itself.
771
770
772 With ``--count``, benchmark bdiffs between delta parents and self for N
771 With ``--count``, benchmark bdiffs between delta parents and self for N
773 revisions starting at the specified revision.
772 revisions starting at the specified revision.
774
773
775 With ``--alldata``, assume the requested revision is a changeset and
774 With ``--alldata``, assume the requested revision is a changeset and
776 measure bdiffs for all changes related to that changeset (manifest
775 measure bdiffs for all changes related to that changeset (manifest
777 and filelogs).
776 and filelogs).
778 """
777 """
779 if opts['alldata']:
778 if opts['alldata']:
780 opts['changelog'] = True
779 opts['changelog'] = True
781
780
782 if opts.get('changelog') or opts.get('manifest'):
781 if opts.get('changelog') or opts.get('manifest'):
783 file_, rev = None, file_
782 file_, rev = None, file_
784 elif rev is None:
783 elif rev is None:
785 raise error.CommandError('perfbdiff', 'invalid arguments')
784 raise error.CommandError('perfbdiff', 'invalid arguments')
786
785
787 textpairs = []
786 textpairs = []
788
787
789 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
788 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
790
789
791 startrev = r.rev(r.lookup(rev))
790 startrev = r.rev(r.lookup(rev))
792 for rev in range(startrev, min(startrev + count, len(r) - 1)):
791 for rev in range(startrev, min(startrev + count, len(r) - 1)):
793 if opts['alldata']:
792 if opts['alldata']:
794 # Load revisions associated with changeset.
793 # Load revisions associated with changeset.
795 ctx = repo[rev]
794 ctx = repo[rev]
796 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
795 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
797 for pctx in ctx.parents():
796 for pctx in ctx.parents():
798 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
797 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
799 textpairs.append((pman, mtext))
798 textpairs.append((pman, mtext))
800
799
801 # Load filelog revisions by iterating manifest delta.
800 # Load filelog revisions by iterating manifest delta.
802 man = ctx.manifest()
801 man = ctx.manifest()
803 pman = ctx.p1().manifest()
802 pman = ctx.p1().manifest()
804 for filename, change in pman.diff(man).items():
803 for filename, change in pman.diff(man).items():
805 fctx = repo.file(filename)
804 fctx = repo.file(filename)
806 f1 = fctx.revision(change[0][0] or -1)
805 f1 = fctx.revision(change[0][0] or -1)
807 f2 = fctx.revision(change[1][0] or -1)
806 f2 = fctx.revision(change[1][0] or -1)
808 textpairs.append((f1, f2))
807 textpairs.append((f1, f2))
809 else:
808 else:
810 dp = r.deltaparent(rev)
809 dp = r.deltaparent(rev)
811 textpairs.append((r.revision(dp), r.revision(rev)))
810 textpairs.append((r.revision(dp), r.revision(rev)))
812
811
813 def d():
812 def d():
814 for pair in textpairs:
813 for pair in textpairs:
815 bdiff.bdiff(*pair)
814 mdiff.textdiff(*pair)
816
815
817 timer, fm = gettimer(ui, opts)
816 timer, fm = gettimer(ui, opts)
818 timer(d)
817 timer(d)
819 fm.end()
818 fm.end()
820
819
821 @command('perfdiffwd', formatteropts)
820 @command('perfdiffwd', formatteropts)
822 def perfdiffwd(ui, repo, **opts):
821 def perfdiffwd(ui, repo, **opts):
823 """Profile diff of working directory changes"""
822 """Profile diff of working directory changes"""
824 timer, fm = gettimer(ui, opts)
823 timer, fm = gettimer(ui, opts)
825 options = {
824 options = {
826 'w': 'ignore_all_space',
825 'w': 'ignore_all_space',
827 'b': 'ignore_space_change',
826 'b': 'ignore_space_change',
828 'B': 'ignore_blank_lines',
827 'B': 'ignore_blank_lines',
829 }
828 }
830
829
831 for diffopt in ('', 'w', 'b', 'B', 'wB'):
830 for diffopt in ('', 'w', 'b', 'B', 'wB'):
832 opts = dict((options[c], '1') for c in diffopt)
831 opts = dict((options[c], '1') for c in diffopt)
833 def d():
832 def d():
834 ui.pushbuffer()
833 ui.pushbuffer()
835 commands.diff(ui, repo, **opts)
834 commands.diff(ui, repo, **opts)
836 ui.popbuffer()
835 ui.popbuffer()
837 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
836 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
838 timer(d, title)
837 timer(d, title)
839 fm.end()
838 fm.end()
840
839
841 @command('perfrevlog', revlogopts + formatteropts +
840 @command('perfrevlog', revlogopts + formatteropts +
842 [('d', 'dist', 100, 'distance between the revisions'),
841 [('d', 'dist', 100, 'distance between the revisions'),
843 ('s', 'startrev', 0, 'revision to start reading at'),
842 ('s', 'startrev', 0, 'revision to start reading at'),
844 ('', 'reverse', False, 'read in reverse')],
843 ('', 'reverse', False, 'read in reverse')],
845 '-c|-m|FILE')
844 '-c|-m|FILE')
846 def perfrevlog(ui, repo, file_=None, startrev=0, reverse=False, **opts):
845 def perfrevlog(ui, repo, file_=None, startrev=0, reverse=False, **opts):
847 """Benchmark reading a series of revisions from a revlog.
846 """Benchmark reading a series of revisions from a revlog.
848
847
849 By default, we read every ``-d/--dist`` revision from 0 to tip of
848 By default, we read every ``-d/--dist`` revision from 0 to tip of
850 the specified revlog.
849 the specified revlog.
851
850
852 The start revision can be defined via ``-s/--startrev``.
851 The start revision can be defined via ``-s/--startrev``.
853 """
852 """
854 timer, fm = gettimer(ui, opts)
853 timer, fm = gettimer(ui, opts)
855 _len = getlen(ui)
854 _len = getlen(ui)
856
855
857 def d():
856 def d():
858 r = cmdutil.openrevlog(repo, 'perfrevlog', file_, opts)
857 r = cmdutil.openrevlog(repo, 'perfrevlog', file_, opts)
859
858
860 startrev = 0
859 startrev = 0
861 endrev = _len(r)
860 endrev = _len(r)
862 dist = opts['dist']
861 dist = opts['dist']
863
862
864 if reverse:
863 if reverse:
865 startrev, endrev = endrev, startrev
864 startrev, endrev = endrev, startrev
866 dist = -1 * dist
865 dist = -1 * dist
867
866
868 for x in xrange(startrev, endrev, dist):
867 for x in xrange(startrev, endrev, dist):
869 r.revision(r.node(x))
868 r.revision(r.node(x))
870
869
871 timer(d)
870 timer(d)
872 fm.end()
871 fm.end()
873
872
874 @command('perfrevlogchunks', revlogopts + formatteropts +
873 @command('perfrevlogchunks', revlogopts + formatteropts +
875 [('e', 'engines', '', 'compression engines to use'),
874 [('e', 'engines', '', 'compression engines to use'),
876 ('s', 'startrev', 0, 'revision to start at')],
875 ('s', 'startrev', 0, 'revision to start at')],
877 '-c|-m|FILE')
876 '-c|-m|FILE')
878 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
877 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
879 """Benchmark operations on revlog chunks.
878 """Benchmark operations on revlog chunks.
880
879
881 Logically, each revlog is a collection of fulltext revisions. However,
880 Logically, each revlog is a collection of fulltext revisions. However,
882 stored within each revlog are "chunks" of possibly compressed data. This
881 stored within each revlog are "chunks" of possibly compressed data. This
883 data needs to be read and decompressed or compressed and written.
882 data needs to be read and decompressed or compressed and written.
884
883
885 This command measures the time it takes to read+decompress and recompress
884 This command measures the time it takes to read+decompress and recompress
886 chunks in a revlog. It effectively isolates I/O and compression performance.
885 chunks in a revlog. It effectively isolates I/O and compression performance.
887 For measurements of higher-level operations like resolving revisions,
886 For measurements of higher-level operations like resolving revisions,
888 see ``perfrevlog`` and ``perfrevlogrevision``.
887 see ``perfrevlog`` and ``perfrevlogrevision``.
889 """
888 """
890 rl = cmdutil.openrevlog(repo, 'perfrevlogchunks', file_, opts)
889 rl = cmdutil.openrevlog(repo, 'perfrevlogchunks', file_, opts)
891
890
892 # Verify engines argument.
891 # Verify engines argument.
893 if engines:
892 if engines:
894 engines = set(e.strip() for e in engines.split(','))
893 engines = set(e.strip() for e in engines.split(','))
895 for engine in engines:
894 for engine in engines:
896 try:
895 try:
897 util.compressionengines[engine]
896 util.compressionengines[engine]
898 except KeyError:
897 except KeyError:
899 raise error.Abort('unknown compression engine: %s' % engine)
898 raise error.Abort('unknown compression engine: %s' % engine)
900 else:
899 else:
901 engines = []
900 engines = []
902 for e in util.compengines:
901 for e in util.compengines:
903 engine = util.compengines[e]
902 engine = util.compengines[e]
904 try:
903 try:
905 if engine.available():
904 if engine.available():
906 engine.revlogcompressor().compress('dummy')
905 engine.revlogcompressor().compress('dummy')
907 engines.append(e)
906 engines.append(e)
908 except NotImplementedError:
907 except NotImplementedError:
909 pass
908 pass
910
909
911 revs = list(rl.revs(startrev, len(rl) - 1))
910 revs = list(rl.revs(startrev, len(rl) - 1))
912
911
913 def rlfh(rl):
912 def rlfh(rl):
914 if rl._inline:
913 if rl._inline:
915 return getsvfs(repo)(rl.indexfile)
914 return getsvfs(repo)(rl.indexfile)
916 else:
915 else:
917 return getsvfs(repo)(rl.datafile)
916 return getsvfs(repo)(rl.datafile)
918
917
919 def doread():
918 def doread():
920 rl.clearcaches()
919 rl.clearcaches()
921 for rev in revs:
920 for rev in revs:
922 rl._chunkraw(rev, rev)
921 rl._chunkraw(rev, rev)
923
922
924 def doreadcachedfh():
923 def doreadcachedfh():
925 rl.clearcaches()
924 rl.clearcaches()
926 fh = rlfh(rl)
925 fh = rlfh(rl)
927 for rev in revs:
926 for rev in revs:
928 rl._chunkraw(rev, rev, df=fh)
927 rl._chunkraw(rev, rev, df=fh)
929
928
930 def doreadbatch():
929 def doreadbatch():
931 rl.clearcaches()
930 rl.clearcaches()
932 rl._chunkraw(revs[0], revs[-1])
931 rl._chunkraw(revs[0], revs[-1])
933
932
934 def doreadbatchcachedfh():
933 def doreadbatchcachedfh():
935 rl.clearcaches()
934 rl.clearcaches()
936 fh = rlfh(rl)
935 fh = rlfh(rl)
937 rl._chunkraw(revs[0], revs[-1], df=fh)
936 rl._chunkraw(revs[0], revs[-1], df=fh)
938
937
939 def dochunk():
938 def dochunk():
940 rl.clearcaches()
939 rl.clearcaches()
941 fh = rlfh(rl)
940 fh = rlfh(rl)
942 for rev in revs:
941 for rev in revs:
943 rl._chunk(rev, df=fh)
942 rl._chunk(rev, df=fh)
944
943
945 chunks = [None]
944 chunks = [None]
946
945
947 def dochunkbatch():
946 def dochunkbatch():
948 rl.clearcaches()
947 rl.clearcaches()
949 fh = rlfh(rl)
948 fh = rlfh(rl)
950 # Save chunks as a side-effect.
949 # Save chunks as a side-effect.
951 chunks[0] = rl._chunks(revs, df=fh)
950 chunks[0] = rl._chunks(revs, df=fh)
952
951
953 def docompress(compressor):
952 def docompress(compressor):
954 rl.clearcaches()
953 rl.clearcaches()
955
954
956 try:
955 try:
957 # Swap in the requested compression engine.
956 # Swap in the requested compression engine.
958 oldcompressor = rl._compressor
957 oldcompressor = rl._compressor
959 rl._compressor = compressor
958 rl._compressor = compressor
960 for chunk in chunks[0]:
959 for chunk in chunks[0]:
961 rl.compress(chunk)
960 rl.compress(chunk)
962 finally:
961 finally:
963 rl._compressor = oldcompressor
962 rl._compressor = oldcompressor
964
963
965 benches = [
964 benches = [
966 (lambda: doread(), 'read'),
965 (lambda: doread(), 'read'),
967 (lambda: doreadcachedfh(), 'read w/ reused fd'),
966 (lambda: doreadcachedfh(), 'read w/ reused fd'),
968 (lambda: doreadbatch(), 'read batch'),
967 (lambda: doreadbatch(), 'read batch'),
969 (lambda: doreadbatchcachedfh(), 'read batch w/ reused fd'),
968 (lambda: doreadbatchcachedfh(), 'read batch w/ reused fd'),
970 (lambda: dochunk(), 'chunk'),
969 (lambda: dochunk(), 'chunk'),
971 (lambda: dochunkbatch(), 'chunk batch'),
970 (lambda: dochunkbatch(), 'chunk batch'),
972 ]
971 ]
973
972
974 for engine in sorted(engines):
973 for engine in sorted(engines):
975 compressor = util.compengines[engine].revlogcompressor()
974 compressor = util.compengines[engine].revlogcompressor()
976 benches.append((functools.partial(docompress, compressor),
975 benches.append((functools.partial(docompress, compressor),
977 'compress w/ %s' % engine))
976 'compress w/ %s' % engine))
978
977
979 for fn, title in benches:
978 for fn, title in benches:
980 timer, fm = gettimer(ui, opts)
979 timer, fm = gettimer(ui, opts)
981 timer(fn, title=title)
980 timer(fn, title=title)
982 fm.end()
981 fm.end()
983
982
984 @command('perfrevlogrevision', revlogopts + formatteropts +
983 @command('perfrevlogrevision', revlogopts + formatteropts +
985 [('', 'cache', False, 'use caches instead of clearing')],
984 [('', 'cache', False, 'use caches instead of clearing')],
986 '-c|-m|FILE REV')
985 '-c|-m|FILE REV')
987 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
986 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
988 """Benchmark obtaining a revlog revision.
987 """Benchmark obtaining a revlog revision.
989
988
990 Obtaining a revlog revision consists of roughly the following steps:
989 Obtaining a revlog revision consists of roughly the following steps:
991
990
992 1. Compute the delta chain
991 1. Compute the delta chain
993 2. Obtain the raw chunks for that delta chain
992 2. Obtain the raw chunks for that delta chain
994 3. Decompress each raw chunk
993 3. Decompress each raw chunk
995 4. Apply binary patches to obtain fulltext
994 4. Apply binary patches to obtain fulltext
996 5. Verify hash of fulltext
995 5. Verify hash of fulltext
997
996
998 This command measures the time spent in each of these phases.
997 This command measures the time spent in each of these phases.
999 """
998 """
1000 if opts.get('changelog') or opts.get('manifest'):
999 if opts.get('changelog') or opts.get('manifest'):
1001 file_, rev = None, file_
1000 file_, rev = None, file_
1002 elif rev is None:
1001 elif rev is None:
1003 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
1002 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
1004
1003
1005 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
1004 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
1006 node = r.lookup(rev)
1005 node = r.lookup(rev)
1007 rev = r.rev(node)
1006 rev = r.rev(node)
1008
1007
1009 def getrawchunks(data, chain):
1008 def getrawchunks(data, chain):
1010 start = r.start
1009 start = r.start
1011 length = r.length
1010 length = r.length
1012 inline = r._inline
1011 inline = r._inline
1013 iosize = r._io.size
1012 iosize = r._io.size
1014 buffer = util.buffer
1013 buffer = util.buffer
1015 offset = start(chain[0])
1014 offset = start(chain[0])
1016
1015
1017 chunks = []
1016 chunks = []
1018 ladd = chunks.append
1017 ladd = chunks.append
1019
1018
1020 for rev in chain:
1019 for rev in chain:
1021 chunkstart = start(rev)
1020 chunkstart = start(rev)
1022 if inline:
1021 if inline:
1023 chunkstart += (rev + 1) * iosize
1022 chunkstart += (rev + 1) * iosize
1024 chunklength = length(rev)
1023 chunklength = length(rev)
1025 ladd(buffer(data, chunkstart - offset, chunklength))
1024 ladd(buffer(data, chunkstart - offset, chunklength))
1026
1025
1027 return chunks
1026 return chunks
1028
1027
1029 def dodeltachain(rev):
1028 def dodeltachain(rev):
1030 if not cache:
1029 if not cache:
1031 r.clearcaches()
1030 r.clearcaches()
1032 r._deltachain(rev)
1031 r._deltachain(rev)
1033
1032
1034 def doread(chain):
1033 def doread(chain):
1035 if not cache:
1034 if not cache:
1036 r.clearcaches()
1035 r.clearcaches()
1037 r._chunkraw(chain[0], chain[-1])
1036 r._chunkraw(chain[0], chain[-1])
1038
1037
1039 def dorawchunks(data, chain):
1038 def dorawchunks(data, chain):
1040 if not cache:
1039 if not cache:
1041 r.clearcaches()
1040 r.clearcaches()
1042 getrawchunks(data, chain)
1041 getrawchunks(data, chain)
1043
1042
1044 def dodecompress(chunks):
1043 def dodecompress(chunks):
1045 decomp = r.decompress
1044 decomp = r.decompress
1046 for chunk in chunks:
1045 for chunk in chunks:
1047 decomp(chunk)
1046 decomp(chunk)
1048
1047
1049 def dopatch(text, bins):
1048 def dopatch(text, bins):
1050 if not cache:
1049 if not cache:
1051 r.clearcaches()
1050 r.clearcaches()
1052 mdiff.patches(text, bins)
1051 mdiff.patches(text, bins)
1053
1052
1054 def dohash(text):
1053 def dohash(text):
1055 if not cache:
1054 if not cache:
1056 r.clearcaches()
1055 r.clearcaches()
1057 r.checkhash(text, node, rev=rev)
1056 r.checkhash(text, node, rev=rev)
1058
1057
1059 def dorevision():
1058 def dorevision():
1060 if not cache:
1059 if not cache:
1061 r.clearcaches()
1060 r.clearcaches()
1062 r.revision(node)
1061 r.revision(node)
1063
1062
1064 chain = r._deltachain(rev)[0]
1063 chain = r._deltachain(rev)[0]
1065 data = r._chunkraw(chain[0], chain[-1])[1]
1064 data = r._chunkraw(chain[0], chain[-1])[1]
1066 rawchunks = getrawchunks(data, chain)
1065 rawchunks = getrawchunks(data, chain)
1067 bins = r._chunks(chain)
1066 bins = r._chunks(chain)
1068 text = str(bins[0])
1067 text = str(bins[0])
1069 bins = bins[1:]
1068 bins = bins[1:]
1070 text = mdiff.patches(text, bins)
1069 text = mdiff.patches(text, bins)
1071
1070
1072 benches = [
1071 benches = [
1073 (lambda: dorevision(), 'full'),
1072 (lambda: dorevision(), 'full'),
1074 (lambda: dodeltachain(rev), 'deltachain'),
1073 (lambda: dodeltachain(rev), 'deltachain'),
1075 (lambda: doread(chain), 'read'),
1074 (lambda: doread(chain), 'read'),
1076 (lambda: dorawchunks(data, chain), 'rawchunks'),
1075 (lambda: dorawchunks(data, chain), 'rawchunks'),
1077 (lambda: dodecompress(rawchunks), 'decompress'),
1076 (lambda: dodecompress(rawchunks), 'decompress'),
1078 (lambda: dopatch(text, bins), 'patch'),
1077 (lambda: dopatch(text, bins), 'patch'),
1079 (lambda: dohash(text), 'hash'),
1078 (lambda: dohash(text), 'hash'),
1080 ]
1079 ]
1081
1080
1082 for fn, title in benches:
1081 for fn, title in benches:
1083 timer, fm = gettimer(ui, opts)
1082 timer, fm = gettimer(ui, opts)
1084 timer(fn, title=title)
1083 timer(fn, title=title)
1085 fm.end()
1084 fm.end()
1086
1085
1087 @command('perfrevset',
1086 @command('perfrevset',
1088 [('C', 'clear', False, 'clear volatile cache between each call.'),
1087 [('C', 'clear', False, 'clear volatile cache between each call.'),
1089 ('', 'contexts', False, 'obtain changectx for each revision')]
1088 ('', 'contexts', False, 'obtain changectx for each revision')]
1090 + formatteropts, "REVSET")
1089 + formatteropts, "REVSET")
1091 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
1090 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
1092 """benchmark the execution time of a revset
1091 """benchmark the execution time of a revset
1093
1092
1094 Use the --clean option if need to evaluate the impact of build volatile
1093 Use the --clean option if need to evaluate the impact of build volatile
1095 revisions set cache on the revset execution. Volatile cache hold filtered
1094 revisions set cache on the revset execution. Volatile cache hold filtered
1096 and obsolete related cache."""
1095 and obsolete related cache."""
1097 timer, fm = gettimer(ui, opts)
1096 timer, fm = gettimer(ui, opts)
1098 def d():
1097 def d():
1099 if clear:
1098 if clear:
1100 repo.invalidatevolatilesets()
1099 repo.invalidatevolatilesets()
1101 if contexts:
1100 if contexts:
1102 for ctx in repo.set(expr): pass
1101 for ctx in repo.set(expr): pass
1103 else:
1102 else:
1104 for r in repo.revs(expr): pass
1103 for r in repo.revs(expr): pass
1105 timer(d)
1104 timer(d)
1106 fm.end()
1105 fm.end()
1107
1106
1108 @command('perfvolatilesets', formatteropts)
1107 @command('perfvolatilesets', formatteropts)
1109 def perfvolatilesets(ui, repo, *names, **opts):
1108 def perfvolatilesets(ui, repo, *names, **opts):
1110 """benchmark the computation of various volatile set
1109 """benchmark the computation of various volatile set
1111
1110
1112 Volatile set computes element related to filtering and obsolescence."""
1111 Volatile set computes element related to filtering and obsolescence."""
1113 timer, fm = gettimer(ui, opts)
1112 timer, fm = gettimer(ui, opts)
1114 repo = repo.unfiltered()
1113 repo = repo.unfiltered()
1115
1114
1116 def getobs(name):
1115 def getobs(name):
1117 def d():
1116 def d():
1118 repo.invalidatevolatilesets()
1117 repo.invalidatevolatilesets()
1119 obsolete.getrevs(repo, name)
1118 obsolete.getrevs(repo, name)
1120 return d
1119 return d
1121
1120
1122 allobs = sorted(obsolete.cachefuncs)
1121 allobs = sorted(obsolete.cachefuncs)
1123 if names:
1122 if names:
1124 allobs = [n for n in allobs if n in names]
1123 allobs = [n for n in allobs if n in names]
1125
1124
1126 for name in allobs:
1125 for name in allobs:
1127 timer(getobs(name), title=name)
1126 timer(getobs(name), title=name)
1128
1127
1129 def getfiltered(name):
1128 def getfiltered(name):
1130 def d():
1129 def d():
1131 repo.invalidatevolatilesets()
1130 repo.invalidatevolatilesets()
1132 repoview.filterrevs(repo, name)
1131 repoview.filterrevs(repo, name)
1133 return d
1132 return d
1134
1133
1135 allfilter = sorted(repoview.filtertable)
1134 allfilter = sorted(repoview.filtertable)
1136 if names:
1135 if names:
1137 allfilter = [n for n in allfilter if n in names]
1136 allfilter = [n for n in allfilter if n in names]
1138
1137
1139 for name in allfilter:
1138 for name in allfilter:
1140 timer(getfiltered(name), title=name)
1139 timer(getfiltered(name), title=name)
1141 fm.end()
1140 fm.end()
1142
1141
1143 @command('perfbranchmap',
1142 @command('perfbranchmap',
1144 [('f', 'full', False,
1143 [('f', 'full', False,
1145 'Includes build time of subset'),
1144 'Includes build time of subset'),
1146 ] + formatteropts)
1145 ] + formatteropts)
1147 def perfbranchmap(ui, repo, full=False, **opts):
1146 def perfbranchmap(ui, repo, full=False, **opts):
1148 """benchmark the update of a branchmap
1147 """benchmark the update of a branchmap
1149
1148
1150 This benchmarks the full repo.branchmap() call with read and write disabled
1149 This benchmarks the full repo.branchmap() call with read and write disabled
1151 """
1150 """
1152 timer, fm = gettimer(ui, opts)
1151 timer, fm = gettimer(ui, opts)
1153 def getbranchmap(filtername):
1152 def getbranchmap(filtername):
1154 """generate a benchmark function for the filtername"""
1153 """generate a benchmark function for the filtername"""
1155 if filtername is None:
1154 if filtername is None:
1156 view = repo
1155 view = repo
1157 else:
1156 else:
1158 view = repo.filtered(filtername)
1157 view = repo.filtered(filtername)
1159 def d():
1158 def d():
1160 if full:
1159 if full:
1161 view._branchcaches.clear()
1160 view._branchcaches.clear()
1162 else:
1161 else:
1163 view._branchcaches.pop(filtername, None)
1162 view._branchcaches.pop(filtername, None)
1164 view.branchmap()
1163 view.branchmap()
1165 return d
1164 return d
1166 # add filter in smaller subset to bigger subset
1165 # add filter in smaller subset to bigger subset
1167 possiblefilters = set(repoview.filtertable)
1166 possiblefilters = set(repoview.filtertable)
1168 subsettable = getbranchmapsubsettable()
1167 subsettable = getbranchmapsubsettable()
1169 allfilters = []
1168 allfilters = []
1170 while possiblefilters:
1169 while possiblefilters:
1171 for name in possiblefilters:
1170 for name in possiblefilters:
1172 subset = subsettable.get(name)
1171 subset = subsettable.get(name)
1173 if subset not in possiblefilters:
1172 if subset not in possiblefilters:
1174 break
1173 break
1175 else:
1174 else:
1176 assert False, 'subset cycle %s!' % possiblefilters
1175 assert False, 'subset cycle %s!' % possiblefilters
1177 allfilters.append(name)
1176 allfilters.append(name)
1178 possiblefilters.remove(name)
1177 possiblefilters.remove(name)
1179
1178
1180 # warm the cache
1179 # warm the cache
1181 if not full:
1180 if not full:
1182 for name in allfilters:
1181 for name in allfilters:
1183 repo.filtered(name).branchmap()
1182 repo.filtered(name).branchmap()
1184 # add unfiltered
1183 # add unfiltered
1185 allfilters.append(None)
1184 allfilters.append(None)
1186
1185
1187 branchcacheread = safeattrsetter(branchmap, 'read')
1186 branchcacheread = safeattrsetter(branchmap, 'read')
1188 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1187 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1189 branchcacheread.set(lambda repo: None)
1188 branchcacheread.set(lambda repo: None)
1190 branchcachewrite.set(lambda bc, repo: None)
1189 branchcachewrite.set(lambda bc, repo: None)
1191 try:
1190 try:
1192 for name in allfilters:
1191 for name in allfilters:
1193 timer(getbranchmap(name), title=str(name))
1192 timer(getbranchmap(name), title=str(name))
1194 finally:
1193 finally:
1195 branchcacheread.restore()
1194 branchcacheread.restore()
1196 branchcachewrite.restore()
1195 branchcachewrite.restore()
1197 fm.end()
1196 fm.end()
1198
1197
1199 @command('perfloadmarkers')
1198 @command('perfloadmarkers')
1200 def perfloadmarkers(ui, repo):
1199 def perfloadmarkers(ui, repo):
1201 """benchmark the time to parse the on-disk markers for a repo
1200 """benchmark the time to parse the on-disk markers for a repo
1202
1201
1203 Result is the number of markers in the repo."""
1202 Result is the number of markers in the repo."""
1204 timer, fm = gettimer(ui)
1203 timer, fm = gettimer(ui)
1205 svfs = getsvfs(repo)
1204 svfs = getsvfs(repo)
1206 timer(lambda: len(obsolete.obsstore(svfs)))
1205 timer(lambda: len(obsolete.obsstore(svfs)))
1207 fm.end()
1206 fm.end()
1208
1207
1209 @command('perflrucachedict', formatteropts +
1208 @command('perflrucachedict', formatteropts +
1210 [('', 'size', 4, 'size of cache'),
1209 [('', 'size', 4, 'size of cache'),
1211 ('', 'gets', 10000, 'number of key lookups'),
1210 ('', 'gets', 10000, 'number of key lookups'),
1212 ('', 'sets', 10000, 'number of key sets'),
1211 ('', 'sets', 10000, 'number of key sets'),
1213 ('', 'mixed', 10000, 'number of mixed mode operations'),
1212 ('', 'mixed', 10000, 'number of mixed mode operations'),
1214 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1213 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1215 norepo=True)
1214 norepo=True)
1216 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1215 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1217 mixedgetfreq=50, **opts):
1216 mixedgetfreq=50, **opts):
1218 def doinit():
1217 def doinit():
1219 for i in xrange(10000):
1218 for i in xrange(10000):
1220 util.lrucachedict(size)
1219 util.lrucachedict(size)
1221
1220
1222 values = []
1221 values = []
1223 for i in xrange(size):
1222 for i in xrange(size):
1224 values.append(random.randint(0, sys.maxint))
1223 values.append(random.randint(0, sys.maxint))
1225
1224
1226 # Get mode fills the cache and tests raw lookup performance with no
1225 # Get mode fills the cache and tests raw lookup performance with no
1227 # eviction.
1226 # eviction.
1228 getseq = []
1227 getseq = []
1229 for i in xrange(gets):
1228 for i in xrange(gets):
1230 getseq.append(random.choice(values))
1229 getseq.append(random.choice(values))
1231
1230
1232 def dogets():
1231 def dogets():
1233 d = util.lrucachedict(size)
1232 d = util.lrucachedict(size)
1234 for v in values:
1233 for v in values:
1235 d[v] = v
1234 d[v] = v
1236 for key in getseq:
1235 for key in getseq:
1237 value = d[key]
1236 value = d[key]
1238 value # silence pyflakes warning
1237 value # silence pyflakes warning
1239
1238
1240 # Set mode tests insertion speed with cache eviction.
1239 # Set mode tests insertion speed with cache eviction.
1241 setseq = []
1240 setseq = []
1242 for i in xrange(sets):
1241 for i in xrange(sets):
1243 setseq.append(random.randint(0, sys.maxint))
1242 setseq.append(random.randint(0, sys.maxint))
1244
1243
1245 def dosets():
1244 def dosets():
1246 d = util.lrucachedict(size)
1245 d = util.lrucachedict(size)
1247 for v in setseq:
1246 for v in setseq:
1248 d[v] = v
1247 d[v] = v
1249
1248
1250 # Mixed mode randomly performs gets and sets with eviction.
1249 # Mixed mode randomly performs gets and sets with eviction.
1251 mixedops = []
1250 mixedops = []
1252 for i in xrange(mixed):
1251 for i in xrange(mixed):
1253 r = random.randint(0, 100)
1252 r = random.randint(0, 100)
1254 if r < mixedgetfreq:
1253 if r < mixedgetfreq:
1255 op = 0
1254 op = 0
1256 else:
1255 else:
1257 op = 1
1256 op = 1
1258
1257
1259 mixedops.append((op, random.randint(0, size * 2)))
1258 mixedops.append((op, random.randint(0, size * 2)))
1260
1259
1261 def domixed():
1260 def domixed():
1262 d = util.lrucachedict(size)
1261 d = util.lrucachedict(size)
1263
1262
1264 for op, v in mixedops:
1263 for op, v in mixedops:
1265 if op == 0:
1264 if op == 0:
1266 try:
1265 try:
1267 d[v]
1266 d[v]
1268 except KeyError:
1267 except KeyError:
1269 pass
1268 pass
1270 else:
1269 else:
1271 d[v] = v
1270 d[v] = v
1272
1271
1273 benches = [
1272 benches = [
1274 (doinit, 'init'),
1273 (doinit, 'init'),
1275 (dogets, 'gets'),
1274 (dogets, 'gets'),
1276 (dosets, 'sets'),
1275 (dosets, 'sets'),
1277 (domixed, 'mixed')
1276 (domixed, 'mixed')
1278 ]
1277 ]
1279
1278
1280 for fn, title in benches:
1279 for fn, title in benches:
1281 timer, fm = gettimer(ui, opts)
1280 timer, fm = gettimer(ui, opts)
1282 timer(fn, title=title)
1281 timer(fn, title=title)
1283 fm.end()
1282 fm.end()
1284
1283
1285 @command('perfwrite', formatteropts)
1284 @command('perfwrite', formatteropts)
1286 def perfwrite(ui, repo, **opts):
1285 def perfwrite(ui, repo, **opts):
1287 """microbenchmark ui.write
1286 """microbenchmark ui.write
1288 """
1287 """
1289 timer, fm = gettimer(ui, opts)
1288 timer, fm = gettimer(ui, opts)
1290 def write():
1289 def write():
1291 for i in range(100000):
1290 for i in range(100000):
1292 ui.write(('Testing write performance\n'))
1291 ui.write(('Testing write performance\n'))
1293 timer(write)
1292 timer(write)
1294 fm.end()
1293 fm.end()
1295
1294
1296 def uisetup(ui):
1295 def uisetup(ui):
1297 if (util.safehasattr(cmdutil, 'openrevlog') and
1296 if (util.safehasattr(cmdutil, 'openrevlog') and
1298 not util.safehasattr(commands, 'debugrevlogopts')):
1297 not util.safehasattr(commands, 'debugrevlogopts')):
1299 # for "historical portability":
1298 # for "historical portability":
1300 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1299 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1301 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1300 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1302 # openrevlog() should cause failure, because it has been
1301 # openrevlog() should cause failure, because it has been
1303 # available since 3.5 (or 49c583ca48c4).
1302 # available since 3.5 (or 49c583ca48c4).
1304 def openrevlog(orig, repo, cmd, file_, opts):
1303 def openrevlog(orig, repo, cmd, file_, opts):
1305 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1304 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1306 raise error.Abort("This version doesn't support --dir option",
1305 raise error.Abort("This version doesn't support --dir option",
1307 hint="use 3.5 or later")
1306 hint="use 3.5 or later")
1308 return orig(repo, cmd, file_, opts)
1307 return orig(repo, cmd, file_, opts)
1309 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
1308 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
@@ -1,483 +1,485
1 # mdiff.py - diff and patch routines for mercurial
1 # mdiff.py - diff and patch routines for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
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
8 from __future__ import absolute_import
9
9
10 import re
10 import re
11 import struct
11 import struct
12 import zlib
12 import zlib
13
13
14 from .i18n import _
14 from .i18n import _
15 from . import (
15 from . import (
16 bdiff,
16 bdiff,
17 error,
17 error,
18 mpatch,
18 mpatch,
19 pycompat,
19 pycompat,
20 util,
20 util,
21 )
21 )
22
22
23 blocks = bdiff.blocks
24 fixws = bdiff.fixws
23 patches = mpatch.patches
25 patches = mpatch.patches
24 patchedsize = mpatch.patchedsize
26 patchedsize = mpatch.patchedsize
25 textdiff = bdiff.bdiff
27 textdiff = bdiff.bdiff
26
28
27 def splitnewlines(text):
29 def splitnewlines(text):
28 '''like str.splitlines, but only split on newlines.'''
30 '''like str.splitlines, but only split on newlines.'''
29 lines = [l + '\n' for l in text.split('\n')]
31 lines = [l + '\n' for l in text.split('\n')]
30 if lines:
32 if lines:
31 if lines[-1] == '\n':
33 if lines[-1] == '\n':
32 lines.pop()
34 lines.pop()
33 else:
35 else:
34 lines[-1] = lines[-1][:-1]
36 lines[-1] = lines[-1][:-1]
35 return lines
37 return lines
36
38
37 class diffopts(object):
39 class diffopts(object):
38 '''context is the number of context lines
40 '''context is the number of context lines
39 text treats all files as text
41 text treats all files as text
40 showfunc enables diff -p output
42 showfunc enables diff -p output
41 git enables the git extended patch format
43 git enables the git extended patch format
42 nodates removes dates from diff headers
44 nodates removes dates from diff headers
43 nobinary ignores binary files
45 nobinary ignores binary files
44 noprefix disables the 'a/' and 'b/' prefixes (ignored in plain mode)
46 noprefix disables the 'a/' and 'b/' prefixes (ignored in plain mode)
45 ignorews ignores all whitespace changes in the diff
47 ignorews ignores all whitespace changes in the diff
46 ignorewsamount ignores changes in the amount of whitespace
48 ignorewsamount ignores changes in the amount of whitespace
47 ignoreblanklines ignores changes whose lines are all blank
49 ignoreblanklines ignores changes whose lines are all blank
48 upgrade generates git diffs to avoid data loss
50 upgrade generates git diffs to avoid data loss
49 '''
51 '''
50
52
51 defaults = {
53 defaults = {
52 'context': 3,
54 'context': 3,
53 'text': False,
55 'text': False,
54 'showfunc': False,
56 'showfunc': False,
55 'git': False,
57 'git': False,
56 'nodates': False,
58 'nodates': False,
57 'nobinary': False,
59 'nobinary': False,
58 'noprefix': False,
60 'noprefix': False,
59 'index': 0,
61 'index': 0,
60 'ignorews': False,
62 'ignorews': False,
61 'ignorewsamount': False,
63 'ignorewsamount': False,
62 'ignoreblanklines': False,
64 'ignoreblanklines': False,
63 'upgrade': False,
65 'upgrade': False,
64 'showsimilarity': False,
66 'showsimilarity': False,
65 }
67 }
66
68
67 def __init__(self, **opts):
69 def __init__(self, **opts):
68 opts = pycompat.byteskwargs(opts)
70 opts = pycompat.byteskwargs(opts)
69 for k in self.defaults.keys():
71 for k in self.defaults.keys():
70 v = opts.get(k)
72 v = opts.get(k)
71 if v is None:
73 if v is None:
72 v = self.defaults[k]
74 v = self.defaults[k]
73 setattr(self, k, v)
75 setattr(self, k, v)
74
76
75 try:
77 try:
76 self.context = int(self.context)
78 self.context = int(self.context)
77 except ValueError:
79 except ValueError:
78 raise error.Abort(_('diff context lines count must be '
80 raise error.Abort(_('diff context lines count must be '
79 'an integer, not %r') % self.context)
81 'an integer, not %r') % self.context)
80
82
81 def copy(self, **kwargs):
83 def copy(self, **kwargs):
82 opts = dict((k, getattr(self, k)) for k in self.defaults)
84 opts = dict((k, getattr(self, k)) for k in self.defaults)
83 opts.update(kwargs)
85 opts.update(kwargs)
84 return diffopts(**opts)
86 return diffopts(**opts)
85
87
86 defaultopts = diffopts()
88 defaultopts = diffopts()
87
89
88 def wsclean(opts, text, blank=True):
90 def wsclean(opts, text, blank=True):
89 if opts.ignorews:
91 if opts.ignorews:
90 text = bdiff.fixws(text, 1)
92 text = bdiff.fixws(text, 1)
91 elif opts.ignorewsamount:
93 elif opts.ignorewsamount:
92 text = bdiff.fixws(text, 0)
94 text = bdiff.fixws(text, 0)
93 if blank and opts.ignoreblanklines:
95 if blank and opts.ignoreblanklines:
94 text = re.sub('\n+', '\n', text).strip('\n')
96 text = re.sub('\n+', '\n', text).strip('\n')
95 return text
97 return text
96
98
97 def splitblock(base1, lines1, base2, lines2, opts):
99 def splitblock(base1, lines1, base2, lines2, opts):
98 # The input lines matches except for interwoven blank lines. We
100 # The input lines matches except for interwoven blank lines. We
99 # transform it into a sequence of matching blocks and blank blocks.
101 # transform it into a sequence of matching blocks and blank blocks.
100 lines1 = [(wsclean(opts, l) and 1 or 0) for l in lines1]
102 lines1 = [(wsclean(opts, l) and 1 or 0) for l in lines1]
101 lines2 = [(wsclean(opts, l) and 1 or 0) for l in lines2]
103 lines2 = [(wsclean(opts, l) and 1 or 0) for l in lines2]
102 s1, e1 = 0, len(lines1)
104 s1, e1 = 0, len(lines1)
103 s2, e2 = 0, len(lines2)
105 s2, e2 = 0, len(lines2)
104 while s1 < e1 or s2 < e2:
106 while s1 < e1 or s2 < e2:
105 i1, i2, btype = s1, s2, '='
107 i1, i2, btype = s1, s2, '='
106 if (i1 >= e1 or lines1[i1] == 0
108 if (i1 >= e1 or lines1[i1] == 0
107 or i2 >= e2 or lines2[i2] == 0):
109 or i2 >= e2 or lines2[i2] == 0):
108 # Consume the block of blank lines
110 # Consume the block of blank lines
109 btype = '~'
111 btype = '~'
110 while i1 < e1 and lines1[i1] == 0:
112 while i1 < e1 and lines1[i1] == 0:
111 i1 += 1
113 i1 += 1
112 while i2 < e2 and lines2[i2] == 0:
114 while i2 < e2 and lines2[i2] == 0:
113 i2 += 1
115 i2 += 1
114 else:
116 else:
115 # Consume the matching lines
117 # Consume the matching lines
116 while i1 < e1 and lines1[i1] == 1 and lines2[i2] == 1:
118 while i1 < e1 and lines1[i1] == 1 and lines2[i2] == 1:
117 i1 += 1
119 i1 += 1
118 i2 += 1
120 i2 += 1
119 yield [base1 + s1, base1 + i1, base2 + s2, base2 + i2], btype
121 yield [base1 + s1, base1 + i1, base2 + s2, base2 + i2], btype
120 s1 = i1
122 s1 = i1
121 s2 = i2
123 s2 = i2
122
124
123 def hunkinrange(hunk, linerange):
125 def hunkinrange(hunk, linerange):
124 """Return True if `hunk` defined as (start, length) is in `linerange`
126 """Return True if `hunk` defined as (start, length) is in `linerange`
125 defined as (lowerbound, upperbound).
127 defined as (lowerbound, upperbound).
126
128
127 >>> hunkinrange((5, 10), (2, 7))
129 >>> hunkinrange((5, 10), (2, 7))
128 True
130 True
129 >>> hunkinrange((5, 10), (6, 12))
131 >>> hunkinrange((5, 10), (6, 12))
130 True
132 True
131 >>> hunkinrange((5, 10), (13, 17))
133 >>> hunkinrange((5, 10), (13, 17))
132 True
134 True
133 >>> hunkinrange((5, 10), (3, 17))
135 >>> hunkinrange((5, 10), (3, 17))
134 True
136 True
135 >>> hunkinrange((5, 10), (1, 3))
137 >>> hunkinrange((5, 10), (1, 3))
136 False
138 False
137 >>> hunkinrange((5, 10), (18, 20))
139 >>> hunkinrange((5, 10), (18, 20))
138 False
140 False
139 >>> hunkinrange((5, 10), (1, 5))
141 >>> hunkinrange((5, 10), (1, 5))
140 False
142 False
141 >>> hunkinrange((5, 10), (15, 27))
143 >>> hunkinrange((5, 10), (15, 27))
142 False
144 False
143 """
145 """
144 start, length = hunk
146 start, length = hunk
145 lowerbound, upperbound = linerange
147 lowerbound, upperbound = linerange
146 return lowerbound < start + length and start < upperbound
148 return lowerbound < start + length and start < upperbound
147
149
148 def blocksinrange(blocks, rangeb):
150 def blocksinrange(blocks, rangeb):
149 """filter `blocks` like (a1, a2, b1, b2) from items outside line range
151 """filter `blocks` like (a1, a2, b1, b2) from items outside line range
150 `rangeb` from ``(b1, b2)`` point of view.
152 `rangeb` from ``(b1, b2)`` point of view.
151
153
152 Return `filteredblocks, rangea` where:
154 Return `filteredblocks, rangea` where:
153
155
154 * `filteredblocks` is list of ``block = (a1, a2, b1, b2), stype`` items of
156 * `filteredblocks` is list of ``block = (a1, a2, b1, b2), stype`` items of
155 `blocks` that are inside `rangeb` from ``(b1, b2)`` point of view; a
157 `blocks` that are inside `rangeb` from ``(b1, b2)`` point of view; a
156 block ``(b1, b2)`` being inside `rangeb` if
158 block ``(b1, b2)`` being inside `rangeb` if
157 ``rangeb[0] < b2 and b1 < rangeb[1]``;
159 ``rangeb[0] < b2 and b1 < rangeb[1]``;
158 * `rangea` is the line range w.r.t. to ``(a1, a2)`` parts of `blocks`.
160 * `rangea` is the line range w.r.t. to ``(a1, a2)`` parts of `blocks`.
159 """
161 """
160 lbb, ubb = rangeb
162 lbb, ubb = rangeb
161 lba, uba = None, None
163 lba, uba = None, None
162 filteredblocks = []
164 filteredblocks = []
163 for block in blocks:
165 for block in blocks:
164 (a1, a2, b1, b2), stype = block
166 (a1, a2, b1, b2), stype = block
165 if lbb >= b1 and ubb <= b2 and stype == '=':
167 if lbb >= b1 and ubb <= b2 and stype == '=':
166 # rangeb is within a single "=" hunk, restrict back linerange1
168 # rangeb is within a single "=" hunk, restrict back linerange1
167 # by offsetting rangeb
169 # by offsetting rangeb
168 lba = lbb - b1 + a1
170 lba = lbb - b1 + a1
169 uba = ubb - b1 + a1
171 uba = ubb - b1 + a1
170 else:
172 else:
171 if b1 <= lbb < b2:
173 if b1 <= lbb < b2:
172 if stype == '=':
174 if stype == '=':
173 lba = a2 - (b2 - lbb)
175 lba = a2 - (b2 - lbb)
174 else:
176 else:
175 lba = a1
177 lba = a1
176 if b1 < ubb <= b2:
178 if b1 < ubb <= b2:
177 if stype == '=':
179 if stype == '=':
178 uba = a1 + (ubb - b1)
180 uba = a1 + (ubb - b1)
179 else:
181 else:
180 uba = a2
182 uba = a2
181 if hunkinrange((b1, (b2 - b1)), rangeb):
183 if hunkinrange((b1, (b2 - b1)), rangeb):
182 filteredblocks.append(block)
184 filteredblocks.append(block)
183 if lba is None or uba is None or uba < lba:
185 if lba is None or uba is None or uba < lba:
184 raise error.Abort(_('line range exceeds file size'))
186 raise error.Abort(_('line range exceeds file size'))
185 return filteredblocks, (lba, uba)
187 return filteredblocks, (lba, uba)
186
188
187 def allblocks(text1, text2, opts=None, lines1=None, lines2=None):
189 def allblocks(text1, text2, opts=None, lines1=None, lines2=None):
188 """Return (block, type) tuples, where block is an mdiff.blocks
190 """Return (block, type) tuples, where block is an mdiff.blocks
189 line entry. type is '=' for blocks matching exactly one another
191 line entry. type is '=' for blocks matching exactly one another
190 (bdiff blocks), '!' for non-matching blocks and '~' for blocks
192 (bdiff blocks), '!' for non-matching blocks and '~' for blocks
191 matching only after having filtered blank lines.
193 matching only after having filtered blank lines.
192 line1 and line2 are text1 and text2 split with splitnewlines() if
194 line1 and line2 are text1 and text2 split with splitnewlines() if
193 they are already available.
195 they are already available.
194 """
196 """
195 if opts is None:
197 if opts is None:
196 opts = defaultopts
198 opts = defaultopts
197 if opts.ignorews or opts.ignorewsamount:
199 if opts.ignorews or opts.ignorewsamount:
198 text1 = wsclean(opts, text1, False)
200 text1 = wsclean(opts, text1, False)
199 text2 = wsclean(opts, text2, False)
201 text2 = wsclean(opts, text2, False)
200 diff = bdiff.blocks(text1, text2)
202 diff = bdiff.blocks(text1, text2)
201 for i, s1 in enumerate(diff):
203 for i, s1 in enumerate(diff):
202 # The first match is special.
204 # The first match is special.
203 # we've either found a match starting at line 0 or a match later
205 # we've either found a match starting at line 0 or a match later
204 # in the file. If it starts later, old and new below will both be
206 # in the file. If it starts later, old and new below will both be
205 # empty and we'll continue to the next match.
207 # empty and we'll continue to the next match.
206 if i > 0:
208 if i > 0:
207 s = diff[i - 1]
209 s = diff[i - 1]
208 else:
210 else:
209 s = [0, 0, 0, 0]
211 s = [0, 0, 0, 0]
210 s = [s[1], s1[0], s[3], s1[2]]
212 s = [s[1], s1[0], s[3], s1[2]]
211
213
212 # bdiff sometimes gives huge matches past eof, this check eats them,
214 # bdiff sometimes gives huge matches past eof, this check eats them,
213 # and deals with the special first match case described above
215 # and deals with the special first match case described above
214 if s[0] != s[1] or s[2] != s[3]:
216 if s[0] != s[1] or s[2] != s[3]:
215 type = '!'
217 type = '!'
216 if opts.ignoreblanklines:
218 if opts.ignoreblanklines:
217 if lines1 is None:
219 if lines1 is None:
218 lines1 = splitnewlines(text1)
220 lines1 = splitnewlines(text1)
219 if lines2 is None:
221 if lines2 is None:
220 lines2 = splitnewlines(text2)
222 lines2 = splitnewlines(text2)
221 old = wsclean(opts, "".join(lines1[s[0]:s[1]]))
223 old = wsclean(opts, "".join(lines1[s[0]:s[1]]))
222 new = wsclean(opts, "".join(lines2[s[2]:s[3]]))
224 new = wsclean(opts, "".join(lines2[s[2]:s[3]]))
223 if old == new:
225 if old == new:
224 type = '~'
226 type = '~'
225 yield s, type
227 yield s, type
226 yield s1, '='
228 yield s1, '='
227
229
228 def unidiff(a, ad, b, bd, fn1, fn2, opts=defaultopts):
230 def unidiff(a, ad, b, bd, fn1, fn2, opts=defaultopts):
229 """Return a unified diff as a (headers, hunks) tuple.
231 """Return a unified diff as a (headers, hunks) tuple.
230
232
231 If the diff is not null, `headers` is a list with unified diff header
233 If the diff is not null, `headers` is a list with unified diff header
232 lines "--- <original>" and "+++ <new>" and `hunks` is a generator yielding
234 lines "--- <original>" and "+++ <new>" and `hunks` is a generator yielding
233 (hunkrange, hunklines) coming from _unidiff().
235 (hunkrange, hunklines) coming from _unidiff().
234 Otherwise, `headers` and `hunks` are empty.
236 Otherwise, `headers` and `hunks` are empty.
235 """
237 """
236 def datetag(date, fn=None):
238 def datetag(date, fn=None):
237 if not opts.git and not opts.nodates:
239 if not opts.git and not opts.nodates:
238 return '\t%s' % date
240 return '\t%s' % date
239 if fn and ' ' in fn:
241 if fn and ' ' in fn:
240 return '\t'
242 return '\t'
241 return ''
243 return ''
242
244
243 sentinel = [], ()
245 sentinel = [], ()
244 if not a and not b:
246 if not a and not b:
245 return sentinel
247 return sentinel
246
248
247 if opts.noprefix:
249 if opts.noprefix:
248 aprefix = bprefix = ''
250 aprefix = bprefix = ''
249 else:
251 else:
250 aprefix = 'a/'
252 aprefix = 'a/'
251 bprefix = 'b/'
253 bprefix = 'b/'
252
254
253 epoch = util.datestr((0, 0))
255 epoch = util.datestr((0, 0))
254
256
255 fn1 = util.pconvert(fn1)
257 fn1 = util.pconvert(fn1)
256 fn2 = util.pconvert(fn2)
258 fn2 = util.pconvert(fn2)
257
259
258 def checknonewline(lines):
260 def checknonewline(lines):
259 for text in lines:
261 for text in lines:
260 if text[-1:] != '\n':
262 if text[-1:] != '\n':
261 text += "\n\ No newline at end of file\n"
263 text += "\n\ No newline at end of file\n"
262 yield text
264 yield text
263
265
264 if not opts.text and (util.binary(a) or util.binary(b)):
266 if not opts.text and (util.binary(a) or util.binary(b)):
265 if a and b and len(a) == len(b) and a == b:
267 if a and b and len(a) == len(b) and a == b:
266 return sentinel
268 return sentinel
267 headerlines = []
269 headerlines = []
268 hunks = (None, ['Binary file %s has changed\n' % fn1]),
270 hunks = (None, ['Binary file %s has changed\n' % fn1]),
269 elif not a:
271 elif not a:
270 b = splitnewlines(b)
272 b = splitnewlines(b)
271 if a is None:
273 if a is None:
272 l1 = '--- /dev/null%s' % datetag(epoch)
274 l1 = '--- /dev/null%s' % datetag(epoch)
273 else:
275 else:
274 l1 = "--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1))
276 l1 = "--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1))
275 l2 = "+++ %s%s" % (bprefix + fn2, datetag(bd, fn2))
277 l2 = "+++ %s%s" % (bprefix + fn2, datetag(bd, fn2))
276 headerlines = [l1, l2]
278 headerlines = [l1, l2]
277 size = len(b)
279 size = len(b)
278 hunkrange = (0, 0, 1, size)
280 hunkrange = (0, 0, 1, size)
279 hunklines = ["@@ -0,0 +1,%d @@\n" % size] + ["+" + e for e in b]
281 hunklines = ["@@ -0,0 +1,%d @@\n" % size] + ["+" + e for e in b]
280 hunks = (hunkrange, checknonewline(hunklines)),
282 hunks = (hunkrange, checknonewline(hunklines)),
281 elif not b:
283 elif not b:
282 a = splitnewlines(a)
284 a = splitnewlines(a)
283 l1 = "--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1))
285 l1 = "--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1))
284 if b is None:
286 if b is None:
285 l2 = '+++ /dev/null%s' % datetag(epoch)
287 l2 = '+++ /dev/null%s' % datetag(epoch)
286 else:
288 else:
287 l2 = "+++ %s%s%s" % (bprefix, fn2, datetag(bd, fn2))
289 l2 = "+++ %s%s%s" % (bprefix, fn2, datetag(bd, fn2))
288 headerlines = [l1, l2]
290 headerlines = [l1, l2]
289 size = len(a)
291 size = len(a)
290 hunkrange = (1, size, 0, 0)
292 hunkrange = (1, size, 0, 0)
291 hunklines = ["@@ -1,%d +0,0 @@\n" % size] + ["-" + e for e in a]
293 hunklines = ["@@ -1,%d +0,0 @@\n" % size] + ["-" + e for e in a]
292 hunks = (hunkrange, checknonewline(hunklines)),
294 hunks = (hunkrange, checknonewline(hunklines)),
293 else:
295 else:
294 diffhunks = _unidiff(a, b, opts=opts)
296 diffhunks = _unidiff(a, b, opts=opts)
295 try:
297 try:
296 hunkrange, hunklines = next(diffhunks)
298 hunkrange, hunklines = next(diffhunks)
297 except StopIteration:
299 except StopIteration:
298 return sentinel
300 return sentinel
299
301
300 headerlines = [
302 headerlines = [
301 "--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1)),
303 "--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1)),
302 "+++ %s%s%s" % (bprefix, fn2, datetag(bd, fn2)),
304 "+++ %s%s%s" % (bprefix, fn2, datetag(bd, fn2)),
303 ]
305 ]
304 def rewindhunks():
306 def rewindhunks():
305 yield hunkrange, checknonewline(hunklines)
307 yield hunkrange, checknonewline(hunklines)
306 for hr, hl in diffhunks:
308 for hr, hl in diffhunks:
307 yield hr, checknonewline(hl)
309 yield hr, checknonewline(hl)
308
310
309 hunks = rewindhunks()
311 hunks = rewindhunks()
310
312
311 return headerlines, hunks
313 return headerlines, hunks
312
314
313 def _unidiff(t1, t2, opts=defaultopts):
315 def _unidiff(t1, t2, opts=defaultopts):
314 """Yield hunks of a headerless unified diff from t1 and t2 texts.
316 """Yield hunks of a headerless unified diff from t1 and t2 texts.
315
317
316 Each hunk consists of a (hunkrange, hunklines) tuple where `hunkrange` is a
318 Each hunk consists of a (hunkrange, hunklines) tuple where `hunkrange` is a
317 tuple (s1, l1, s2, l2) representing the range information of the hunk to
319 tuple (s1, l1, s2, l2) representing the range information of the hunk to
318 form the '@@ -s1,l1 +s2,l2 @@' header and `hunklines` is a list of lines
320 form the '@@ -s1,l1 +s2,l2 @@' header and `hunklines` is a list of lines
319 of the hunk combining said header followed by line additions and
321 of the hunk combining said header followed by line additions and
320 deletions.
322 deletions.
321 """
323 """
322 l1 = splitnewlines(t1)
324 l1 = splitnewlines(t1)
323 l2 = splitnewlines(t2)
325 l2 = splitnewlines(t2)
324 def contextend(l, len):
326 def contextend(l, len):
325 ret = l + opts.context
327 ret = l + opts.context
326 if ret > len:
328 if ret > len:
327 ret = len
329 ret = len
328 return ret
330 return ret
329
331
330 def contextstart(l):
332 def contextstart(l):
331 ret = l - opts.context
333 ret = l - opts.context
332 if ret < 0:
334 if ret < 0:
333 return 0
335 return 0
334 return ret
336 return ret
335
337
336 lastfunc = [0, '']
338 lastfunc = [0, '']
337 def yieldhunk(hunk):
339 def yieldhunk(hunk):
338 (astart, a2, bstart, b2, delta) = hunk
340 (astart, a2, bstart, b2, delta) = hunk
339 aend = contextend(a2, len(l1))
341 aend = contextend(a2, len(l1))
340 alen = aend - astart
342 alen = aend - astart
341 blen = b2 - bstart + aend - a2
343 blen = b2 - bstart + aend - a2
342
344
343 func = ""
345 func = ""
344 if opts.showfunc:
346 if opts.showfunc:
345 lastpos, func = lastfunc
347 lastpos, func = lastfunc
346 # walk backwards from the start of the context up to the start of
348 # walk backwards from the start of the context up to the start of
347 # the previous hunk context until we find a line starting with an
349 # the previous hunk context until we find a line starting with an
348 # alphanumeric char.
350 # alphanumeric char.
349 for i in xrange(astart - 1, lastpos - 1, -1):
351 for i in xrange(astart - 1, lastpos - 1, -1):
350 if l1[i][0].isalnum():
352 if l1[i][0].isalnum():
351 func = ' ' + l1[i].rstrip()[:40]
353 func = ' ' + l1[i].rstrip()[:40]
352 lastfunc[1] = func
354 lastfunc[1] = func
353 break
355 break
354 # by recording this hunk's starting point as the next place to
356 # by recording this hunk's starting point as the next place to
355 # start looking for function lines, we avoid reading any line in
357 # start looking for function lines, we avoid reading any line in
356 # the file more than once.
358 # the file more than once.
357 lastfunc[0] = astart
359 lastfunc[0] = astart
358
360
359 # zero-length hunk ranges report their start line as one less
361 # zero-length hunk ranges report their start line as one less
360 if alen:
362 if alen:
361 astart += 1
363 astart += 1
362 if blen:
364 if blen:
363 bstart += 1
365 bstart += 1
364
366
365 hunkrange = astart, alen, bstart, blen
367 hunkrange = astart, alen, bstart, blen
366 hunklines = (
368 hunklines = (
367 ["@@ -%d,%d +%d,%d @@%s\n" % (hunkrange + (func,))]
369 ["@@ -%d,%d +%d,%d @@%s\n" % (hunkrange + (func,))]
368 + delta
370 + delta
369 + [' ' + l1[x] for x in xrange(a2, aend)]
371 + [' ' + l1[x] for x in xrange(a2, aend)]
370 )
372 )
371 yield hunkrange, hunklines
373 yield hunkrange, hunklines
372
374
373 # bdiff.blocks gives us the matching sequences in the files. The loop
375 # bdiff.blocks gives us the matching sequences in the files. The loop
374 # below finds the spaces between those matching sequences and translates
376 # below finds the spaces between those matching sequences and translates
375 # them into diff output.
377 # them into diff output.
376 #
378 #
377 hunk = None
379 hunk = None
378 ignoredlines = 0
380 ignoredlines = 0
379 for s, stype in allblocks(t1, t2, opts, l1, l2):
381 for s, stype in allblocks(t1, t2, opts, l1, l2):
380 a1, a2, b1, b2 = s
382 a1, a2, b1, b2 = s
381 if stype != '!':
383 if stype != '!':
382 if stype == '~':
384 if stype == '~':
383 # The diff context lines are based on t1 content. When
385 # The diff context lines are based on t1 content. When
384 # blank lines are ignored, the new lines offsets must
386 # blank lines are ignored, the new lines offsets must
385 # be adjusted as if equivalent blocks ('~') had the
387 # be adjusted as if equivalent blocks ('~') had the
386 # same sizes on both sides.
388 # same sizes on both sides.
387 ignoredlines += (b2 - b1) - (a2 - a1)
389 ignoredlines += (b2 - b1) - (a2 - a1)
388 continue
390 continue
389 delta = []
391 delta = []
390 old = l1[a1:a2]
392 old = l1[a1:a2]
391 new = l2[b1:b2]
393 new = l2[b1:b2]
392
394
393 b1 -= ignoredlines
395 b1 -= ignoredlines
394 b2 -= ignoredlines
396 b2 -= ignoredlines
395 astart = contextstart(a1)
397 astart = contextstart(a1)
396 bstart = contextstart(b1)
398 bstart = contextstart(b1)
397 prev = None
399 prev = None
398 if hunk:
400 if hunk:
399 # join with the previous hunk if it falls inside the context
401 # join with the previous hunk if it falls inside the context
400 if astart < hunk[1] + opts.context + 1:
402 if astart < hunk[1] + opts.context + 1:
401 prev = hunk
403 prev = hunk
402 astart = hunk[1]
404 astart = hunk[1]
403 bstart = hunk[3]
405 bstart = hunk[3]
404 else:
406 else:
405 for x in yieldhunk(hunk):
407 for x in yieldhunk(hunk):
406 yield x
408 yield x
407 if prev:
409 if prev:
408 # we've joined the previous hunk, record the new ending points.
410 # we've joined the previous hunk, record the new ending points.
409 hunk[1] = a2
411 hunk[1] = a2
410 hunk[3] = b2
412 hunk[3] = b2
411 delta = hunk[4]
413 delta = hunk[4]
412 else:
414 else:
413 # create a new hunk
415 # create a new hunk
414 hunk = [astart, a2, bstart, b2, delta]
416 hunk = [astart, a2, bstart, b2, delta]
415
417
416 delta[len(delta):] = [' ' + x for x in l1[astart:a1]]
418 delta[len(delta):] = [' ' + x for x in l1[astart:a1]]
417 delta[len(delta):] = ['-' + x for x in old]
419 delta[len(delta):] = ['-' + x for x in old]
418 delta[len(delta):] = ['+' + x for x in new]
420 delta[len(delta):] = ['+' + x for x in new]
419
421
420 if hunk:
422 if hunk:
421 for x in yieldhunk(hunk):
423 for x in yieldhunk(hunk):
422 yield x
424 yield x
423
425
424 def b85diff(to, tn):
426 def b85diff(to, tn):
425 '''print base85-encoded binary diff'''
427 '''print base85-encoded binary diff'''
426 def fmtline(line):
428 def fmtline(line):
427 l = len(line)
429 l = len(line)
428 if l <= 26:
430 if l <= 26:
429 l = chr(ord('A') + l - 1)
431 l = chr(ord('A') + l - 1)
430 else:
432 else:
431 l = chr(l - 26 + ord('a') - 1)
433 l = chr(l - 26 + ord('a') - 1)
432 return '%c%s\n' % (l, util.b85encode(line, True))
434 return '%c%s\n' % (l, util.b85encode(line, True))
433
435
434 def chunk(text, csize=52):
436 def chunk(text, csize=52):
435 l = len(text)
437 l = len(text)
436 i = 0
438 i = 0
437 while i < l:
439 while i < l:
438 yield text[i:i + csize]
440 yield text[i:i + csize]
439 i += csize
441 i += csize
440
442
441 if to is None:
443 if to is None:
442 to = ''
444 to = ''
443 if tn is None:
445 if tn is None:
444 tn = ''
446 tn = ''
445
447
446 if to == tn:
448 if to == tn:
447 return ''
449 return ''
448
450
449 # TODO: deltas
451 # TODO: deltas
450 ret = []
452 ret = []
451 ret.append('GIT binary patch\n')
453 ret.append('GIT binary patch\n')
452 ret.append('literal %s\n' % len(tn))
454 ret.append('literal %s\n' % len(tn))
453 for l in chunk(zlib.compress(tn)):
455 for l in chunk(zlib.compress(tn)):
454 ret.append(fmtline(l))
456 ret.append(fmtline(l))
455 ret.append('\n')
457 ret.append('\n')
456
458
457 return ''.join(ret)
459 return ''.join(ret)
458
460
459 def patchtext(bin):
461 def patchtext(bin):
460 pos = 0
462 pos = 0
461 t = []
463 t = []
462 while pos < len(bin):
464 while pos < len(bin):
463 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
465 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
464 pos += 12
466 pos += 12
465 t.append(bin[pos:pos + l])
467 t.append(bin[pos:pos + l])
466 pos += l
468 pos += l
467 return "".join(t)
469 return "".join(t)
468
470
469 def patch(a, bin):
471 def patch(a, bin):
470 if len(a) == 0:
472 if len(a) == 0:
471 # skip over trivial delta header
473 # skip over trivial delta header
472 return util.buffer(bin, 12)
474 return util.buffer(bin, 12)
473 return mpatch.patches(a, [bin])
475 return mpatch.patches(a, [bin])
474
476
475 # similar to difflib.SequenceMatcher.get_matching_blocks
477 # similar to difflib.SequenceMatcher.get_matching_blocks
476 def get_matching_blocks(a, b):
478 def get_matching_blocks(a, b):
477 return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)]
479 return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)]
478
480
479 def trivialdiffheader(length):
481 def trivialdiffheader(length):
480 return struct.pack(">lll", 0, 0, length) if length else ''
482 return struct.pack(">lll", 0, 0, length) if length else ''
481
483
482 def replacediffheader(oldlen, newlen):
484 def replacediffheader(oldlen, newlen):
483 return struct.pack(">lll", 0, oldlen, newlen)
485 return struct.pack(">lll", 0, oldlen, newlen)
@@ -1,123 +1,122
1 # similar.py - mechanisms for finding similar files
1 # similar.py - mechanisms for finding similar files
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
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
8 from __future__ import absolute_import
9
9
10 from .i18n import _
10 from .i18n import _
11 from . import (
11 from . import (
12 bdiff,
13 mdiff,
12 mdiff,
14 )
13 )
15
14
16 def _findexactmatches(repo, added, removed):
15 def _findexactmatches(repo, added, removed):
17 '''find renamed files that have no changes
16 '''find renamed files that have no changes
18
17
19 Takes a list of new filectxs and a list of removed filectxs, and yields
18 Takes a list of new filectxs and a list of removed filectxs, and yields
20 (before, after) tuples of exact matches.
19 (before, after) tuples of exact matches.
21 '''
20 '''
22 numfiles = len(added) + len(removed)
21 numfiles = len(added) + len(removed)
23
22
24 # Build table of removed files: {hash(fctx.data()): [fctx, ...]}.
23 # Build table of removed files: {hash(fctx.data()): [fctx, ...]}.
25 # We use hash() to discard fctx.data() from memory.
24 # We use hash() to discard fctx.data() from memory.
26 hashes = {}
25 hashes = {}
27 for i, fctx in enumerate(removed):
26 for i, fctx in enumerate(removed):
28 repo.ui.progress(_('searching for exact renames'), i, total=numfiles,
27 repo.ui.progress(_('searching for exact renames'), i, total=numfiles,
29 unit=_('files'))
28 unit=_('files'))
30 h = hash(fctx.data())
29 h = hash(fctx.data())
31 if h not in hashes:
30 if h not in hashes:
32 hashes[h] = [fctx]
31 hashes[h] = [fctx]
33 else:
32 else:
34 hashes[h].append(fctx)
33 hashes[h].append(fctx)
35
34
36 # For each added file, see if it corresponds to a removed file.
35 # For each added file, see if it corresponds to a removed file.
37 for i, fctx in enumerate(added):
36 for i, fctx in enumerate(added):
38 repo.ui.progress(_('searching for exact renames'), i + len(removed),
37 repo.ui.progress(_('searching for exact renames'), i + len(removed),
39 total=numfiles, unit=_('files'))
38 total=numfiles, unit=_('files'))
40 adata = fctx.data()
39 adata = fctx.data()
41 h = hash(adata)
40 h = hash(adata)
42 for rfctx in hashes.get(h, []):
41 for rfctx in hashes.get(h, []):
43 # compare between actual file contents for exact identity
42 # compare between actual file contents for exact identity
44 if adata == rfctx.data():
43 if adata == rfctx.data():
45 yield (rfctx, fctx)
44 yield (rfctx, fctx)
46 break
45 break
47
46
48 # Done
47 # Done
49 repo.ui.progress(_('searching for exact renames'), None)
48 repo.ui.progress(_('searching for exact renames'), None)
50
49
51 def _ctxdata(fctx):
50 def _ctxdata(fctx):
52 # lazily load text
51 # lazily load text
53 orig = fctx.data()
52 orig = fctx.data()
54 return orig, mdiff.splitnewlines(orig)
53 return orig, mdiff.splitnewlines(orig)
55
54
56 def _score(fctx, otherdata):
55 def _score(fctx, otherdata):
57 orig, lines = otherdata
56 orig, lines = otherdata
58 text = fctx.data()
57 text = fctx.data()
59 # bdiff.blocks() returns blocks of matching lines
58 # mdiff.blocks() returns blocks of matching lines
60 # count the number of bytes in each
59 # count the number of bytes in each
61 equal = 0
60 equal = 0
62 matches = bdiff.blocks(text, orig)
61 matches = mdiff.blocks(text, orig)
63 for x1, x2, y1, y2 in matches:
62 for x1, x2, y1, y2 in matches:
64 for line in lines[y1:y2]:
63 for line in lines[y1:y2]:
65 equal += len(line)
64 equal += len(line)
66
65
67 lengths = len(text) + len(orig)
66 lengths = len(text) + len(orig)
68 return equal * 2.0 / lengths
67 return equal * 2.0 / lengths
69
68
70 def score(fctx1, fctx2):
69 def score(fctx1, fctx2):
71 return _score(fctx1, _ctxdata(fctx2))
70 return _score(fctx1, _ctxdata(fctx2))
72
71
73 def _findsimilarmatches(repo, added, removed, threshold):
72 def _findsimilarmatches(repo, added, removed, threshold):
74 '''find potentially renamed files based on similar file content
73 '''find potentially renamed files based on similar file content
75
74
76 Takes a list of new filectxs and a list of removed filectxs, and yields
75 Takes a list of new filectxs and a list of removed filectxs, and yields
77 (before, after, score) tuples of partial matches.
76 (before, after, score) tuples of partial matches.
78 '''
77 '''
79 copies = {}
78 copies = {}
80 for i, r in enumerate(removed):
79 for i, r in enumerate(removed):
81 repo.ui.progress(_('searching for similar files'), i,
80 repo.ui.progress(_('searching for similar files'), i,
82 total=len(removed), unit=_('files'))
81 total=len(removed), unit=_('files'))
83
82
84 data = None
83 data = None
85 for a in added:
84 for a in added:
86 bestscore = copies.get(a, (None, threshold))[1]
85 bestscore = copies.get(a, (None, threshold))[1]
87 if data is None:
86 if data is None:
88 data = _ctxdata(r)
87 data = _ctxdata(r)
89 myscore = _score(a, data)
88 myscore = _score(a, data)
90 if myscore > bestscore:
89 if myscore > bestscore:
91 copies[a] = (r, myscore)
90 copies[a] = (r, myscore)
92 repo.ui.progress(_('searching'), None)
91 repo.ui.progress(_('searching'), None)
93
92
94 for dest, v in copies.iteritems():
93 for dest, v in copies.iteritems():
95 source, bscore = v
94 source, bscore = v
96 yield source, dest, bscore
95 yield source, dest, bscore
97
96
98 def _dropempty(fctxs):
97 def _dropempty(fctxs):
99 return [x for x in fctxs if x.size() > 0]
98 return [x for x in fctxs if x.size() > 0]
100
99
101 def findrenames(repo, added, removed, threshold):
100 def findrenames(repo, added, removed, threshold):
102 '''find renamed files -- yields (before, after, score) tuples'''
101 '''find renamed files -- yields (before, after, score) tuples'''
103 wctx = repo[None]
102 wctx = repo[None]
104 pctx = wctx.p1()
103 pctx = wctx.p1()
105
104
106 # Zero length files will be frequently unrelated to each other, and
105 # Zero length files will be frequently unrelated to each other, and
107 # tracking the deletion/addition of such a file will probably cause more
106 # tracking the deletion/addition of such a file will probably cause more
108 # harm than good. We strip them out here to avoid matching them later on.
107 # harm than good. We strip them out here to avoid matching them later on.
109 addedfiles = _dropempty(wctx[fp] for fp in sorted(added))
108 addedfiles = _dropempty(wctx[fp] for fp in sorted(added))
110 removedfiles = _dropempty(pctx[fp] for fp in sorted(removed) if fp in pctx)
109 removedfiles = _dropempty(pctx[fp] for fp in sorted(removed) if fp in pctx)
111
110
112 # Find exact matches.
111 # Find exact matches.
113 matchedfiles = set()
112 matchedfiles = set()
114 for (a, b) in _findexactmatches(repo, addedfiles, removedfiles):
113 for (a, b) in _findexactmatches(repo, addedfiles, removedfiles):
115 matchedfiles.add(b)
114 matchedfiles.add(b)
116 yield (a.path(), b.path(), 1.0)
115 yield (a.path(), b.path(), 1.0)
117
116
118 # If the user requested similar files to be matched, search for them also.
117 # If the user requested similar files to be matched, search for them also.
119 if threshold < 1.0:
118 if threshold < 1.0:
120 addedfiles = [x for x in addedfiles if x not in matchedfiles]
119 addedfiles = [x for x in addedfiles if x not in matchedfiles]
121 for (a, b, score) in _findsimilarmatches(repo, addedfiles,
120 for (a, b, score) in _findsimilarmatches(repo, addedfiles,
122 removedfiles, threshold):
121 removedfiles, threshold):
123 yield (a.path(), b.path(), score)
122 yield (a.path(), b.path(), score)
@@ -1,150 +1,150
1 from __future__ import absolute_import, print_function
1 from __future__ import absolute_import, print_function
2 import collections
2 import collections
3 import struct
3 import struct
4 import unittest
4 import unittest
5
5
6 from mercurial import (
6 from mercurial import (
7 bdiff,
7 mdiff,
8 mpatch,
8 mpatch,
9 )
9 )
10
10
11 class diffreplace(
11 class diffreplace(
12 collections.namedtuple('diffreplace', 'start end from_ to')):
12 collections.namedtuple('diffreplace', 'start end from_ to')):
13 def __repr__(self):
13 def __repr__(self):
14 return 'diffreplace(%r, %r, %r, %r)' % self
14 return 'diffreplace(%r, %r, %r, %r)' % self
15
15
16 class BdiffTests(unittest.TestCase):
16 class BdiffTests(unittest.TestCase):
17
17
18 def assert_bdiff_applies(self, a, b):
18 def assert_bdiff_applies(self, a, b):
19 d = bdiff.bdiff(a, b)
19 d = mdiff.textdiff(a, b)
20 c = a
20 c = a
21 if d:
21 if d:
22 c = mpatch.patches(a, [d])
22 c = mpatch.patches(a, [d])
23 self.assertEqual(
23 self.assertEqual(
24 c, b, ("bad diff+patch result from\n %r to\n "
24 c, b, ("bad diff+patch result from\n %r to\n "
25 "%r: \nbdiff: %r\npatched: %r" % (a, b, d, c[:200])))
25 "%r: \nbdiff: %r\npatched: %r" % (a, b, d, c[:200])))
26
26
27 def assert_bdiff(self, a, b):
27 def assert_bdiff(self, a, b):
28 self.assert_bdiff_applies(a, b)
28 self.assert_bdiff_applies(a, b)
29 self.assert_bdiff_applies(b, a)
29 self.assert_bdiff_applies(b, a)
30
30
31 def test_bdiff_basic(self):
31 def test_bdiff_basic(self):
32 cases = [
32 cases = [
33 ("a\nc\n\n\n\n", "a\nb\n\n\n"),
33 ("a\nc\n\n\n\n", "a\nb\n\n\n"),
34 ("a\nb\nc\n", "a\nc\n"),
34 ("a\nb\nc\n", "a\nc\n"),
35 ("", ""),
35 ("", ""),
36 ("a\nb\nc", "a\nb\nc"),
36 ("a\nb\nc", "a\nb\nc"),
37 ("a\nb\nc\nd\n", "a\nd\n"),
37 ("a\nb\nc\nd\n", "a\nd\n"),
38 ("a\nb\nc\nd\n", "a\nc\ne\n"),
38 ("a\nb\nc\nd\n", "a\nc\ne\n"),
39 ("a\nb\nc\n", "a\nc\n"),
39 ("a\nb\nc\n", "a\nc\n"),
40 ("a\n", "c\na\nb\n"),
40 ("a\n", "c\na\nb\n"),
41 ("a\n", ""),
41 ("a\n", ""),
42 ("a\n", "b\nc\n"),
42 ("a\n", "b\nc\n"),
43 ("a\n", "c\na\n"),
43 ("a\n", "c\na\n"),
44 ("", "adjfkjdjksdhfksj"),
44 ("", "adjfkjdjksdhfksj"),
45 ("", "ab"),
45 ("", "ab"),
46 ("", "abc"),
46 ("", "abc"),
47 ("a", "a"),
47 ("a", "a"),
48 ("ab", "ab"),
48 ("ab", "ab"),
49 ("abc", "abc"),
49 ("abc", "abc"),
50 ("a\n", "a\n"),
50 ("a\n", "a\n"),
51 ("a\nb", "a\nb"),
51 ("a\nb", "a\nb"),
52 ]
52 ]
53 for a, b in cases:
53 for a, b in cases:
54 self.assert_bdiff(a, b)
54 self.assert_bdiff(a, b)
55
55
56 def showdiff(self, a, b):
56 def showdiff(self, a, b):
57 bin = bdiff.bdiff(a, b)
57 bin = mdiff.textdiff(a, b)
58 pos = 0
58 pos = 0
59 q = 0
59 q = 0
60 actions = []
60 actions = []
61 while pos < len(bin):
61 while pos < len(bin):
62 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
62 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
63 pos += 12
63 pos += 12
64 if p1:
64 if p1:
65 actions.append(a[q:p1])
65 actions.append(a[q:p1])
66 actions.append(diffreplace(p1, p2, a[p1:p2], bin[pos:pos + l]))
66 actions.append(diffreplace(p1, p2, a[p1:p2], bin[pos:pos + l]))
67 pos += l
67 pos += l
68 q = p2
68 q = p2
69 if q < len(a):
69 if q < len(a):
70 actions.append(a[q:])
70 actions.append(a[q:])
71 return actions
71 return actions
72
72
73 def test_issue1295(self):
73 def test_issue1295(self):
74 cases = [
74 cases = [
75 ("x\n\nx\n\nx\n\nx\n\nz\n", "x\n\nx\n\ny\n\nx\n\nx\n\nz\n",
75 ("x\n\nx\n\nx\n\nx\n\nz\n", "x\n\nx\n\ny\n\nx\n\nx\n\nz\n",
76 ['x\n\nx\n\n', diffreplace(6, 6, '', 'y\n\n'), 'x\n\nx\n\nz\n']),
76 ['x\n\nx\n\n', diffreplace(6, 6, '', 'y\n\n'), 'x\n\nx\n\nz\n']),
77 ("x\n\nx\n\nx\n\nx\n\nz\n", "x\n\nx\n\ny\n\nx\n\ny\n\nx\n\nz\n",
77 ("x\n\nx\n\nx\n\nx\n\nz\n", "x\n\nx\n\ny\n\nx\n\ny\n\nx\n\nz\n",
78 ['x\n\nx\n\n',
78 ['x\n\nx\n\n',
79 diffreplace(6, 6, '', 'y\n\n'),
79 diffreplace(6, 6, '', 'y\n\n'),
80 'x\n\n',
80 'x\n\n',
81 diffreplace(9, 9, '', 'y\n\n'),
81 diffreplace(9, 9, '', 'y\n\n'),
82 'x\n\nz\n']),
82 'x\n\nz\n']),
83 ]
83 ]
84 for old, new, want in cases:
84 for old, new, want in cases:
85 self.assertEqual(self.showdiff(old, new), want)
85 self.assertEqual(self.showdiff(old, new), want)
86
86
87 def test_issue1295_varies_on_pure(self):
87 def test_issue1295_varies_on_pure(self):
88 # we should pick up abbbc. rather than bc.de as the longest match
88 # we should pick up abbbc. rather than bc.de as the longest match
89 got = self.showdiff("a\nb\nb\nb\nc\n.\nd\ne\n.\nf\n",
89 got = self.showdiff("a\nb\nb\nb\nc\n.\nd\ne\n.\nf\n",
90 "a\nb\nb\na\nb\nb\nb\nc\n.\nb\nc\n.\nd\ne\nf\n")
90 "a\nb\nb\na\nb\nb\nb\nc\n.\nb\nc\n.\nd\ne\nf\n")
91 want_c = ['a\nb\nb\n',
91 want_c = ['a\nb\nb\n',
92 diffreplace(6, 6, '', 'a\nb\nb\nb\nc\n.\n'),
92 diffreplace(6, 6, '', 'a\nb\nb\nb\nc\n.\n'),
93 'b\nc\n.\nd\ne\n',
93 'b\nc\n.\nd\ne\n',
94 diffreplace(16, 18, '.\n', ''),
94 diffreplace(16, 18, '.\n', ''),
95 'f\n']
95 'f\n']
96 want_pure = [diffreplace(0, 0, '', 'a\nb\nb\n'),
96 want_pure = [diffreplace(0, 0, '', 'a\nb\nb\n'),
97 'a\nb\nb\nb\nc\n.\n',
97 'a\nb\nb\nb\nc\n.\n',
98 diffreplace(12, 12, '', 'b\nc\n.\n'),
98 diffreplace(12, 12, '', 'b\nc\n.\n'),
99 'd\ne\n',
99 'd\ne\n',
100 diffreplace(16, 18, '.\n', ''), 'f\n']
100 diffreplace(16, 18, '.\n', ''), 'f\n']
101 self.assert_(got in (want_c, want_pure),
101 self.assert_(got in (want_c, want_pure),
102 'got: %r, wanted either %r or %r' % (
102 'got: %r, wanted either %r or %r' % (
103 got, want_c, want_pure))
103 got, want_c, want_pure))
104
104
105 def test_fixws(self):
105 def test_fixws(self):
106 cases = [
106 cases = [
107 (" \ta\r b\t\n", "ab\n", 1),
107 (" \ta\r b\t\n", "ab\n", 1),
108 (" \ta\r b\t\n", " a b\n", 0),
108 (" \ta\r b\t\n", " a b\n", 0),
109 ("", "", 1),
109 ("", "", 1),
110 ("", "", 0),
110 ("", "", 0),
111 ]
111 ]
112 for a, b, allws in cases:
112 for a, b, allws in cases:
113 c = bdiff.fixws(a, allws)
113 c = mdiff.fixws(a, allws)
114 self.assertEqual(
114 self.assertEqual(
115 c, b, 'fixws(%r) want %r got %r (allws=%r)' % (a, b, c, allws))
115 c, b, 'fixws(%r) want %r got %r (allws=%r)' % (a, b, c, allws))
116
116
117 def test_nice_diff_for_trivial_change(self):
117 def test_nice_diff_for_trivial_change(self):
118 self.assertEqual(self.showdiff(
118 self.assertEqual(self.showdiff(
119 ''.join('<%s\n-\n' % i for i in range(5)),
119 ''.join('<%s\n-\n' % i for i in range(5)),
120 ''.join('>%s\n-\n' % i for i in range(5))),
120 ''.join('>%s\n-\n' % i for i in range(5))),
121 [diffreplace(0, 3, '<0\n', '>0\n'),
121 [diffreplace(0, 3, '<0\n', '>0\n'),
122 '-\n',
122 '-\n',
123 diffreplace(5, 8, '<1\n', '>1\n'),
123 diffreplace(5, 8, '<1\n', '>1\n'),
124 '-\n',
124 '-\n',
125 diffreplace(10, 13, '<2\n', '>2\n'),
125 diffreplace(10, 13, '<2\n', '>2\n'),
126 '-\n',
126 '-\n',
127 diffreplace(15, 18, '<3\n', '>3\n'),
127 diffreplace(15, 18, '<3\n', '>3\n'),
128 '-\n',
128 '-\n',
129 diffreplace(20, 23, '<4\n', '>4\n'),
129 diffreplace(20, 23, '<4\n', '>4\n'),
130 '-\n'])
130 '-\n'])
131
131
132 def test_prefer_appending(self):
132 def test_prefer_appending(self):
133 # 1 line to 3 lines
133 # 1 line to 3 lines
134 self.assertEqual(self.showdiff('a\n', 'a\n' * 3),
134 self.assertEqual(self.showdiff('a\n', 'a\n' * 3),
135 ['a\n', diffreplace(2, 2, '', 'a\na\n')])
135 ['a\n', diffreplace(2, 2, '', 'a\na\n')])
136 # 1 line to 5 lines
136 # 1 line to 5 lines
137 self.assertEqual(self.showdiff('a\n', 'a\n' * 5),
137 self.assertEqual(self.showdiff('a\n', 'a\n' * 5),
138 ['a\n', diffreplace(2, 2, '', 'a\na\na\na\n')])
138 ['a\n', diffreplace(2, 2, '', 'a\na\na\na\n')])
139
139
140 def test_prefer_removing_trailing(self):
140 def test_prefer_removing_trailing(self):
141 # 3 lines to 1 line
141 # 3 lines to 1 line
142 self.assertEqual(self.showdiff('a\n' * 3, 'a\n'),
142 self.assertEqual(self.showdiff('a\n' * 3, 'a\n'),
143 ['a\n', diffreplace(2, 6, 'a\na\n', '')])
143 ['a\n', diffreplace(2, 6, 'a\na\n', '')])
144 # 5 lines to 1 line
144 # 5 lines to 1 line
145 self.assertEqual(self.showdiff('a\n' * 5, 'a\n'),
145 self.assertEqual(self.showdiff('a\n' * 5, 'a\n'),
146 ['a\n', diffreplace(2, 10, 'a\na\na\na\n', '')])
146 ['a\n', diffreplace(2, 10, 'a\na\na\na\n', '')])
147
147
148 if __name__ == '__main__':
148 if __name__ == '__main__':
149 import silenttestrunner
149 import silenttestrunner
150 silenttestrunner.main(__name__)
150 silenttestrunner.main(__name__)
@@ -1,53 +1,53
1 #require test-repo
1 #require test-repo
2
2
3 $ . "$TESTDIR/helpers-testrepo.sh"
3 $ . "$TESTDIR/helpers-testrepo.sh"
4 $ check_code="$TESTDIR"/../contrib/check-code.py
4 $ check_code="$TESTDIR"/../contrib/check-code.py
5 $ cd "$TESTDIR"/..
5 $ cd "$TESTDIR"/..
6
6
7 New errors are not allowed. Warnings are strongly discouraged.
7 New errors are not allowed. Warnings are strongly discouraged.
8 (The writing "no-che?k-code" is for not skipping this file when checking.)
8 (The writing "no-che?k-code" is for not skipping this file when checking.)
9
9
10 $ hg locate -X contrib/python-zstandard -X hgext/fsmonitor/pywatchman |
10 $ hg locate -X contrib/python-zstandard -X hgext/fsmonitor/pywatchman |
11 > sed 's-\\-/-g' | "$check_code" --warnings --per-file=0 - || false
11 > sed 's-\\-/-g' | "$check_code" --warnings --per-file=0 - || false
12 contrib/perf.py:869:
12 contrib/perf.py:868:
13 > r.revision(r.node(x))
13 > r.revision(r.node(x))
14 don't convert rev to node before passing to revision(nodeorrev)
14 don't convert rev to node before passing to revision(nodeorrev)
15 Skipping i18n/polib.py it has no-che?k-code (glob)
15 Skipping i18n/polib.py it has no-che?k-code (glob)
16 Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob)
16 Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob)
17 Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob)
17 Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob)
18 Skipping mercurial/statprof.py it has no-che?k-code (glob)
18 Skipping mercurial/statprof.py it has no-che?k-code (glob)
19 Skipping tests/badserverext.py it has no-che?k-code (glob)
19 Skipping tests/badserverext.py it has no-che?k-code (glob)
20 [1]
20 [1]
21
21
22 @commands in debugcommands.py should be in alphabetical order.
22 @commands in debugcommands.py should be in alphabetical order.
23
23
24 >>> import re
24 >>> import re
25 >>> commands = []
25 >>> commands = []
26 >>> with open('mercurial/debugcommands.py', 'rb') as fh:
26 >>> with open('mercurial/debugcommands.py', 'rb') as fh:
27 ... for line in fh:
27 ... for line in fh:
28 ... m = re.match("^@command\('([a-z]+)", line)
28 ... m = re.match("^@command\('([a-z]+)", line)
29 ... if m:
29 ... if m:
30 ... commands.append(m.group(1))
30 ... commands.append(m.group(1))
31 >>> scommands = list(sorted(commands))
31 >>> scommands = list(sorted(commands))
32 >>> for i, command in enumerate(scommands):
32 >>> for i, command in enumerate(scommands):
33 ... if command != commands[i]:
33 ... if command != commands[i]:
34 ... print('commands in debugcommands.py not sorted; first differing '
34 ... print('commands in debugcommands.py not sorted; first differing '
35 ... 'command is %s; expected %s' % (commands[i], command))
35 ... 'command is %s; expected %s' % (commands[i], command))
36 ... break
36 ... break
37
37
38 Prevent adding new files in the root directory accidentally.
38 Prevent adding new files in the root directory accidentally.
39
39
40 $ hg files 'glob:*'
40 $ hg files 'glob:*'
41 .editorconfig
41 .editorconfig
42 .hgignore
42 .hgignore
43 .hgsigs
43 .hgsigs
44 .hgtags
44 .hgtags
45 CONTRIBUTING
45 CONTRIBUTING
46 CONTRIBUTORS
46 CONTRIBUTORS
47 COPYING
47 COPYING
48 Makefile
48 Makefile
49 README
49 README
50 hg
50 hg
51 hgeditor
51 hgeditor
52 hgweb.cgi
52 hgweb.cgi
53 setup.py
53 setup.py
@@ -1,171 +1,171
1 #require test-repo
1 #require test-repo
2
2
3 Set vars:
3 Set vars:
4
4
5 $ . "$TESTDIR/helpers-testrepo.sh"
5 $ . "$TESTDIR/helpers-testrepo.sh"
6 $ CONTRIBDIR="$TESTDIR/../contrib"
6 $ CONTRIBDIR="$TESTDIR/../contrib"
7
7
8 Prepare repo:
8 Prepare repo:
9
9
10 $ hg init
10 $ hg init
11
11
12 $ echo this is file a > a
12 $ echo this is file a > a
13 $ hg add a
13 $ hg add a
14 $ hg commit -m first
14 $ hg commit -m first
15
15
16 $ echo adding to file a >> a
16 $ echo adding to file a >> a
17 $ hg commit -m second
17 $ hg commit -m second
18
18
19 $ echo adding more to file a >> a
19 $ echo adding more to file a >> a
20 $ hg commit -m third
20 $ hg commit -m third
21
21
22 $ hg up -r 0
22 $ hg up -r 0
23 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
23 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
24 $ echo merge-this >> a
24 $ echo merge-this >> a
25 $ hg commit -m merge-able
25 $ hg commit -m merge-able
26 created new head
26 created new head
27
27
28 $ hg up -r 2
28 $ hg up -r 2
29 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
30
30
31 perfstatus
31 perfstatus
32
32
33 $ cat >> $HGRCPATH << EOF
33 $ cat >> $HGRCPATH << EOF
34 > [extensions]
34 > [extensions]
35 > perfstatusext=$CONTRIBDIR/perf.py
35 > perfstatusext=$CONTRIBDIR/perf.py
36 > [perf]
36 > [perf]
37 > presleep=0
37 > presleep=0
38 > stub=on
38 > stub=on
39 > parentscount=1
39 > parentscount=1
40 > EOF
40 > EOF
41 $ hg help perfstatusext
41 $ hg help perfstatusext
42 perfstatusext extension - helper extension to measure performance
42 perfstatusext extension - helper extension to measure performance
43
43
44 list of commands:
44 list of commands:
45
45
46 perfaddremove
46 perfaddremove
47 (no help text available)
47 (no help text available)
48 perfancestors
48 perfancestors
49 (no help text available)
49 (no help text available)
50 perfancestorset
50 perfancestorset
51 (no help text available)
51 (no help text available)
52 perfannotate (no help text available)
52 perfannotate (no help text available)
53 perfbdiff benchmark a bdiff between revisions
53 perfbdiff benchmark a bdiff between revisions
54 perfbranchmap
54 perfbranchmap
55 benchmark the update of a branchmap
55 benchmark the update of a branchmap
56 perfcca (no help text available)
56 perfcca (no help text available)
57 perfchangegroupchangelog
57 perfchangegroupchangelog
58 Benchmark producing a changelog group for a changegroup.
58 Benchmark producing a changelog group for a changegroup.
59 perfchangeset
59 perfchangeset
60 (no help text available)
60 (no help text available)
61 perfctxfiles (no help text available)
61 perfctxfiles (no help text available)
62 perfdiffwd Profile diff of working directory changes
62 perfdiffwd Profile diff of working directory changes
63 perfdirfoldmap
63 perfdirfoldmap
64 (no help text available)
64 (no help text available)
65 perfdirs (no help text available)
65 perfdirs (no help text available)
66 perfdirstate (no help text available)
66 perfdirstate (no help text available)
67 perfdirstatedirs
67 perfdirstatedirs
68 (no help text available)
68 (no help text available)
69 perfdirstatefoldmap
69 perfdirstatefoldmap
70 (no help text available)
70 (no help text available)
71 perfdirstatewrite
71 perfdirstatewrite
72 (no help text available)
72 (no help text available)
73 perffncacheencode
73 perffncacheencode
74 (no help text available)
74 (no help text available)
75 perffncacheload
75 perffncacheload
76 (no help text available)
76 (no help text available)
77 perffncachewrite
77 perffncachewrite
78 (no help text available)
78 (no help text available)
79 perfheads (no help text available)
79 perfheads (no help text available)
80 perfindex (no help text available)
80 perfindex (no help text available)
81 perfloadmarkers
81 perfloadmarkers
82 benchmark the time to parse the on-disk markers for a repo
82 benchmark the time to parse the on-disk markers for a repo
83 perflog (no help text available)
83 perflog (no help text available)
84 perflookup (no help text available)
84 perflookup (no help text available)
85 perflrucachedict
85 perflrucachedict
86 (no help text available)
86 (no help text available)
87 perfmanifest (no help text available)
87 perfmanifest (no help text available)
88 perfmergecalculate
88 perfmergecalculate
89 (no help text available)
89 (no help text available)
90 perfmoonwalk benchmark walking the changelog backwards
90 perfmoonwalk benchmark walking the changelog backwards
91 perfnodelookup
91 perfnodelookup
92 (no help text available)
92 (no help text available)
93 perfparents (no help text available)
93 perfparents (no help text available)
94 perfpathcopies
94 perfpathcopies
95 (no help text available)
95 (no help text available)
96 perfrawfiles (no help text available)
96 perfrawfiles (no help text available)
97 perfrevlog Benchmark reading a series of revisions from a revlog.
97 perfrevlog Benchmark reading a series of revisions from a revlog.
98 perfrevlogchunks
98 perfrevlogchunks
99 Benchmark operations on revlog chunks.
99 Benchmark operations on revlog chunks.
100 perfrevlogrevision
100 perfrevlogrevision
101 Benchmark obtaining a revlog revision.
101 Benchmark obtaining a revlog revision.
102 perfrevrange (no help text available)
102 perfrevrange (no help text available)
103 perfrevset benchmark the execution time of a revset
103 perfrevset benchmark the execution time of a revset
104 perfstartup (no help text available)
104 perfstartup (no help text available)
105 perfstatus (no help text available)
105 perfstatus (no help text available)
106 perftags (no help text available)
106 perftags (no help text available)
107 perftemplating
107 perftemplating
108 (no help text available)
108 (no help text available)
109 perfvolatilesets
109 perfvolatilesets
110 benchmark the computation of various volatile set
110 benchmark the computation of various volatile set
111 perfwalk (no help text available)
111 perfwalk (no help text available)
112 perfwrite microbenchmark ui.write
112 perfwrite microbenchmark ui.write
113
113
114 (use 'hg help -v perfstatusext' to show built-in aliases and global options)
114 (use 'hg help -v perfstatusext' to show built-in aliases and global options)
115 $ hg perfaddremove
115 $ hg perfaddremove
116 $ hg perfancestors
116 $ hg perfancestors
117 $ hg perfancestorset 2
117 $ hg perfancestorset 2
118 $ hg perfannotate a
118 $ hg perfannotate a
119 $ hg perfbdiff -c 1
119 $ hg perfbdiff -c 1
120 $ hg perfbdiff --alldata 1
120 $ hg perfbdiff --alldata 1
121 $ hg perfbranchmap
121 $ hg perfbranchmap
122 $ hg perfcca
122 $ hg perfcca
123 $ hg perfchangegroupchangelog
123 $ hg perfchangegroupchangelog
124 $ hg perfchangeset 2
124 $ hg perfchangeset 2
125 $ hg perfctxfiles 2
125 $ hg perfctxfiles 2
126 $ hg perfdiffwd
126 $ hg perfdiffwd
127 $ hg perfdirfoldmap
127 $ hg perfdirfoldmap
128 $ hg perfdirs
128 $ hg perfdirs
129 $ hg perfdirstate
129 $ hg perfdirstate
130 $ hg perfdirstatedirs
130 $ hg perfdirstatedirs
131 $ hg perfdirstatefoldmap
131 $ hg perfdirstatefoldmap
132 $ hg perfdirstatewrite
132 $ hg perfdirstatewrite
133 $ hg perffncacheencode
133 $ hg perffncacheencode
134 $ hg perffncacheload
134 $ hg perffncacheload
135 $ hg perffncachewrite
135 $ hg perffncachewrite
136 $ hg perfheads
136 $ hg perfheads
137 $ hg perfindex
137 $ hg perfindex
138 $ hg perfloadmarkers
138 $ hg perfloadmarkers
139 $ hg perflog
139 $ hg perflog
140 $ hg perflookup 2
140 $ hg perflookup 2
141 $ hg perflrucache
141 $ hg perflrucache
142 $ hg perfmanifest 2
142 $ hg perfmanifest 2
143 $ hg perfmergecalculate -r 3
143 $ hg perfmergecalculate -r 3
144 $ hg perfmoonwalk
144 $ hg perfmoonwalk
145 $ hg perfnodelookup 2
145 $ hg perfnodelookup 2
146 $ hg perfpathcopies 1 2
146 $ hg perfpathcopies 1 2
147 $ hg perfrawfiles 2
147 $ hg perfrawfiles 2
148 $ hg perfrevlog .hg/store/data/a.i
148 $ hg perfrevlog .hg/store/data/a.i
149 $ hg perfrevlogrevision -m 0
149 $ hg perfrevlogrevision -m 0
150 $ hg perfrevlogchunks -c
150 $ hg perfrevlogchunks -c
151 $ hg perfrevrange
151 $ hg perfrevrange
152 $ hg perfrevset 'all()'
152 $ hg perfrevset 'all()'
153 $ hg perfstartup
153 $ hg perfstartup
154 $ hg perfstatus
154 $ hg perfstatus
155 $ hg perftags
155 $ hg perftags
156 $ hg perftemplating
156 $ hg perftemplating
157 $ hg perfvolatilesets
157 $ hg perfvolatilesets
158 $ hg perfwalk
158 $ hg perfwalk
159 $ hg perfparents
159 $ hg perfparents
160
160
161 Check perf.py for historical portability
161 Check perf.py for historical portability
162
162
163 $ cd "$TESTDIR/.."
163 $ cd "$TESTDIR/.."
164
164
165 $ (hg files -r 1.2 glob:mercurial/*.c glob:mercurial/*.py;
165 $ (hg files -r 1.2 glob:mercurial/*.c glob:mercurial/*.py;
166 > hg files -r tip glob:mercurial/*.c glob:mercurial/*.py) |
166 > hg files -r tip glob:mercurial/*.c glob:mercurial/*.py) |
167 > "$TESTDIR"/check-perf-code.py contrib/perf.py
167 > "$TESTDIR"/check-perf-code.py contrib/perf.py
168 contrib/perf.py:869:
168 contrib/perf.py:868:
169 > r.revision(r.node(x))
169 > r.revision(r.node(x))
170 don't convert rev to node before passing to revision(nodeorrev)
170 don't convert rev to node before passing to revision(nodeorrev)
171 [1]
171 [1]
General Comments 0
You need to be logged in to leave comments. Login now