##// END OF EJS Templates
manifest: move clearcaches to manifestlog...
Durham Goode -
r30370:10c92459 default
parent child Browse files
Show More
@@ -1,1162 +1,1162
1 1 # perf.py - performance test routines
2 2 '''helper extension to measure performance'''
3 3
4 4 # "historical portability" policy of perf.py:
5 5 #
6 6 # We have to do:
7 7 # - make perf.py "loadable" with as wide Mercurial version as possible
8 8 # This doesn't mean that perf commands work correctly with that Mercurial.
9 9 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
10 10 # - make historical perf command work correctly with as wide Mercurial
11 11 # version as possible
12 12 #
13 13 # We have to do, if possible with reasonable cost:
14 14 # - make recent perf command for historical feature work correctly
15 15 # with early Mercurial
16 16 #
17 17 # We don't have to do:
18 18 # - make perf command for recent feature work correctly with early
19 19 # Mercurial
20 20
21 21 from __future__ import absolute_import
22 22 import functools
23 23 import os
24 24 import random
25 25 import sys
26 26 import time
27 27 from mercurial import (
28 28 bdiff,
29 29 changegroup,
30 30 cmdutil,
31 31 commands,
32 32 copies,
33 33 error,
34 34 extensions,
35 35 mdiff,
36 36 merge,
37 37 revlog,
38 38 util,
39 39 )
40 40
41 41 # for "historical portability":
42 42 # try to import modules separately (in dict order), and ignore
43 43 # failure, because these aren't available with early Mercurial
44 44 try:
45 45 from mercurial import branchmap # since 2.5 (or bcee63733aad)
46 46 except ImportError:
47 47 pass
48 48 try:
49 49 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
50 50 except ImportError:
51 51 pass
52 52 try:
53 53 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
54 54 except ImportError:
55 55 pass
56 56 try:
57 57 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
58 58 except ImportError:
59 59 pass
60 60
61 61 # for "historical portability":
62 62 # define util.safehasattr forcibly, because util.safehasattr has been
63 63 # available since 1.9.3 (or 94b200a11cf7)
64 64 _undefined = object()
65 65 def safehasattr(thing, attr):
66 66 return getattr(thing, attr, _undefined) is not _undefined
67 67 setattr(util, 'safehasattr', safehasattr)
68 68
69 69 # for "historical portability":
70 70 # use locally defined empty option list, if formatteropts isn't
71 71 # available, because commands.formatteropts has been available since
72 72 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
73 73 # available since 2.2 (or ae5f92e154d3)
74 74 formatteropts = getattr(commands, "formatteropts", [])
75 75
76 76 # for "historical portability":
77 77 # use locally defined option list, if debugrevlogopts isn't available,
78 78 # because commands.debugrevlogopts has been available since 3.7 (or
79 79 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
80 80 # since 1.9 (or a79fea6b3e77).
81 81 revlogopts = getattr(commands, "debugrevlogopts", [
82 82 ('c', 'changelog', False, ('open changelog')),
83 83 ('m', 'manifest', False, ('open manifest')),
84 84 ('', 'dir', False, ('open directory manifest')),
85 85 ])
86 86
87 87 cmdtable = {}
88 88
89 89 # for "historical portability":
90 90 # define parsealiases locally, because cmdutil.parsealiases has been
91 91 # available since 1.5 (or 6252852b4332)
92 92 def parsealiases(cmd):
93 93 return cmd.lstrip("^").split("|")
94 94
95 95 if safehasattr(cmdutil, 'command'):
96 96 import inspect
97 97 command = cmdutil.command(cmdtable)
98 98 if 'norepo' not in inspect.getargspec(command)[0]:
99 99 # for "historical portability":
100 100 # wrap original cmdutil.command, because "norepo" option has
101 101 # been available since 3.1 (or 75a96326cecb)
102 102 _command = command
103 103 def command(name, options=(), synopsis=None, norepo=False):
104 104 if norepo:
105 105 commands.norepo += ' %s' % ' '.join(parsealiases(name))
106 106 return _command(name, list(options), synopsis)
107 107 else:
108 108 # for "historical portability":
109 109 # define "@command" annotation locally, because cmdutil.command
110 110 # has been available since 1.9 (or 2daa5179e73f)
111 111 def command(name, options=(), synopsis=None, norepo=False):
112 112 def decorator(func):
113 113 if synopsis:
114 114 cmdtable[name] = func, list(options), synopsis
115 115 else:
116 116 cmdtable[name] = func, list(options)
117 117 if norepo:
118 118 commands.norepo += ' %s' % ' '.join(parsealiases(name))
119 119 return func
120 120 return decorator
121 121
122 122 def getlen(ui):
123 123 if ui.configbool("perf", "stub"):
124 124 return lambda x: 1
125 125 return len
126 126
127 127 def gettimer(ui, opts=None):
128 128 """return a timer function and formatter: (timer, formatter)
129 129
130 130 This function exists to gather the creation of formatter in a single
131 131 place instead of duplicating it in all performance commands."""
132 132
133 133 # enforce an idle period before execution to counteract power management
134 134 # experimental config: perf.presleep
135 135 time.sleep(getint(ui, "perf", "presleep", 1))
136 136
137 137 if opts is None:
138 138 opts = {}
139 139 # redirect all to stderr
140 140 ui = ui.copy()
141 141 uifout = safeattrsetter(ui, 'fout', ignoremissing=True)
142 142 if uifout:
143 143 # for "historical portability":
144 144 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
145 145 uifout.set(ui.ferr)
146 146
147 147 # get a formatter
148 148 uiformatter = getattr(ui, 'formatter', None)
149 149 if uiformatter:
150 150 fm = uiformatter('perf', opts)
151 151 else:
152 152 # for "historical portability":
153 153 # define formatter locally, because ui.formatter has been
154 154 # available since 2.2 (or ae5f92e154d3)
155 155 from mercurial import node
156 156 class defaultformatter(object):
157 157 """Minimized composition of baseformatter and plainformatter
158 158 """
159 159 def __init__(self, ui, topic, opts):
160 160 self._ui = ui
161 161 if ui.debugflag:
162 162 self.hexfunc = node.hex
163 163 else:
164 164 self.hexfunc = node.short
165 165 def __nonzero__(self):
166 166 return False
167 167 def startitem(self):
168 168 pass
169 169 def data(self, **data):
170 170 pass
171 171 def write(self, fields, deftext, *fielddata, **opts):
172 172 self._ui.write(deftext % fielddata, **opts)
173 173 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
174 174 if cond:
175 175 self._ui.write(deftext % fielddata, **opts)
176 176 def plain(self, text, **opts):
177 177 self._ui.write(text, **opts)
178 178 def end(self):
179 179 pass
180 180 fm = defaultformatter(ui, 'perf', opts)
181 181
182 182 # stub function, runs code only once instead of in a loop
183 183 # experimental config: perf.stub
184 184 if ui.configbool("perf", "stub"):
185 185 return functools.partial(stub_timer, fm), fm
186 186 return functools.partial(_timer, fm), fm
187 187
188 188 def stub_timer(fm, func, title=None):
189 189 func()
190 190
191 191 def _timer(fm, func, title=None):
192 192 results = []
193 193 begin = time.time()
194 194 count = 0
195 195 while True:
196 196 ostart = os.times()
197 197 cstart = time.time()
198 198 r = func()
199 199 cstop = time.time()
200 200 ostop = os.times()
201 201 count += 1
202 202 a, b = ostart, ostop
203 203 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
204 204 if cstop - begin > 3 and count >= 100:
205 205 break
206 206 if cstop - begin > 10 and count >= 3:
207 207 break
208 208
209 209 fm.startitem()
210 210
211 211 if title:
212 212 fm.write('title', '! %s\n', title)
213 213 if r:
214 214 fm.write('result', '! result: %s\n', r)
215 215 m = min(results)
216 216 fm.plain('!')
217 217 fm.write('wall', ' wall %f', m[0])
218 218 fm.write('comb', ' comb %f', m[1] + m[2])
219 219 fm.write('user', ' user %f', m[1])
220 220 fm.write('sys', ' sys %f', m[2])
221 221 fm.write('count', ' (best of %d)', count)
222 222 fm.plain('\n')
223 223
224 224 # utilities for historical portability
225 225
226 226 def getint(ui, section, name, default):
227 227 # for "historical portability":
228 228 # ui.configint has been available since 1.9 (or fa2b596db182)
229 229 v = ui.config(section, name, None)
230 230 if v is None:
231 231 return default
232 232 try:
233 233 return int(v)
234 234 except ValueError:
235 235 raise error.ConfigError(("%s.%s is not an integer ('%s')")
236 236 % (section, name, v))
237 237
238 238 def safeattrsetter(obj, name, ignoremissing=False):
239 239 """Ensure that 'obj' has 'name' attribute before subsequent setattr
240 240
241 241 This function is aborted, if 'obj' doesn't have 'name' attribute
242 242 at runtime. This avoids overlooking removal of an attribute, which
243 243 breaks assumption of performance measurement, in the future.
244 244
245 245 This function returns the object to (1) assign a new value, and
246 246 (2) restore an original value to the attribute.
247 247
248 248 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
249 249 abortion, and this function returns None. This is useful to
250 250 examine an attribute, which isn't ensured in all Mercurial
251 251 versions.
252 252 """
253 253 if not util.safehasattr(obj, name):
254 254 if ignoremissing:
255 255 return None
256 256 raise error.Abort(("missing attribute %s of %s might break assumption"
257 257 " of performance measurement") % (name, obj))
258 258
259 259 origvalue = getattr(obj, name)
260 260 class attrutil(object):
261 261 def set(self, newvalue):
262 262 setattr(obj, name, newvalue)
263 263 def restore(self):
264 264 setattr(obj, name, origvalue)
265 265
266 266 return attrutil()
267 267
268 268 # utilities to examine each internal API changes
269 269
270 270 def getbranchmapsubsettable():
271 271 # for "historical portability":
272 272 # subsettable is defined in:
273 273 # - branchmap since 2.9 (or 175c6fd8cacc)
274 274 # - repoview since 2.5 (or 59a9f18d4587)
275 275 for mod in (branchmap, repoview):
276 276 subsettable = getattr(mod, 'subsettable', None)
277 277 if subsettable:
278 278 return subsettable
279 279
280 280 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
281 281 # branchmap and repoview modules exist, but subsettable attribute
282 282 # doesn't)
283 283 raise error.Abort(("perfbranchmap not available with this Mercurial"),
284 284 hint="use 2.5 or later")
285 285
286 286 def getsvfs(repo):
287 287 """Return appropriate object to access files under .hg/store
288 288 """
289 289 # for "historical portability":
290 290 # repo.svfs has been available since 2.3 (or 7034365089bf)
291 291 svfs = getattr(repo, 'svfs', None)
292 292 if svfs:
293 293 return svfs
294 294 else:
295 295 return getattr(repo, 'sopener')
296 296
297 297 def getvfs(repo):
298 298 """Return appropriate object to access files under .hg
299 299 """
300 300 # for "historical portability":
301 301 # repo.vfs has been available since 2.3 (or 7034365089bf)
302 302 vfs = getattr(repo, 'vfs', None)
303 303 if vfs:
304 304 return vfs
305 305 else:
306 306 return getattr(repo, 'opener')
307 307
308 308 def repocleartagscachefunc(repo):
309 309 """Return the function to clear tags cache according to repo internal API
310 310 """
311 311 if util.safehasattr(repo, '_tagscache'): # since 2.0 (or 9dca7653b525)
312 312 # in this case, setattr(repo, '_tagscache', None) or so isn't
313 313 # correct way to clear tags cache, because existing code paths
314 314 # expect _tagscache to be a structured object.
315 315 def clearcache():
316 316 # _tagscache has been filteredpropertycache since 2.5 (or
317 317 # 98c867ac1330), and delattr() can't work in such case
318 318 if '_tagscache' in vars(repo):
319 319 del repo.__dict__['_tagscache']
320 320 return clearcache
321 321
322 322 repotags = safeattrsetter(repo, '_tags', ignoremissing=True)
323 323 if repotags: # since 1.4 (or 5614a628d173)
324 324 return lambda : repotags.set(None)
325 325
326 326 repotagscache = safeattrsetter(repo, 'tagscache', ignoremissing=True)
327 327 if repotagscache: # since 0.6 (or d7df759d0e97)
328 328 return lambda : repotagscache.set(None)
329 329
330 330 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
331 331 # this point, but it isn't so problematic, because:
332 332 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
333 333 # in perftags() causes failure soon
334 334 # - perf.py itself has been available since 1.1 (or eb240755386d)
335 335 raise error.Abort(("tags API of this hg command is unknown"))
336 336
337 337 # perf commands
338 338
339 339 @command('perfwalk', formatteropts)
340 340 def perfwalk(ui, repo, *pats, **opts):
341 341 timer, fm = gettimer(ui, opts)
342 342 try:
343 343 m = scmutil.match(repo[None], pats, {})
344 344 timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
345 345 except Exception:
346 346 try:
347 347 m = scmutil.match(repo[None], pats, {})
348 348 timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
349 349 except Exception:
350 350 timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
351 351 fm.end()
352 352
353 353 @command('perfannotate', formatteropts)
354 354 def perfannotate(ui, repo, f, **opts):
355 355 timer, fm = gettimer(ui, opts)
356 356 fc = repo['.'][f]
357 357 timer(lambda: len(fc.annotate(True)))
358 358 fm.end()
359 359
360 360 @command('perfstatus',
361 361 [('u', 'unknown', False,
362 362 'ask status to look for unknown files')] + formatteropts)
363 363 def perfstatus(ui, repo, **opts):
364 364 #m = match.always(repo.root, repo.getcwd())
365 365 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
366 366 # False))))
367 367 timer, fm = gettimer(ui, opts)
368 368 timer(lambda: sum(map(len, repo.status(unknown=opts['unknown']))))
369 369 fm.end()
370 370
371 371 @command('perfaddremove', formatteropts)
372 372 def perfaddremove(ui, repo, **opts):
373 373 timer, fm = gettimer(ui, opts)
374 374 try:
375 375 oldquiet = repo.ui.quiet
376 376 repo.ui.quiet = True
377 377 matcher = scmutil.match(repo[None])
378 378 timer(lambda: scmutil.addremove(repo, matcher, "", dry_run=True))
379 379 finally:
380 380 repo.ui.quiet = oldquiet
381 381 fm.end()
382 382
383 383 def clearcaches(cl):
384 384 # behave somewhat consistently across internal API changes
385 385 if util.safehasattr(cl, 'clearcaches'):
386 386 cl.clearcaches()
387 387 elif util.safehasattr(cl, '_nodecache'):
388 388 from mercurial.node import nullid, nullrev
389 389 cl._nodecache = {nullid: nullrev}
390 390 cl._nodepos = None
391 391
392 392 @command('perfheads', formatteropts)
393 393 def perfheads(ui, repo, **opts):
394 394 timer, fm = gettimer(ui, opts)
395 395 cl = repo.changelog
396 396 def d():
397 397 len(cl.headrevs())
398 398 clearcaches(cl)
399 399 timer(d)
400 400 fm.end()
401 401
402 402 @command('perftags', formatteropts)
403 403 def perftags(ui, repo, **opts):
404 404 import mercurial.changelog
405 405 import mercurial.manifest
406 406 timer, fm = gettimer(ui, opts)
407 407 svfs = getsvfs(repo)
408 408 repocleartagscache = repocleartagscachefunc(repo)
409 409 def t():
410 410 repo.changelog = mercurial.changelog.changelog(svfs)
411 411 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo)
412 412 repocleartagscache()
413 413 return len(repo.tags())
414 414 timer(t)
415 415 fm.end()
416 416
417 417 @command('perfancestors', formatteropts)
418 418 def perfancestors(ui, repo, **opts):
419 419 timer, fm = gettimer(ui, opts)
420 420 heads = repo.changelog.headrevs()
421 421 def d():
422 422 for a in repo.changelog.ancestors(heads):
423 423 pass
424 424 timer(d)
425 425 fm.end()
426 426
427 427 @command('perfancestorset', formatteropts)
428 428 def perfancestorset(ui, repo, revset, **opts):
429 429 timer, fm = gettimer(ui, opts)
430 430 revs = repo.revs(revset)
431 431 heads = repo.changelog.headrevs()
432 432 def d():
433 433 s = repo.changelog.ancestors(heads)
434 434 for rev in revs:
435 435 rev in s
436 436 timer(d)
437 437 fm.end()
438 438
439 439 @command('perfchangegroupchangelog', formatteropts +
440 440 [('', 'version', '02', 'changegroup version'),
441 441 ('r', 'rev', '', 'revisions to add to changegroup')])
442 442 def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
443 443 """Benchmark producing a changelog group for a changegroup.
444 444
445 445 This measures the time spent processing the changelog during a
446 446 bundle operation. This occurs during `hg bundle` and on a server
447 447 processing a `getbundle` wire protocol request (handles clones
448 448 and pull requests).
449 449
450 450 By default, all revisions are added to the changegroup.
451 451 """
452 452 cl = repo.changelog
453 453 revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
454 454 bundler = changegroup.getbundler(version, repo)
455 455
456 456 def lookup(node):
457 457 # The real bundler reads the revision in order to access the
458 458 # manifest node and files list. Do that here.
459 459 cl.read(node)
460 460 return node
461 461
462 462 def d():
463 463 for chunk in bundler.group(revs, cl, lookup):
464 464 pass
465 465
466 466 timer, fm = gettimer(ui, opts)
467 467 timer(d)
468 468 fm.end()
469 469
470 470 @command('perfdirs', formatteropts)
471 471 def perfdirs(ui, repo, **opts):
472 472 timer, fm = gettimer(ui, opts)
473 473 dirstate = repo.dirstate
474 474 'a' in dirstate
475 475 def d():
476 476 dirstate.dirs()
477 477 del dirstate._dirs
478 478 timer(d)
479 479 fm.end()
480 480
481 481 @command('perfdirstate', formatteropts)
482 482 def perfdirstate(ui, repo, **opts):
483 483 timer, fm = gettimer(ui, opts)
484 484 "a" in repo.dirstate
485 485 def d():
486 486 repo.dirstate.invalidate()
487 487 "a" in repo.dirstate
488 488 timer(d)
489 489 fm.end()
490 490
491 491 @command('perfdirstatedirs', formatteropts)
492 492 def perfdirstatedirs(ui, repo, **opts):
493 493 timer, fm = gettimer(ui, opts)
494 494 "a" in repo.dirstate
495 495 def d():
496 496 "a" in repo.dirstate._dirs
497 497 del repo.dirstate._dirs
498 498 timer(d)
499 499 fm.end()
500 500
501 501 @command('perfdirstatefoldmap', formatteropts)
502 502 def perfdirstatefoldmap(ui, repo, **opts):
503 503 timer, fm = gettimer(ui, opts)
504 504 dirstate = repo.dirstate
505 505 'a' in dirstate
506 506 def d():
507 507 dirstate._filefoldmap.get('a')
508 508 del dirstate._filefoldmap
509 509 timer(d)
510 510 fm.end()
511 511
512 512 @command('perfdirfoldmap', formatteropts)
513 513 def perfdirfoldmap(ui, repo, **opts):
514 514 timer, fm = gettimer(ui, opts)
515 515 dirstate = repo.dirstate
516 516 'a' in dirstate
517 517 def d():
518 518 dirstate._dirfoldmap.get('a')
519 519 del dirstate._dirfoldmap
520 520 del dirstate._dirs
521 521 timer(d)
522 522 fm.end()
523 523
524 524 @command('perfdirstatewrite', formatteropts)
525 525 def perfdirstatewrite(ui, repo, **opts):
526 526 timer, fm = gettimer(ui, opts)
527 527 ds = repo.dirstate
528 528 "a" in ds
529 529 def d():
530 530 ds._dirty = True
531 531 ds.write(repo.currenttransaction())
532 532 timer(d)
533 533 fm.end()
534 534
535 535 @command('perfmergecalculate',
536 536 [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
537 537 def perfmergecalculate(ui, repo, rev, **opts):
538 538 timer, fm = gettimer(ui, opts)
539 539 wctx = repo[None]
540 540 rctx = scmutil.revsingle(repo, rev, rev)
541 541 ancestor = wctx.ancestor(rctx)
542 542 # we don't want working dir files to be stat'd in the benchmark, so prime
543 543 # that cache
544 544 wctx.dirty()
545 545 def d():
546 546 # acceptremote is True because we don't want prompts in the middle of
547 547 # our benchmark
548 548 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
549 549 acceptremote=True, followcopies=True)
550 550 timer(d)
551 551 fm.end()
552 552
553 553 @command('perfpathcopies', [], "REV REV")
554 554 def perfpathcopies(ui, repo, rev1, rev2, **opts):
555 555 timer, fm = gettimer(ui, opts)
556 556 ctx1 = scmutil.revsingle(repo, rev1, rev1)
557 557 ctx2 = scmutil.revsingle(repo, rev2, rev2)
558 558 def d():
559 559 copies.pathcopies(ctx1, ctx2)
560 560 timer(d)
561 561 fm.end()
562 562
563 563 @command('perfmanifest', [], 'REV')
564 564 def perfmanifest(ui, repo, rev, **opts):
565 565 timer, fm = gettimer(ui, opts)
566 566 ctx = scmutil.revsingle(repo, rev, rev)
567 567 t = ctx.manifestnode()
568 568 def d():
569 repo.manifest.clearcaches()
569 repo.manifestlog.clearcaches()
570 570 repo.manifestlog[t].read()
571 571 timer(d)
572 572 fm.end()
573 573
574 574 @command('perfchangeset', formatteropts)
575 575 def perfchangeset(ui, repo, rev, **opts):
576 576 timer, fm = gettimer(ui, opts)
577 577 n = repo[rev].node()
578 578 def d():
579 579 repo.changelog.read(n)
580 580 #repo.changelog._cache = None
581 581 timer(d)
582 582 fm.end()
583 583
584 584 @command('perfindex', formatteropts)
585 585 def perfindex(ui, repo, **opts):
586 586 import mercurial.revlog
587 587 timer, fm = gettimer(ui, opts)
588 588 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
589 589 n = repo["tip"].node()
590 590 svfs = getsvfs(repo)
591 591 def d():
592 592 cl = mercurial.revlog.revlog(svfs, "00changelog.i")
593 593 cl.rev(n)
594 594 timer(d)
595 595 fm.end()
596 596
597 597 @command('perfstartup', formatteropts)
598 598 def perfstartup(ui, repo, **opts):
599 599 timer, fm = gettimer(ui, opts)
600 600 cmd = sys.argv[0]
601 601 def d():
602 602 if os.name != 'nt':
603 603 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
604 604 else:
605 605 os.environ['HGRCPATH'] = ''
606 606 os.system("%s version -q > NUL" % cmd)
607 607 timer(d)
608 608 fm.end()
609 609
610 610 @command('perfparents', formatteropts)
611 611 def perfparents(ui, repo, **opts):
612 612 timer, fm = gettimer(ui, opts)
613 613 # control the number of commits perfparents iterates over
614 614 # experimental config: perf.parentscount
615 615 count = getint(ui, "perf", "parentscount", 1000)
616 616 if len(repo.changelog) < count:
617 617 raise error.Abort("repo needs %d commits for this test" % count)
618 618 repo = repo.unfiltered()
619 619 nl = [repo.changelog.node(i) for i in xrange(count)]
620 620 def d():
621 621 for n in nl:
622 622 repo.changelog.parents(n)
623 623 timer(d)
624 624 fm.end()
625 625
626 626 @command('perfctxfiles', formatteropts)
627 627 def perfctxfiles(ui, repo, x, **opts):
628 628 x = int(x)
629 629 timer, fm = gettimer(ui, opts)
630 630 def d():
631 631 len(repo[x].files())
632 632 timer(d)
633 633 fm.end()
634 634
635 635 @command('perfrawfiles', formatteropts)
636 636 def perfrawfiles(ui, repo, x, **opts):
637 637 x = int(x)
638 638 timer, fm = gettimer(ui, opts)
639 639 cl = repo.changelog
640 640 def d():
641 641 len(cl.read(x)[3])
642 642 timer(d)
643 643 fm.end()
644 644
645 645 @command('perflookup', formatteropts)
646 646 def perflookup(ui, repo, rev, **opts):
647 647 timer, fm = gettimer(ui, opts)
648 648 timer(lambda: len(repo.lookup(rev)))
649 649 fm.end()
650 650
651 651 @command('perfrevrange', formatteropts)
652 652 def perfrevrange(ui, repo, *specs, **opts):
653 653 timer, fm = gettimer(ui, opts)
654 654 revrange = scmutil.revrange
655 655 timer(lambda: len(revrange(repo, specs)))
656 656 fm.end()
657 657
658 658 @command('perfnodelookup', formatteropts)
659 659 def perfnodelookup(ui, repo, rev, **opts):
660 660 timer, fm = gettimer(ui, opts)
661 661 import mercurial.revlog
662 662 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
663 663 n = repo[rev].node()
664 664 cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i")
665 665 def d():
666 666 cl.rev(n)
667 667 clearcaches(cl)
668 668 timer(d)
669 669 fm.end()
670 670
671 671 @command('perflog',
672 672 [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
673 673 def perflog(ui, repo, rev=None, **opts):
674 674 if rev is None:
675 675 rev=[]
676 676 timer, fm = gettimer(ui, opts)
677 677 ui.pushbuffer()
678 678 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
679 679 copies=opts.get('rename')))
680 680 ui.popbuffer()
681 681 fm.end()
682 682
683 683 @command('perfmoonwalk', formatteropts)
684 684 def perfmoonwalk(ui, repo, **opts):
685 685 """benchmark walking the changelog backwards
686 686
687 687 This also loads the changelog data for each revision in the changelog.
688 688 """
689 689 timer, fm = gettimer(ui, opts)
690 690 def moonwalk():
691 691 for i in xrange(len(repo), -1, -1):
692 692 ctx = repo[i]
693 693 ctx.branch() # read changelog data (in addition to the index)
694 694 timer(moonwalk)
695 695 fm.end()
696 696
697 697 @command('perftemplating', formatteropts)
698 698 def perftemplating(ui, repo, rev=None, **opts):
699 699 if rev is None:
700 700 rev=[]
701 701 timer, fm = gettimer(ui, opts)
702 702 ui.pushbuffer()
703 703 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
704 704 template='{date|shortdate} [{rev}:{node|short}]'
705 705 ' {author|person}: {desc|firstline}\n'))
706 706 ui.popbuffer()
707 707 fm.end()
708 708
709 709 @command('perfcca', formatteropts)
710 710 def perfcca(ui, repo, **opts):
711 711 timer, fm = gettimer(ui, opts)
712 712 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
713 713 fm.end()
714 714
715 715 @command('perffncacheload', formatteropts)
716 716 def perffncacheload(ui, repo, **opts):
717 717 timer, fm = gettimer(ui, opts)
718 718 s = repo.store
719 719 def d():
720 720 s.fncache._load()
721 721 timer(d)
722 722 fm.end()
723 723
724 724 @command('perffncachewrite', formatteropts)
725 725 def perffncachewrite(ui, repo, **opts):
726 726 timer, fm = gettimer(ui, opts)
727 727 s = repo.store
728 728 s.fncache._load()
729 729 lock = repo.lock()
730 730 tr = repo.transaction('perffncachewrite')
731 731 def d():
732 732 s.fncache._dirty = True
733 733 s.fncache.write(tr)
734 734 timer(d)
735 735 tr.close()
736 736 lock.release()
737 737 fm.end()
738 738
739 739 @command('perffncacheencode', formatteropts)
740 740 def perffncacheencode(ui, repo, **opts):
741 741 timer, fm = gettimer(ui, opts)
742 742 s = repo.store
743 743 s.fncache._load()
744 744 def d():
745 745 for p in s.fncache.entries:
746 746 s.encode(p)
747 747 timer(d)
748 748 fm.end()
749 749
750 750 @command('perfbdiff', revlogopts + formatteropts + [
751 751 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
752 752 ('', 'alldata', False, 'test bdiffs for all associated revisions')],
753 753 '-c|-m|FILE REV')
754 754 def perfbdiff(ui, repo, file_, rev=None, count=None, **opts):
755 755 """benchmark a bdiff between revisions
756 756
757 757 By default, benchmark a bdiff between its delta parent and itself.
758 758
759 759 With ``--count``, benchmark bdiffs between delta parents and self for N
760 760 revisions starting at the specified revision.
761 761
762 762 With ``--alldata``, assume the requested revision is a changeset and
763 763 measure bdiffs for all changes related to that changeset (manifest
764 764 and filelogs).
765 765 """
766 766 if opts['alldata']:
767 767 opts['changelog'] = True
768 768
769 769 if opts.get('changelog') or opts.get('manifest'):
770 770 file_, rev = None, file_
771 771 elif rev is None:
772 772 raise error.CommandError('perfbdiff', 'invalid arguments')
773 773
774 774 textpairs = []
775 775
776 776 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
777 777
778 778 startrev = r.rev(r.lookup(rev))
779 779 for rev in range(startrev, min(startrev + count, len(r) - 1)):
780 780 if opts['alldata']:
781 781 # Load revisions associated with changeset.
782 782 ctx = repo[rev]
783 783 mtext = repo.manifest.revision(ctx.manifestnode())
784 784 for pctx in ctx.parents():
785 785 pman = repo.manifest.revision(pctx.manifestnode())
786 786 textpairs.append((pman, mtext))
787 787
788 788 # Load filelog revisions by iterating manifest delta.
789 789 man = ctx.manifest()
790 790 pman = ctx.p1().manifest()
791 791 for filename, change in pman.diff(man).items():
792 792 fctx = repo.file(filename)
793 793 f1 = fctx.revision(change[0][0] or -1)
794 794 f2 = fctx.revision(change[1][0] or -1)
795 795 textpairs.append((f1, f2))
796 796 else:
797 797 dp = r.deltaparent(rev)
798 798 textpairs.append((r.revision(dp), r.revision(rev)))
799 799
800 800 def d():
801 801 for pair in textpairs:
802 802 bdiff.bdiff(*pair)
803 803
804 804 timer, fm = gettimer(ui, opts)
805 805 timer(d)
806 806 fm.end()
807 807
808 808 @command('perfdiffwd', formatteropts)
809 809 def perfdiffwd(ui, repo, **opts):
810 810 """Profile diff of working directory changes"""
811 811 timer, fm = gettimer(ui, opts)
812 812 options = {
813 813 'w': 'ignore_all_space',
814 814 'b': 'ignore_space_change',
815 815 'B': 'ignore_blank_lines',
816 816 }
817 817
818 818 for diffopt in ('', 'w', 'b', 'B', 'wB'):
819 819 opts = dict((options[c], '1') for c in diffopt)
820 820 def d():
821 821 ui.pushbuffer()
822 822 commands.diff(ui, repo, **opts)
823 823 ui.popbuffer()
824 824 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
825 825 timer(d, title)
826 826 fm.end()
827 827
828 828 @command('perfrevlog', revlogopts + formatteropts +
829 829 [('d', 'dist', 100, 'distance between the revisions'),
830 830 ('s', 'startrev', 0, 'revision to start reading at'),
831 831 ('', 'reverse', False, 'read in reverse')],
832 832 '-c|-m|FILE')
833 833 def perfrevlog(ui, repo, file_=None, startrev=0, reverse=False, **opts):
834 834 """Benchmark reading a series of revisions from a revlog.
835 835
836 836 By default, we read every ``-d/--dist`` revision from 0 to tip of
837 837 the specified revlog.
838 838
839 839 The start revision can be defined via ``-s/--startrev``.
840 840 """
841 841 timer, fm = gettimer(ui, opts)
842 842 _len = getlen(ui)
843 843
844 844 def d():
845 845 r = cmdutil.openrevlog(repo, 'perfrevlog', file_, opts)
846 846
847 847 startrev = 0
848 848 endrev = _len(r)
849 849 dist = opts['dist']
850 850
851 851 if reverse:
852 852 startrev, endrev = endrev, startrev
853 853 dist = -1 * dist
854 854
855 855 for x in xrange(startrev, endrev, dist):
856 856 r.revision(r.node(x))
857 857
858 858 timer(d)
859 859 fm.end()
860 860
861 861 @command('perfrevlogrevision', revlogopts + formatteropts +
862 862 [('', 'cache', False, 'use caches instead of clearing')],
863 863 '-c|-m|FILE REV')
864 864 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
865 865 """Benchmark obtaining a revlog revision.
866 866
867 867 Obtaining a revlog revision consists of roughly the following steps:
868 868
869 869 1. Compute the delta chain
870 870 2. Obtain the raw chunks for that delta chain
871 871 3. Decompress each raw chunk
872 872 4. Apply binary patches to obtain fulltext
873 873 5. Verify hash of fulltext
874 874
875 875 This command measures the time spent in each of these phases.
876 876 """
877 877 if opts.get('changelog') or opts.get('manifest'):
878 878 file_, rev = None, file_
879 879 elif rev is None:
880 880 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
881 881
882 882 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
883 883 node = r.lookup(rev)
884 884 rev = r.rev(node)
885 885
886 886 def dodeltachain(rev):
887 887 if not cache:
888 888 r.clearcaches()
889 889 r._deltachain(rev)
890 890
891 891 def doread(chain):
892 892 if not cache:
893 893 r.clearcaches()
894 894 r._chunkraw(chain[0], chain[-1])
895 895
896 896 def dodecompress(data, chain):
897 897 if not cache:
898 898 r.clearcaches()
899 899
900 900 start = r.start
901 901 length = r.length
902 902 inline = r._inline
903 903 iosize = r._io.size
904 904 buffer = util.buffer
905 905 offset = start(chain[0])
906 906
907 907 for rev in chain:
908 908 chunkstart = start(rev)
909 909 if inline:
910 910 chunkstart += (rev + 1) * iosize
911 911 chunklength = length(rev)
912 912 b = buffer(data, chunkstart - offset, chunklength)
913 913 revlog.decompress(b)
914 914
915 915 def dopatch(text, bins):
916 916 if not cache:
917 917 r.clearcaches()
918 918 mdiff.patches(text, bins)
919 919
920 920 def dohash(text):
921 921 if not cache:
922 922 r.clearcaches()
923 923 r._checkhash(text, node, rev)
924 924
925 925 def dorevision():
926 926 if not cache:
927 927 r.clearcaches()
928 928 r.revision(node)
929 929
930 930 chain = r._deltachain(rev)[0]
931 931 data = r._chunkraw(chain[0], chain[-1])[1]
932 932 bins = r._chunks(chain)
933 933 text = str(bins[0])
934 934 bins = bins[1:]
935 935 text = mdiff.patches(text, bins)
936 936
937 937 benches = [
938 938 (lambda: dorevision(), 'full'),
939 939 (lambda: dodeltachain(rev), 'deltachain'),
940 940 (lambda: doread(chain), 'read'),
941 941 (lambda: dodecompress(data, chain), 'decompress'),
942 942 (lambda: dopatch(text, bins), 'patch'),
943 943 (lambda: dohash(text), 'hash'),
944 944 ]
945 945
946 946 for fn, title in benches:
947 947 timer, fm = gettimer(ui, opts)
948 948 timer(fn, title=title)
949 949 fm.end()
950 950
951 951 @command('perfrevset',
952 952 [('C', 'clear', False, 'clear volatile cache between each call.'),
953 953 ('', 'contexts', False, 'obtain changectx for each revision')]
954 954 + formatteropts, "REVSET")
955 955 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
956 956 """benchmark the execution time of a revset
957 957
958 958 Use the --clean option if need to evaluate the impact of build volatile
959 959 revisions set cache on the revset execution. Volatile cache hold filtered
960 960 and obsolete related cache."""
961 961 timer, fm = gettimer(ui, opts)
962 962 def d():
963 963 if clear:
964 964 repo.invalidatevolatilesets()
965 965 if contexts:
966 966 for ctx in repo.set(expr): pass
967 967 else:
968 968 for r in repo.revs(expr): pass
969 969 timer(d)
970 970 fm.end()
971 971
972 972 @command('perfvolatilesets', formatteropts)
973 973 def perfvolatilesets(ui, repo, *names, **opts):
974 974 """benchmark the computation of various volatile set
975 975
976 976 Volatile set computes element related to filtering and obsolescence."""
977 977 timer, fm = gettimer(ui, opts)
978 978 repo = repo.unfiltered()
979 979
980 980 def getobs(name):
981 981 def d():
982 982 repo.invalidatevolatilesets()
983 983 obsolete.getrevs(repo, name)
984 984 return d
985 985
986 986 allobs = sorted(obsolete.cachefuncs)
987 987 if names:
988 988 allobs = [n for n in allobs if n in names]
989 989
990 990 for name in allobs:
991 991 timer(getobs(name), title=name)
992 992
993 993 def getfiltered(name):
994 994 def d():
995 995 repo.invalidatevolatilesets()
996 996 repoview.filterrevs(repo, name)
997 997 return d
998 998
999 999 allfilter = sorted(repoview.filtertable)
1000 1000 if names:
1001 1001 allfilter = [n for n in allfilter if n in names]
1002 1002
1003 1003 for name in allfilter:
1004 1004 timer(getfiltered(name), title=name)
1005 1005 fm.end()
1006 1006
1007 1007 @command('perfbranchmap',
1008 1008 [('f', 'full', False,
1009 1009 'Includes build time of subset'),
1010 1010 ] + formatteropts)
1011 1011 def perfbranchmap(ui, repo, full=False, **opts):
1012 1012 """benchmark the update of a branchmap
1013 1013
1014 1014 This benchmarks the full repo.branchmap() call with read and write disabled
1015 1015 """
1016 1016 timer, fm = gettimer(ui, opts)
1017 1017 def getbranchmap(filtername):
1018 1018 """generate a benchmark function for the filtername"""
1019 1019 if filtername is None:
1020 1020 view = repo
1021 1021 else:
1022 1022 view = repo.filtered(filtername)
1023 1023 def d():
1024 1024 if full:
1025 1025 view._branchcaches.clear()
1026 1026 else:
1027 1027 view._branchcaches.pop(filtername, None)
1028 1028 view.branchmap()
1029 1029 return d
1030 1030 # add filter in smaller subset to bigger subset
1031 1031 possiblefilters = set(repoview.filtertable)
1032 1032 subsettable = getbranchmapsubsettable()
1033 1033 allfilters = []
1034 1034 while possiblefilters:
1035 1035 for name in possiblefilters:
1036 1036 subset = subsettable.get(name)
1037 1037 if subset not in possiblefilters:
1038 1038 break
1039 1039 else:
1040 1040 assert False, 'subset cycle %s!' % possiblefilters
1041 1041 allfilters.append(name)
1042 1042 possiblefilters.remove(name)
1043 1043
1044 1044 # warm the cache
1045 1045 if not full:
1046 1046 for name in allfilters:
1047 1047 repo.filtered(name).branchmap()
1048 1048 # add unfiltered
1049 1049 allfilters.append(None)
1050 1050
1051 1051 branchcacheread = safeattrsetter(branchmap, 'read')
1052 1052 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1053 1053 branchcacheread.set(lambda repo: None)
1054 1054 branchcachewrite.set(lambda bc, repo: None)
1055 1055 try:
1056 1056 for name in allfilters:
1057 1057 timer(getbranchmap(name), title=str(name))
1058 1058 finally:
1059 1059 branchcacheread.restore()
1060 1060 branchcachewrite.restore()
1061 1061 fm.end()
1062 1062
1063 1063 @command('perfloadmarkers')
1064 1064 def perfloadmarkers(ui, repo):
1065 1065 """benchmark the time to parse the on-disk markers for a repo
1066 1066
1067 1067 Result is the number of markers in the repo."""
1068 1068 timer, fm = gettimer(ui)
1069 1069 svfs = getsvfs(repo)
1070 1070 timer(lambda: len(obsolete.obsstore(svfs)))
1071 1071 fm.end()
1072 1072
1073 1073 @command('perflrucachedict', formatteropts +
1074 1074 [('', 'size', 4, 'size of cache'),
1075 1075 ('', 'gets', 10000, 'number of key lookups'),
1076 1076 ('', 'sets', 10000, 'number of key sets'),
1077 1077 ('', 'mixed', 10000, 'number of mixed mode operations'),
1078 1078 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1079 1079 norepo=True)
1080 1080 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1081 1081 mixedgetfreq=50, **opts):
1082 1082 def doinit():
1083 1083 for i in xrange(10000):
1084 1084 util.lrucachedict(size)
1085 1085
1086 1086 values = []
1087 1087 for i in xrange(size):
1088 1088 values.append(random.randint(0, sys.maxint))
1089 1089
1090 1090 # Get mode fills the cache and tests raw lookup performance with no
1091 1091 # eviction.
1092 1092 getseq = []
1093 1093 for i in xrange(gets):
1094 1094 getseq.append(random.choice(values))
1095 1095
1096 1096 def dogets():
1097 1097 d = util.lrucachedict(size)
1098 1098 for v in values:
1099 1099 d[v] = v
1100 1100 for key in getseq:
1101 1101 value = d[key]
1102 1102 value # silence pyflakes warning
1103 1103
1104 1104 # Set mode tests insertion speed with cache eviction.
1105 1105 setseq = []
1106 1106 for i in xrange(sets):
1107 1107 setseq.append(random.randint(0, sys.maxint))
1108 1108
1109 1109 def dosets():
1110 1110 d = util.lrucachedict(size)
1111 1111 for v in setseq:
1112 1112 d[v] = v
1113 1113
1114 1114 # Mixed mode randomly performs gets and sets with eviction.
1115 1115 mixedops = []
1116 1116 for i in xrange(mixed):
1117 1117 r = random.randint(0, 100)
1118 1118 if r < mixedgetfreq:
1119 1119 op = 0
1120 1120 else:
1121 1121 op = 1
1122 1122
1123 1123 mixedops.append((op, random.randint(0, size * 2)))
1124 1124
1125 1125 def domixed():
1126 1126 d = util.lrucachedict(size)
1127 1127
1128 1128 for op, v in mixedops:
1129 1129 if op == 0:
1130 1130 try:
1131 1131 d[v]
1132 1132 except KeyError:
1133 1133 pass
1134 1134 else:
1135 1135 d[v] = v
1136 1136
1137 1137 benches = [
1138 1138 (doinit, 'init'),
1139 1139 (dogets, 'gets'),
1140 1140 (dosets, 'sets'),
1141 1141 (domixed, 'mixed')
1142 1142 ]
1143 1143
1144 1144 for fn, title in benches:
1145 1145 timer, fm = gettimer(ui, opts)
1146 1146 timer(fn, title=title)
1147 1147 fm.end()
1148 1148
1149 1149 def uisetup(ui):
1150 1150 if (util.safehasattr(cmdutil, 'openrevlog') and
1151 1151 not util.safehasattr(commands, 'debugrevlogopts')):
1152 1152 # for "historical portability":
1153 1153 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1154 1154 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1155 1155 # openrevlog() should cause failure, because it has been
1156 1156 # available since 3.5 (or 49c583ca48c4).
1157 1157 def openrevlog(orig, repo, cmd, file_, opts):
1158 1158 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1159 1159 raise error.Abort("This version doesn't support --dir option",
1160 1160 hint="use 3.5 or later")
1161 1161 return orig(repo, cmd, file_, opts)
1162 1162 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
@@ -1,1599 +1,1599
1 1 # manifest.py - manifest revision class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import array
11 11 import heapq
12 12 import os
13 13 import struct
14 14
15 15 from .i18n import _
16 16 from . import (
17 17 error,
18 18 mdiff,
19 19 parsers,
20 20 revlog,
21 21 util,
22 22 )
23 23
24 24 propertycache = util.propertycache
25 25
26 26 def _parsev1(data):
27 27 # This method does a little bit of excessive-looking
28 28 # precondition checking. This is so that the behavior of this
29 29 # class exactly matches its C counterpart to try and help
30 30 # prevent surprise breakage for anyone that develops against
31 31 # the pure version.
32 32 if data and data[-1] != '\n':
33 33 raise ValueError('Manifest did not end in a newline.')
34 34 prev = None
35 35 for l in data.splitlines():
36 36 if prev is not None and prev > l:
37 37 raise ValueError('Manifest lines not in sorted order.')
38 38 prev = l
39 39 f, n = l.split('\0')
40 40 if len(n) > 40:
41 41 yield f, revlog.bin(n[:40]), n[40:]
42 42 else:
43 43 yield f, revlog.bin(n), ''
44 44
45 45 def _parsev2(data):
46 46 metadataend = data.find('\n')
47 47 # Just ignore metadata for now
48 48 pos = metadataend + 1
49 49 prevf = ''
50 50 while pos < len(data):
51 51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
52 52 if end == -1:
53 53 raise ValueError('Manifest ended with incomplete file entry.')
54 54 stemlen = ord(data[pos])
55 55 items = data[pos + 1:end].split('\0')
56 56 f = prevf[:stemlen] + items[0]
57 57 if prevf > f:
58 58 raise ValueError('Manifest entries not in sorted order.')
59 59 fl = items[1]
60 60 # Just ignore metadata (items[2:] for now)
61 61 n = data[end + 1:end + 21]
62 62 yield f, n, fl
63 63 pos = end + 22
64 64 prevf = f
65 65
66 66 def _parse(data):
67 67 """Generates (path, node, flags) tuples from a manifest text"""
68 68 if data.startswith('\0'):
69 69 return iter(_parsev2(data))
70 70 else:
71 71 return iter(_parsev1(data))
72 72
73 73 def _text(it, usemanifestv2):
74 74 """Given an iterator over (path, node, flags) tuples, returns a manifest
75 75 text"""
76 76 if usemanifestv2:
77 77 return _textv2(it)
78 78 else:
79 79 return _textv1(it)
80 80
81 81 def _textv1(it):
82 82 files = []
83 83 lines = []
84 84 _hex = revlog.hex
85 85 for f, n, fl in it:
86 86 files.append(f)
87 87 # if this is changed to support newlines in filenames,
88 88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
89 89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
90 90
91 91 _checkforbidden(files)
92 92 return ''.join(lines)
93 93
94 94 def _textv2(it):
95 95 files = []
96 96 lines = ['\0\n']
97 97 prevf = ''
98 98 for f, n, fl in it:
99 99 files.append(f)
100 100 stem = os.path.commonprefix([prevf, f])
101 101 stemlen = min(len(stem), 255)
102 102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
103 103 prevf = f
104 104 _checkforbidden(files)
105 105 return ''.join(lines)
106 106
107 107 class lazymanifestiter(object):
108 108 def __init__(self, lm):
109 109 self.pos = 0
110 110 self.lm = lm
111 111
112 112 def __iter__(self):
113 113 return self
114 114
115 115 def next(self):
116 116 try:
117 117 data, pos = self.lm._get(self.pos)
118 118 except IndexError:
119 119 raise StopIteration
120 120 if pos == -1:
121 121 self.pos += 1
122 122 return data[0]
123 123 self.pos += 1
124 124 zeropos = data.find('\x00', pos)
125 125 return data[pos:zeropos]
126 126
127 127 class lazymanifestiterentries(object):
128 128 def __init__(self, lm):
129 129 self.lm = lm
130 130 self.pos = 0
131 131
132 132 def __iter__(self):
133 133 return self
134 134
135 135 def next(self):
136 136 try:
137 137 data, pos = self.lm._get(self.pos)
138 138 except IndexError:
139 139 raise StopIteration
140 140 if pos == -1:
141 141 self.pos += 1
142 142 return data
143 143 zeropos = data.find('\x00', pos)
144 144 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
145 145 zeropos + 1, 40)
146 146 flags = self.lm._getflags(data, self.pos, zeropos)
147 147 self.pos += 1
148 148 return (data[pos:zeropos], hashval, flags)
149 149
150 150 def unhexlify(data, extra, pos, length):
151 151 s = data[pos:pos + length].decode('hex')
152 152 if extra:
153 153 s += chr(extra & 0xff)
154 154 return s
155 155
156 156 def _cmp(a, b):
157 157 return (a > b) - (a < b)
158 158
159 159 class _lazymanifest(object):
160 160 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
161 161 if positions is None:
162 162 self.positions = self.findlines(data)
163 163 self.extrainfo = [0] * len(self.positions)
164 164 self.data = data
165 165 self.extradata = []
166 166 else:
167 167 self.positions = positions[:]
168 168 self.extrainfo = extrainfo[:]
169 169 self.extradata = extradata[:]
170 170 self.data = data
171 171
172 172 def findlines(self, data):
173 173 if not data:
174 174 return []
175 175 pos = data.find("\n")
176 176 if pos == -1 or data[-1] != '\n':
177 177 raise ValueError("Manifest did not end in a newline.")
178 178 positions = [0]
179 179 prev = data[:data.find('\x00')]
180 180 while pos < len(data) - 1 and pos != -1:
181 181 positions.append(pos + 1)
182 182 nexts = data[pos + 1:data.find('\x00', pos + 1)]
183 183 if nexts < prev:
184 184 raise ValueError("Manifest lines not in sorted order.")
185 185 prev = nexts
186 186 pos = data.find("\n", pos + 1)
187 187 return positions
188 188
189 189 def _get(self, index):
190 190 # get the position encoded in pos:
191 191 # positive number is an index in 'data'
192 192 # negative number is in extrapieces
193 193 pos = self.positions[index]
194 194 if pos >= 0:
195 195 return self.data, pos
196 196 return self.extradata[-pos - 1], -1
197 197
198 198 def _getkey(self, pos):
199 199 if pos >= 0:
200 200 return self.data[pos:self.data.find('\x00', pos + 1)]
201 201 return self.extradata[-pos - 1][0]
202 202
203 203 def bsearch(self, key):
204 204 first = 0
205 205 last = len(self.positions) - 1
206 206
207 207 while first <= last:
208 208 midpoint = (first + last)//2
209 209 nextpos = self.positions[midpoint]
210 210 candidate = self._getkey(nextpos)
211 211 r = _cmp(key, candidate)
212 212 if r == 0:
213 213 return midpoint
214 214 else:
215 215 if r < 0:
216 216 last = midpoint - 1
217 217 else:
218 218 first = midpoint + 1
219 219 return -1
220 220
221 221 def bsearch2(self, key):
222 222 # same as the above, but will always return the position
223 223 # done for performance reasons
224 224 first = 0
225 225 last = len(self.positions) - 1
226 226
227 227 while first <= last:
228 228 midpoint = (first + last)//2
229 229 nextpos = self.positions[midpoint]
230 230 candidate = self._getkey(nextpos)
231 231 r = _cmp(key, candidate)
232 232 if r == 0:
233 233 return (midpoint, True)
234 234 else:
235 235 if r < 0:
236 236 last = midpoint - 1
237 237 else:
238 238 first = midpoint + 1
239 239 return (first, False)
240 240
241 241 def __contains__(self, key):
242 242 return self.bsearch(key) != -1
243 243
244 244 def _getflags(self, data, needle, pos):
245 245 start = pos + 41
246 246 end = data.find("\n", start)
247 247 if end == -1:
248 248 end = len(data) - 1
249 249 if start == end:
250 250 return ''
251 251 return self.data[start:end]
252 252
253 253 def __getitem__(self, key):
254 254 if not isinstance(key, str):
255 255 raise TypeError("getitem: manifest keys must be a string.")
256 256 needle = self.bsearch(key)
257 257 if needle == -1:
258 258 raise KeyError
259 259 data, pos = self._get(needle)
260 260 if pos == -1:
261 261 return (data[1], data[2])
262 262 zeropos = data.find('\x00', pos)
263 263 assert 0 <= needle <= len(self.positions)
264 264 assert len(self.extrainfo) == len(self.positions)
265 265 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
266 266 flags = self._getflags(data, needle, zeropos)
267 267 return (hashval, flags)
268 268
269 269 def __delitem__(self, key):
270 270 needle, found = self.bsearch2(key)
271 271 if not found:
272 272 raise KeyError
273 273 cur = self.positions[needle]
274 274 self.positions = self.positions[:needle] + self.positions[needle + 1:]
275 275 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
276 276 if cur >= 0:
277 277 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
278 278
279 279 def __setitem__(self, key, value):
280 280 if not isinstance(key, str):
281 281 raise TypeError("setitem: manifest keys must be a string.")
282 282 if not isinstance(value, tuple) or len(value) != 2:
283 283 raise TypeError("Manifest values must be a tuple of (node, flags).")
284 284 hashval = value[0]
285 285 if not isinstance(hashval, str) or not 20 <= len(hashval) <= 22:
286 286 raise TypeError("node must be a 20-byte string")
287 287 flags = value[1]
288 288 if len(hashval) == 22:
289 289 hashval = hashval[:-1]
290 290 if not isinstance(flags, str) or len(flags) > 1:
291 291 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
292 292 needle, found = self.bsearch2(key)
293 293 if found:
294 294 # put the item
295 295 pos = self.positions[needle]
296 296 if pos < 0:
297 297 self.extradata[-pos - 1] = (key, hashval, value[1])
298 298 else:
299 299 # just don't bother
300 300 self.extradata.append((key, hashval, value[1]))
301 301 self.positions[needle] = -len(self.extradata)
302 302 else:
303 303 # not found, put it in with extra positions
304 304 self.extradata.append((key, hashval, value[1]))
305 305 self.positions = (self.positions[:needle] + [-len(self.extradata)]
306 306 + self.positions[needle:])
307 307 self.extrainfo = (self.extrainfo[:needle] + [0] +
308 308 self.extrainfo[needle:])
309 309
310 310 def copy(self):
311 311 # XXX call _compact like in C?
312 312 return _lazymanifest(self.data, self.positions, self.extrainfo,
313 313 self.extradata)
314 314
315 315 def _compact(self):
316 316 # hopefully not called TOO often
317 317 if len(self.extradata) == 0:
318 318 return
319 319 l = []
320 320 last_cut = 0
321 321 i = 0
322 322 offset = 0
323 323 self.extrainfo = [0] * len(self.positions)
324 324 while i < len(self.positions):
325 325 if self.positions[i] >= 0:
326 326 cur = self.positions[i]
327 327 last_cut = cur
328 328 while True:
329 329 self.positions[i] = offset
330 330 i += 1
331 331 if i == len(self.positions) or self.positions[i] < 0:
332 332 break
333 333 offset += self.positions[i] - cur
334 334 cur = self.positions[i]
335 335 end_cut = self.data.find('\n', cur)
336 336 if end_cut != -1:
337 337 end_cut += 1
338 338 offset += end_cut - cur
339 339 l.append(self.data[last_cut:end_cut])
340 340 else:
341 341 while i < len(self.positions) and self.positions[i] < 0:
342 342 cur = self.positions[i]
343 343 t = self.extradata[-cur - 1]
344 344 l.append(self._pack(t))
345 345 self.positions[i] = offset
346 346 if len(t[1]) > 20:
347 347 self.extrainfo[i] = ord(t[1][21])
348 348 offset += len(l[-1])
349 349 i += 1
350 350 self.data = ''.join(l)
351 351 self.extradata = []
352 352
353 353 def _pack(self, d):
354 354 return d[0] + '\x00' + d[1][:20].encode('hex') + d[2] + '\n'
355 355
356 356 def text(self):
357 357 self._compact()
358 358 return self.data
359 359
360 360 def diff(self, m2, clean=False):
361 361 '''Finds changes between the current manifest and m2.'''
362 362 # XXX think whether efficiency matters here
363 363 diff = {}
364 364
365 365 for fn, e1, flags in self.iterentries():
366 366 if fn not in m2:
367 367 diff[fn] = (e1, flags), (None, '')
368 368 else:
369 369 e2 = m2[fn]
370 370 if (e1, flags) != e2:
371 371 diff[fn] = (e1, flags), e2
372 372 elif clean:
373 373 diff[fn] = None
374 374
375 375 for fn, e2, flags in m2.iterentries():
376 376 if fn not in self:
377 377 diff[fn] = (None, ''), (e2, flags)
378 378
379 379 return diff
380 380
381 381 def iterentries(self):
382 382 return lazymanifestiterentries(self)
383 383
384 384 def iterkeys(self):
385 385 return lazymanifestiter(self)
386 386
387 387 def __iter__(self):
388 388 return lazymanifestiter(self)
389 389
390 390 def __len__(self):
391 391 return len(self.positions)
392 392
393 393 def filtercopy(self, filterfn):
394 394 # XXX should be optimized
395 395 c = _lazymanifest('')
396 396 for f, n, fl in self.iterentries():
397 397 if filterfn(f):
398 398 c[f] = n, fl
399 399 return c
400 400
401 401 try:
402 402 _lazymanifest = parsers.lazymanifest
403 403 except AttributeError:
404 404 pass
405 405
406 406 class manifestdict(object):
407 407 def __init__(self, data=''):
408 408 if data.startswith('\0'):
409 409 #_lazymanifest can not parse v2
410 410 self._lm = _lazymanifest('')
411 411 for f, n, fl in _parsev2(data):
412 412 self._lm[f] = n, fl
413 413 else:
414 414 self._lm = _lazymanifest(data)
415 415
416 416 def __getitem__(self, key):
417 417 return self._lm[key][0]
418 418
419 419 def find(self, key):
420 420 return self._lm[key]
421 421
422 422 def __len__(self):
423 423 return len(self._lm)
424 424
425 425 def __nonzero__(self):
426 426 # nonzero is covered by the __len__ function, but implementing it here
427 427 # makes it easier for extensions to override.
428 428 return len(self._lm) != 0
429 429
430 430 def __setitem__(self, key, node):
431 431 self._lm[key] = node, self.flags(key, '')
432 432
433 433 def __contains__(self, key):
434 434 return key in self._lm
435 435
436 436 def __delitem__(self, key):
437 437 del self._lm[key]
438 438
439 439 def __iter__(self):
440 440 return self._lm.__iter__()
441 441
442 442 def iterkeys(self):
443 443 return self._lm.iterkeys()
444 444
445 445 def keys(self):
446 446 return list(self.iterkeys())
447 447
448 448 def filesnotin(self, m2):
449 449 '''Set of files in this manifest that are not in the other'''
450 450 diff = self.diff(m2)
451 451 files = set(filepath
452 452 for filepath, hashflags in diff.iteritems()
453 453 if hashflags[1][0] is None)
454 454 return files
455 455
456 456 @propertycache
457 457 def _dirs(self):
458 458 return util.dirs(self)
459 459
460 460 def dirs(self):
461 461 return self._dirs
462 462
463 463 def hasdir(self, dir):
464 464 return dir in self._dirs
465 465
466 466 def _filesfastpath(self, match):
467 467 '''Checks whether we can correctly and quickly iterate over matcher
468 468 files instead of over manifest files.'''
469 469 files = match.files()
470 470 return (len(files) < 100 and (match.isexact() or
471 471 (match.prefix() and all(fn in self for fn in files))))
472 472
473 473 def walk(self, match):
474 474 '''Generates matching file names.
475 475
476 476 Equivalent to manifest.matches(match).iterkeys(), but without creating
477 477 an entirely new manifest.
478 478
479 479 It also reports nonexistent files by marking them bad with match.bad().
480 480 '''
481 481 if match.always():
482 482 for f in iter(self):
483 483 yield f
484 484 return
485 485
486 486 fset = set(match.files())
487 487
488 488 # avoid the entire walk if we're only looking for specific files
489 489 if self._filesfastpath(match):
490 490 for fn in sorted(fset):
491 491 yield fn
492 492 return
493 493
494 494 for fn in self:
495 495 if fn in fset:
496 496 # specified pattern is the exact name
497 497 fset.remove(fn)
498 498 if match(fn):
499 499 yield fn
500 500
501 501 # for dirstate.walk, files=['.'] means "walk the whole tree".
502 502 # follow that here, too
503 503 fset.discard('.')
504 504
505 505 for fn in sorted(fset):
506 506 if not self.hasdir(fn):
507 507 match.bad(fn, None)
508 508
509 509 def matches(self, match):
510 510 '''generate a new manifest filtered by the match argument'''
511 511 if match.always():
512 512 return self.copy()
513 513
514 514 if self._filesfastpath(match):
515 515 m = manifestdict()
516 516 lm = self._lm
517 517 for fn in match.files():
518 518 if fn in lm:
519 519 m._lm[fn] = lm[fn]
520 520 return m
521 521
522 522 m = manifestdict()
523 523 m._lm = self._lm.filtercopy(match)
524 524 return m
525 525
526 526 def diff(self, m2, clean=False):
527 527 '''Finds changes between the current manifest and m2.
528 528
529 529 Args:
530 530 m2: the manifest to which this manifest should be compared.
531 531 clean: if true, include files unchanged between these manifests
532 532 with a None value in the returned dictionary.
533 533
534 534 The result is returned as a dict with filename as key and
535 535 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
536 536 nodeid in the current/other manifest and fl1/fl2 is the flag
537 537 in the current/other manifest. Where the file does not exist,
538 538 the nodeid will be None and the flags will be the empty
539 539 string.
540 540 '''
541 541 return self._lm.diff(m2._lm, clean)
542 542
543 543 def setflag(self, key, flag):
544 544 self._lm[key] = self[key], flag
545 545
546 546 def get(self, key, default=None):
547 547 try:
548 548 return self._lm[key][0]
549 549 except KeyError:
550 550 return default
551 551
552 552 def flags(self, key, default=''):
553 553 try:
554 554 return self._lm[key][1]
555 555 except KeyError:
556 556 return default
557 557
558 558 def copy(self):
559 559 c = manifestdict()
560 560 c._lm = self._lm.copy()
561 561 return c
562 562
563 563 def iteritems(self):
564 564 return (x[:2] for x in self._lm.iterentries())
565 565
566 566 def iterentries(self):
567 567 return self._lm.iterentries()
568 568
569 569 def text(self, usemanifestv2=False):
570 570 if usemanifestv2:
571 571 return _textv2(self._lm.iterentries())
572 572 else:
573 573 # use (probably) native version for v1
574 574 return self._lm.text()
575 575
576 576 def fastdelta(self, base, changes):
577 577 """Given a base manifest text as an array.array and a list of changes
578 578 relative to that text, compute a delta that can be used by revlog.
579 579 """
580 580 delta = []
581 581 dstart = None
582 582 dend = None
583 583 dline = [""]
584 584 start = 0
585 585 # zero copy representation of base as a buffer
586 586 addbuf = util.buffer(base)
587 587
588 588 changes = list(changes)
589 589 if len(changes) < 1000:
590 590 # start with a readonly loop that finds the offset of
591 591 # each line and creates the deltas
592 592 for f, todelete in changes:
593 593 # bs will either be the index of the item or the insert point
594 594 start, end = _msearch(addbuf, f, start)
595 595 if not todelete:
596 596 h, fl = self._lm[f]
597 597 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
598 598 else:
599 599 if start == end:
600 600 # item we want to delete was not found, error out
601 601 raise AssertionError(
602 602 _("failed to remove %s from manifest") % f)
603 603 l = ""
604 604 if dstart is not None and dstart <= start and dend >= start:
605 605 if dend < end:
606 606 dend = end
607 607 if l:
608 608 dline.append(l)
609 609 else:
610 610 if dstart is not None:
611 611 delta.append([dstart, dend, "".join(dline)])
612 612 dstart = start
613 613 dend = end
614 614 dline = [l]
615 615
616 616 if dstart is not None:
617 617 delta.append([dstart, dend, "".join(dline)])
618 618 # apply the delta to the base, and get a delta for addrevision
619 619 deltatext, arraytext = _addlistdelta(base, delta)
620 620 else:
621 621 # For large changes, it's much cheaper to just build the text and
622 622 # diff it.
623 623 arraytext = array.array('c', self.text())
624 624 deltatext = mdiff.textdiff(base, arraytext)
625 625
626 626 return arraytext, deltatext
627 627
628 628 def _msearch(m, s, lo=0, hi=None):
629 629 '''return a tuple (start, end) that says where to find s within m.
630 630
631 631 If the string is found m[start:end] are the line containing
632 632 that string. If start == end the string was not found and
633 633 they indicate the proper sorted insertion point.
634 634
635 635 m should be a buffer or a string
636 636 s is a string'''
637 637 def advance(i, c):
638 638 while i < lenm and m[i] != c:
639 639 i += 1
640 640 return i
641 641 if not s:
642 642 return (lo, lo)
643 643 lenm = len(m)
644 644 if not hi:
645 645 hi = lenm
646 646 while lo < hi:
647 647 mid = (lo + hi) // 2
648 648 start = mid
649 649 while start > 0 and m[start - 1] != '\n':
650 650 start -= 1
651 651 end = advance(start, '\0')
652 652 if m[start:end] < s:
653 653 # we know that after the null there are 40 bytes of sha1
654 654 # this translates to the bisect lo = mid + 1
655 655 lo = advance(end + 40, '\n') + 1
656 656 else:
657 657 # this translates to the bisect hi = mid
658 658 hi = start
659 659 end = advance(lo, '\0')
660 660 found = m[lo:end]
661 661 if s == found:
662 662 # we know that after the null there are 40 bytes of sha1
663 663 end = advance(end + 40, '\n')
664 664 return (lo, end + 1)
665 665 else:
666 666 return (lo, lo)
667 667
668 668 def _checkforbidden(l):
669 669 """Check filenames for illegal characters."""
670 670 for f in l:
671 671 if '\n' in f or '\r' in f:
672 672 raise error.RevlogError(
673 673 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
674 674
675 675
676 676 # apply the changes collected during the bisect loop to our addlist
677 677 # return a delta suitable for addrevision
678 678 def _addlistdelta(addlist, x):
679 679 # for large addlist arrays, building a new array is cheaper
680 680 # than repeatedly modifying the existing one
681 681 currentposition = 0
682 682 newaddlist = array.array('c')
683 683
684 684 for start, end, content in x:
685 685 newaddlist += addlist[currentposition:start]
686 686 if content:
687 687 newaddlist += array.array('c', content)
688 688
689 689 currentposition = end
690 690
691 691 newaddlist += addlist[currentposition:]
692 692
693 693 deltatext = "".join(struct.pack(">lll", start, end, len(content))
694 694 + content for start, end, content in x)
695 695 return deltatext, newaddlist
696 696
697 697 def _splittopdir(f):
698 698 if '/' in f:
699 699 dir, subpath = f.split('/', 1)
700 700 return dir + '/', subpath
701 701 else:
702 702 return '', f
703 703
704 704 _noop = lambda s: None
705 705
706 706 class treemanifest(object):
707 707 def __init__(self, dir='', text=''):
708 708 self._dir = dir
709 709 self._node = revlog.nullid
710 710 self._loadfunc = _noop
711 711 self._copyfunc = _noop
712 712 self._dirty = False
713 713 self._dirs = {}
714 714 # Using _lazymanifest here is a little slower than plain old dicts
715 715 self._files = {}
716 716 self._flags = {}
717 717 if text:
718 718 def readsubtree(subdir, subm):
719 719 raise AssertionError('treemanifest constructor only accepts '
720 720 'flat manifests')
721 721 self.parse(text, readsubtree)
722 722 self._dirty = True # Mark flat manifest dirty after parsing
723 723
724 724 def _subpath(self, path):
725 725 return self._dir + path
726 726
727 727 def __len__(self):
728 728 self._load()
729 729 size = len(self._files)
730 730 for m in self._dirs.values():
731 731 size += m.__len__()
732 732 return size
733 733
734 734 def _isempty(self):
735 735 self._load() # for consistency; already loaded by all callers
736 736 return (not self._files and (not self._dirs or
737 737 all(m._isempty() for m in self._dirs.values())))
738 738
739 739 def __repr__(self):
740 740 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
741 741 (self._dir, revlog.hex(self._node),
742 742 bool(self._loadfunc is _noop),
743 743 self._dirty, id(self)))
744 744
745 745 def dir(self):
746 746 '''The directory that this tree manifest represents, including a
747 747 trailing '/'. Empty string for the repo root directory.'''
748 748 return self._dir
749 749
750 750 def node(self):
751 751 '''This node of this instance. nullid for unsaved instances. Should
752 752 be updated when the instance is read or written from a revlog.
753 753 '''
754 754 assert not self._dirty
755 755 return self._node
756 756
757 757 def setnode(self, node):
758 758 self._node = node
759 759 self._dirty = False
760 760
761 761 def iterentries(self):
762 762 self._load()
763 763 for p, n in sorted(self._dirs.items() + self._files.items()):
764 764 if p in self._files:
765 765 yield self._subpath(p), n, self._flags.get(p, '')
766 766 else:
767 767 for x in n.iterentries():
768 768 yield x
769 769
770 770 def iteritems(self):
771 771 self._load()
772 772 for p, n in sorted(self._dirs.items() + self._files.items()):
773 773 if p in self._files:
774 774 yield self._subpath(p), n
775 775 else:
776 776 for f, sn in n.iteritems():
777 777 yield f, sn
778 778
779 779 def iterkeys(self):
780 780 self._load()
781 781 for p in sorted(self._dirs.keys() + self._files.keys()):
782 782 if p in self._files:
783 783 yield self._subpath(p)
784 784 else:
785 785 for f in self._dirs[p].iterkeys():
786 786 yield f
787 787
788 788 def keys(self):
789 789 return list(self.iterkeys())
790 790
791 791 def __iter__(self):
792 792 return self.iterkeys()
793 793
794 794 def __contains__(self, f):
795 795 if f is None:
796 796 return False
797 797 self._load()
798 798 dir, subpath = _splittopdir(f)
799 799 if dir:
800 800 if dir not in self._dirs:
801 801 return False
802 802 return self._dirs[dir].__contains__(subpath)
803 803 else:
804 804 return f in self._files
805 805
806 806 def get(self, f, default=None):
807 807 self._load()
808 808 dir, subpath = _splittopdir(f)
809 809 if dir:
810 810 if dir not in self._dirs:
811 811 return default
812 812 return self._dirs[dir].get(subpath, default)
813 813 else:
814 814 return self._files.get(f, default)
815 815
816 816 def __getitem__(self, f):
817 817 self._load()
818 818 dir, subpath = _splittopdir(f)
819 819 if dir:
820 820 return self._dirs[dir].__getitem__(subpath)
821 821 else:
822 822 return self._files[f]
823 823
824 824 def flags(self, f):
825 825 self._load()
826 826 dir, subpath = _splittopdir(f)
827 827 if dir:
828 828 if dir not in self._dirs:
829 829 return ''
830 830 return self._dirs[dir].flags(subpath)
831 831 else:
832 832 if f in self._dirs:
833 833 return ''
834 834 return self._flags.get(f, '')
835 835
836 836 def find(self, f):
837 837 self._load()
838 838 dir, subpath = _splittopdir(f)
839 839 if dir:
840 840 return self._dirs[dir].find(subpath)
841 841 else:
842 842 return self._files[f], self._flags.get(f, '')
843 843
844 844 def __delitem__(self, f):
845 845 self._load()
846 846 dir, subpath = _splittopdir(f)
847 847 if dir:
848 848 self._dirs[dir].__delitem__(subpath)
849 849 # If the directory is now empty, remove it
850 850 if self._dirs[dir]._isempty():
851 851 del self._dirs[dir]
852 852 else:
853 853 del self._files[f]
854 854 if f in self._flags:
855 855 del self._flags[f]
856 856 self._dirty = True
857 857
858 858 def __setitem__(self, f, n):
859 859 assert n is not None
860 860 self._load()
861 861 dir, subpath = _splittopdir(f)
862 862 if dir:
863 863 if dir not in self._dirs:
864 864 self._dirs[dir] = treemanifest(self._subpath(dir))
865 865 self._dirs[dir].__setitem__(subpath, n)
866 866 else:
867 867 self._files[f] = n[:21] # to match manifestdict's behavior
868 868 self._dirty = True
869 869
870 870 def _load(self):
871 871 if self._loadfunc is not _noop:
872 872 lf, self._loadfunc = self._loadfunc, _noop
873 873 lf(self)
874 874 elif self._copyfunc is not _noop:
875 875 cf, self._copyfunc = self._copyfunc, _noop
876 876 cf(self)
877 877
878 878 def setflag(self, f, flags):
879 879 """Set the flags (symlink, executable) for path f."""
880 880 self._load()
881 881 dir, subpath = _splittopdir(f)
882 882 if dir:
883 883 if dir not in self._dirs:
884 884 self._dirs[dir] = treemanifest(self._subpath(dir))
885 885 self._dirs[dir].setflag(subpath, flags)
886 886 else:
887 887 self._flags[f] = flags
888 888 self._dirty = True
889 889
890 890 def copy(self):
891 891 copy = treemanifest(self._dir)
892 892 copy._node = self._node
893 893 copy._dirty = self._dirty
894 894 if self._copyfunc is _noop:
895 895 def _copyfunc(s):
896 896 self._load()
897 897 for d in self._dirs:
898 898 s._dirs[d] = self._dirs[d].copy()
899 899 s._files = dict.copy(self._files)
900 900 s._flags = dict.copy(self._flags)
901 901 if self._loadfunc is _noop:
902 902 _copyfunc(copy)
903 903 else:
904 904 copy._copyfunc = _copyfunc
905 905 else:
906 906 copy._copyfunc = self._copyfunc
907 907 return copy
908 908
909 909 def filesnotin(self, m2):
910 910 '''Set of files in this manifest that are not in the other'''
911 911 files = set()
912 912 def _filesnotin(t1, t2):
913 913 if t1._node == t2._node and not t1._dirty and not t2._dirty:
914 914 return
915 915 t1._load()
916 916 t2._load()
917 917 for d, m1 in t1._dirs.iteritems():
918 918 if d in t2._dirs:
919 919 m2 = t2._dirs[d]
920 920 _filesnotin(m1, m2)
921 921 else:
922 922 files.update(m1.iterkeys())
923 923
924 924 for fn in t1._files.iterkeys():
925 925 if fn not in t2._files:
926 926 files.add(t1._subpath(fn))
927 927
928 928 _filesnotin(self, m2)
929 929 return files
930 930
931 931 @propertycache
932 932 def _alldirs(self):
933 933 return util.dirs(self)
934 934
935 935 def dirs(self):
936 936 return self._alldirs
937 937
938 938 def hasdir(self, dir):
939 939 self._load()
940 940 topdir, subdir = _splittopdir(dir)
941 941 if topdir:
942 942 if topdir in self._dirs:
943 943 return self._dirs[topdir].hasdir(subdir)
944 944 return False
945 945 return (dir + '/') in self._dirs
946 946
947 947 def walk(self, match):
948 948 '''Generates matching file names.
949 949
950 950 Equivalent to manifest.matches(match).iterkeys(), but without creating
951 951 an entirely new manifest.
952 952
953 953 It also reports nonexistent files by marking them bad with match.bad().
954 954 '''
955 955 if match.always():
956 956 for f in iter(self):
957 957 yield f
958 958 return
959 959
960 960 fset = set(match.files())
961 961
962 962 for fn in self._walk(match):
963 963 if fn in fset:
964 964 # specified pattern is the exact name
965 965 fset.remove(fn)
966 966 yield fn
967 967
968 968 # for dirstate.walk, files=['.'] means "walk the whole tree".
969 969 # follow that here, too
970 970 fset.discard('.')
971 971
972 972 for fn in sorted(fset):
973 973 if not self.hasdir(fn):
974 974 match.bad(fn, None)
975 975
976 976 def _walk(self, match):
977 977 '''Recursively generates matching file names for walk().'''
978 978 if not match.visitdir(self._dir[:-1] or '.'):
979 979 return
980 980
981 981 # yield this dir's files and walk its submanifests
982 982 self._load()
983 983 for p in sorted(self._dirs.keys() + self._files.keys()):
984 984 if p in self._files:
985 985 fullp = self._subpath(p)
986 986 if match(fullp):
987 987 yield fullp
988 988 else:
989 989 for f in self._dirs[p]._walk(match):
990 990 yield f
991 991
992 992 def matches(self, match):
993 993 '''generate a new manifest filtered by the match argument'''
994 994 if match.always():
995 995 return self.copy()
996 996
997 997 return self._matches(match)
998 998
999 999 def _matches(self, match):
1000 1000 '''recursively generate a new manifest filtered by the match argument.
1001 1001 '''
1002 1002
1003 1003 visit = match.visitdir(self._dir[:-1] or '.')
1004 1004 if visit == 'all':
1005 1005 return self.copy()
1006 1006 ret = treemanifest(self._dir)
1007 1007 if not visit:
1008 1008 return ret
1009 1009
1010 1010 self._load()
1011 1011 for fn in self._files:
1012 1012 fullp = self._subpath(fn)
1013 1013 if not match(fullp):
1014 1014 continue
1015 1015 ret._files[fn] = self._files[fn]
1016 1016 if fn in self._flags:
1017 1017 ret._flags[fn] = self._flags[fn]
1018 1018
1019 1019 for dir, subm in self._dirs.iteritems():
1020 1020 m = subm._matches(match)
1021 1021 if not m._isempty():
1022 1022 ret._dirs[dir] = m
1023 1023
1024 1024 if not ret._isempty():
1025 1025 ret._dirty = True
1026 1026 return ret
1027 1027
1028 1028 def diff(self, m2, clean=False):
1029 1029 '''Finds changes between the current manifest and m2.
1030 1030
1031 1031 Args:
1032 1032 m2: the manifest to which this manifest should be compared.
1033 1033 clean: if true, include files unchanged between these manifests
1034 1034 with a None value in the returned dictionary.
1035 1035
1036 1036 The result is returned as a dict with filename as key and
1037 1037 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1038 1038 nodeid in the current/other manifest and fl1/fl2 is the flag
1039 1039 in the current/other manifest. Where the file does not exist,
1040 1040 the nodeid will be None and the flags will be the empty
1041 1041 string.
1042 1042 '''
1043 1043 result = {}
1044 1044 emptytree = treemanifest()
1045 1045 def _diff(t1, t2):
1046 1046 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1047 1047 return
1048 1048 t1._load()
1049 1049 t2._load()
1050 1050 for d, m1 in t1._dirs.iteritems():
1051 1051 m2 = t2._dirs.get(d, emptytree)
1052 1052 _diff(m1, m2)
1053 1053
1054 1054 for d, m2 in t2._dirs.iteritems():
1055 1055 if d not in t1._dirs:
1056 1056 _diff(emptytree, m2)
1057 1057
1058 1058 for fn, n1 in t1._files.iteritems():
1059 1059 fl1 = t1._flags.get(fn, '')
1060 1060 n2 = t2._files.get(fn, None)
1061 1061 fl2 = t2._flags.get(fn, '')
1062 1062 if n1 != n2 or fl1 != fl2:
1063 1063 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1064 1064 elif clean:
1065 1065 result[t1._subpath(fn)] = None
1066 1066
1067 1067 for fn, n2 in t2._files.iteritems():
1068 1068 if fn not in t1._files:
1069 1069 fl2 = t2._flags.get(fn, '')
1070 1070 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1071 1071
1072 1072 _diff(self, m2)
1073 1073 return result
1074 1074
1075 1075 def unmodifiedsince(self, m2):
1076 1076 return not self._dirty and not m2._dirty and self._node == m2._node
1077 1077
1078 1078 def parse(self, text, readsubtree):
1079 1079 for f, n, fl in _parse(text):
1080 1080 if fl == 't':
1081 1081 f = f + '/'
1082 1082 self._dirs[f] = readsubtree(self._subpath(f), n)
1083 1083 elif '/' in f:
1084 1084 # This is a flat manifest, so use __setitem__ and setflag rather
1085 1085 # than assigning directly to _files and _flags, so we can
1086 1086 # assign a path in a subdirectory, and to mark dirty (compared
1087 1087 # to nullid).
1088 1088 self[f] = n
1089 1089 if fl:
1090 1090 self.setflag(f, fl)
1091 1091 else:
1092 1092 # Assigning to _files and _flags avoids marking as dirty,
1093 1093 # and should be a little faster.
1094 1094 self._files[f] = n
1095 1095 if fl:
1096 1096 self._flags[f] = fl
1097 1097
1098 1098 def text(self, usemanifestv2=False):
1099 1099 """Get the full data of this manifest as a bytestring."""
1100 1100 self._load()
1101 1101 return _text(self.iterentries(), usemanifestv2)
1102 1102
1103 1103 def dirtext(self, usemanifestv2=False):
1104 1104 """Get the full data of this directory as a bytestring. Make sure that
1105 1105 any submanifests have been written first, so their nodeids are correct.
1106 1106 """
1107 1107 self._load()
1108 1108 flags = self.flags
1109 1109 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1110 1110 files = [(f, self._files[f], flags(f)) for f in self._files]
1111 1111 return _text(sorted(dirs + files), usemanifestv2)
1112 1112
1113 1113 def read(self, gettext, readsubtree):
1114 1114 def _load_for_read(s):
1115 1115 s.parse(gettext(), readsubtree)
1116 1116 s._dirty = False
1117 1117 self._loadfunc = _load_for_read
1118 1118
1119 1119 def writesubtrees(self, m1, m2, writesubtree):
1120 1120 self._load() # for consistency; should never have any effect here
1121 1121 m1._load()
1122 1122 m2._load()
1123 1123 emptytree = treemanifest()
1124 1124 for d, subm in self._dirs.iteritems():
1125 1125 subp1 = m1._dirs.get(d, emptytree)._node
1126 1126 subp2 = m2._dirs.get(d, emptytree)._node
1127 1127 if subp1 == revlog.nullid:
1128 1128 subp1, subp2 = subp2, subp1
1129 1129 writesubtree(subm, subp1, subp2)
1130 1130
1131 1131 class manifestrevlog(revlog.revlog):
1132 1132 '''A revlog that stores manifest texts. This is responsible for caching the
1133 1133 full-text manifest contents.
1134 1134 '''
1135 1135 def __init__(self, opener, dir='', dirlogcache=None):
1136 1136 # During normal operations, we expect to deal with not more than four
1137 1137 # revs at a time (such as during commit --amend). When rebasing large
1138 1138 # stacks of commits, the number can go up, hence the config knob below.
1139 1139 cachesize = 4
1140 1140 usetreemanifest = False
1141 1141 usemanifestv2 = False
1142 1142 opts = getattr(opener, 'options', None)
1143 1143 if opts is not None:
1144 1144 cachesize = opts.get('manifestcachesize', cachesize)
1145 1145 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1146 1146 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1147 1147
1148 1148 self._treeondisk = usetreemanifest
1149 1149 self._usemanifestv2 = usemanifestv2
1150 1150
1151 1151 self._fulltextcache = util.lrucachedict(cachesize)
1152 1152
1153 1153 indexfile = "00manifest.i"
1154 1154 if dir:
1155 1155 assert self._treeondisk, 'opts is %r' % opts
1156 1156 if not dir.endswith('/'):
1157 1157 dir = dir + '/'
1158 1158 indexfile = "meta/" + dir + "00manifest.i"
1159 1159 self._dir = dir
1160 1160 # The dirlogcache is kept on the root manifest log
1161 1161 if dir:
1162 1162 self._dirlogcache = dirlogcache
1163 1163 else:
1164 1164 self._dirlogcache = {'': self}
1165 1165
1166 1166 super(manifestrevlog, self).__init__(opener, indexfile,
1167 1167 checkambig=bool(dir))
1168 1168
1169 1169 @property
1170 1170 def fulltextcache(self):
1171 1171 return self._fulltextcache
1172 1172
1173 1173 def clearcaches(self):
1174 1174 super(manifestrevlog, self).clearcaches()
1175 1175 self._fulltextcache.clear()
1176 1176 self._dirlogcache = {'': self}
1177 1177
1178 1178 def dirlog(self, dir):
1179 1179 if dir:
1180 1180 assert self._treeondisk
1181 1181 if dir not in self._dirlogcache:
1182 1182 self._dirlogcache[dir] = manifestrevlog(self.opener, dir,
1183 1183 self._dirlogcache)
1184 1184 return self._dirlogcache[dir]
1185 1185
1186 1186 def add(self, m, transaction, link, p1, p2, added, removed, readtree=None):
1187 1187 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1188 1188 and not self._usemanifestv2):
1189 1189 # If our first parent is in the manifest cache, we can
1190 1190 # compute a delta here using properties we know about the
1191 1191 # manifest up-front, which may save time later for the
1192 1192 # revlog layer.
1193 1193
1194 1194 _checkforbidden(added)
1195 1195 # combine the changed lists into one sorted iterator
1196 1196 work = heapq.merge([(x, False) for x in added],
1197 1197 [(x, True) for x in removed])
1198 1198
1199 1199 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1200 1200 cachedelta = self.rev(p1), deltatext
1201 1201 text = util.buffer(arraytext)
1202 1202 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1203 1203 else:
1204 1204 # The first parent manifest isn't already loaded, so we'll
1205 1205 # just encode a fulltext of the manifest and pass that
1206 1206 # through to the revlog layer, and let it handle the delta
1207 1207 # process.
1208 1208 if self._treeondisk:
1209 1209 assert readtree, "readtree must be set for treemanifest writes"
1210 1210 m1 = readtree(self._dir, p1)
1211 1211 m2 = readtree(self._dir, p2)
1212 1212 n = self._addtree(m, transaction, link, m1, m2, readtree)
1213 1213 arraytext = None
1214 1214 else:
1215 1215 text = m.text(self._usemanifestv2)
1216 1216 n = self.addrevision(text, transaction, link, p1, p2)
1217 1217 arraytext = array.array('c', text)
1218 1218
1219 1219 if arraytext is not None:
1220 1220 self.fulltextcache[n] = arraytext
1221 1221
1222 1222 return n
1223 1223
1224 1224 def _addtree(self, m, transaction, link, m1, m2, readtree):
1225 1225 # If the manifest is unchanged compared to one parent,
1226 1226 # don't write a new revision
1227 1227 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1228 1228 return m.node()
1229 1229 def writesubtree(subm, subp1, subp2):
1230 1230 sublog = self.dirlog(subm.dir())
1231 1231 sublog.add(subm, transaction, link, subp1, subp2, None, None,
1232 1232 readtree=readtree)
1233 1233 m.writesubtrees(m1, m2, writesubtree)
1234 1234 text = m.dirtext(self._usemanifestv2)
1235 1235 # Double-check whether contents are unchanged to one parent
1236 1236 if text == m1.dirtext(self._usemanifestv2):
1237 1237 n = m1.node()
1238 1238 elif text == m2.dirtext(self._usemanifestv2):
1239 1239 n = m2.node()
1240 1240 else:
1241 1241 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1242 1242 # Save nodeid so parent manifest can calculate its nodeid
1243 1243 m.setnode(n)
1244 1244 return n
1245 1245
1246 1246 class manifestlog(object):
1247 1247 """A collection class representing the collection of manifest snapshots
1248 1248 referenced by commits in the repository.
1249 1249
1250 1250 In this situation, 'manifest' refers to the abstract concept of a snapshot
1251 1251 of the list of files in the given commit. Consumers of the output of this
1252 1252 class do not care about the implementation details of the actual manifests
1253 1253 they receive (i.e. tree or flat or lazily loaded, etc)."""
1254 1254 def __init__(self, opener, repo):
1255 1255 self._repo = repo
1256 1256
1257 1257 usetreemanifest = False
1258 1258
1259 1259 opts = getattr(opener, 'options', None)
1260 1260 if opts is not None:
1261 1261 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1262 1262 self._treeinmem = usetreemanifest
1263 1263
1264 1264 self._oldmanifest = repo._constructmanifest()
1265 1265 self._revlog = self._oldmanifest
1266 1266
1267 1267 # A cache of the manifestctx or treemanifestctx for each directory
1268 1268 self._dirmancache = {}
1269 1269
1270 1270 # We'll separate this into it's own cache once oldmanifest is no longer
1271 1271 # used
1272 1272 self._mancache = self._oldmanifest._mancache
1273 1273 self._dirmancache[''] = self._mancache
1274 1274
1275 1275 # A future patch makes this use the same config value as the existing
1276 1276 # mancache
1277 1277 self.cachesize = 4
1278 1278
1279 1279 def __getitem__(self, node):
1280 1280 """Retrieves the manifest instance for the given node. Throws a
1281 1281 LookupError if not found.
1282 1282 """
1283 1283 return self.get('', node)
1284 1284
1285 1285 def get(self, dir, node):
1286 1286 """Retrieves the manifest instance for the given node. Throws a
1287 1287 LookupError if not found.
1288 1288 """
1289 1289 if node in self._dirmancache.get(dir, ()):
1290 1290 cachemf = self._dirmancache[dir][node]
1291 1291 # The old manifest may put non-ctx manifests in the cache, so
1292 1292 # skip those since they don't implement the full api.
1293 1293 if (isinstance(cachemf, manifestctx) or
1294 1294 isinstance(cachemf, treemanifestctx)):
1295 1295 return cachemf
1296 1296
1297 1297 if dir:
1298 1298 if self._revlog._treeondisk:
1299 1299 dirlog = self._revlog.dirlog(dir)
1300 1300 if node not in dirlog.nodemap:
1301 1301 raise LookupError(node, dirlog.indexfile,
1302 1302 _('no node'))
1303 1303 m = treemanifestctx(self._repo, dir, node)
1304 1304 else:
1305 1305 raise error.Abort(
1306 1306 _("cannot ask for manifest directory '%s' in a flat "
1307 1307 "manifest") % dir)
1308 1308 else:
1309 1309 if node not in self._revlog.nodemap:
1310 1310 raise LookupError(node, self._revlog.indexfile,
1311 1311 _('no node'))
1312 1312 if self._treeinmem:
1313 1313 m = treemanifestctx(self._repo, '', node)
1314 1314 else:
1315 1315 m = manifestctx(self._repo, node)
1316 1316
1317 1317 if node != revlog.nullid:
1318 1318 mancache = self._dirmancache.get(dir)
1319 1319 if not mancache:
1320 1320 mancache = util.lrucachedict(self.cachesize)
1321 1321 self._dirmancache[dir] = mancache
1322 1322 mancache[node] = m
1323 1323 return m
1324 1324
1325 def clearcaches(self):
1326 self._dirmancache.clear()
1327 self._revlog.clearcaches()
1328
1325 1329 class memmanifestctx(object):
1326 1330 def __init__(self, repo):
1327 1331 self._repo = repo
1328 1332 self._manifestdict = manifestdict()
1329 1333
1330 1334 def _revlog(self):
1331 1335 return self._repo.manifestlog._revlog
1332 1336
1333 1337 def new(self):
1334 1338 return memmanifestctx(self._repo)
1335 1339
1336 1340 def copy(self):
1337 1341 memmf = memmanifestctx(self._repo)
1338 1342 memmf._manifestdict = self.read().copy()
1339 1343 return memmf
1340 1344
1341 1345 def read(self):
1342 1346 return self._manifestdict
1343 1347
1344 1348 def write(self, transaction, link, p1, p2, added, removed):
1345 1349 return self._revlog().add(self._manifestdict, transaction, link, p1, p2,
1346 1350 added, removed)
1347 1351
1348 1352 class manifestctx(object):
1349 1353 """A class representing a single revision of a manifest, including its
1350 1354 contents, its parent revs, and its linkrev.
1351 1355 """
1352 1356 def __init__(self, repo, node):
1353 1357 self._repo = repo
1354 1358 self._data = None
1355 1359
1356 1360 self._node = node
1357 1361
1358 1362 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1359 1363 # but let's add it later when something needs it and we can load it
1360 1364 # lazily.
1361 1365 #self.p1, self.p2 = revlog.parents(node)
1362 1366 #rev = revlog.rev(node)
1363 1367 #self.linkrev = revlog.linkrev(rev)
1364 1368
1365 1369 def _revlog(self):
1366 1370 return self._repo.manifestlog._revlog
1367 1371
1368 1372 def node(self):
1369 1373 return self._node
1370 1374
1371 1375 def new(self):
1372 1376 return memmanifestctx(self._repo)
1373 1377
1374 1378 def copy(self):
1375 1379 memmf = memmanifestctx(self._repo)
1376 1380 memmf._manifestdict = self.read().copy()
1377 1381 return memmf
1378 1382
1379 1383 def read(self):
1380 1384 if not self._data:
1381 1385 if self._node == revlog.nullid:
1382 1386 self._data = manifestdict()
1383 1387 else:
1384 1388 rl = self._revlog()
1385 1389 text = rl.revision(self._node)
1386 1390 arraytext = array.array('c', text)
1387 1391 rl._fulltextcache[self._node] = arraytext
1388 1392 self._data = manifestdict(text)
1389 1393 return self._data
1390 1394
1391 1395 def readfast(self, shallow=False):
1392 1396 '''Calls either readdelta or read, based on which would be less work.
1393 1397 readdelta is called if the delta is against the p1, and therefore can be
1394 1398 read quickly.
1395 1399
1396 1400 If `shallow` is True, nothing changes since this is a flat manifest.
1397 1401 '''
1398 1402 rl = self._revlog()
1399 1403 r = rl.rev(self._node)
1400 1404 deltaparent = rl.deltaparent(r)
1401 1405 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1402 1406 return self.readdelta()
1403 1407 return self.read()
1404 1408
1405 1409 def readdelta(self, shallow=False):
1406 1410 '''Returns a manifest containing just the entries that are present
1407 1411 in this manifest, but not in its p1 manifest. This is efficient to read
1408 1412 if the revlog delta is already p1.
1409 1413
1410 1414 Changing the value of `shallow` has no effect on flat manifests.
1411 1415 '''
1412 1416 revlog = self._revlog()
1413 1417 if revlog._usemanifestv2:
1414 1418 # Need to perform a slow delta
1415 1419 r0 = revlog.deltaparent(revlog.rev(self._node))
1416 1420 m0 = manifestctx(self._repo, revlog.node(r0)).read()
1417 1421 m1 = self.read()
1418 1422 md = manifestdict()
1419 1423 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1420 1424 if n1:
1421 1425 md[f] = n1
1422 1426 if fl1:
1423 1427 md.setflag(f, fl1)
1424 1428 return md
1425 1429
1426 1430 r = revlog.rev(self._node)
1427 1431 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1428 1432 return manifestdict(d)
1429 1433
1430 1434 def find(self, key):
1431 1435 return self.read().find(key)
1432 1436
1433 1437 class memtreemanifestctx(object):
1434 1438 def __init__(self, repo, dir=''):
1435 1439 self._repo = repo
1436 1440 self._dir = dir
1437 1441 self._treemanifest = treemanifest()
1438 1442
1439 1443 def _revlog(self):
1440 1444 return self._repo.manifestlog._revlog
1441 1445
1442 1446 def new(self, dir=''):
1443 1447 return memtreemanifestctx(self._repo, dir=dir)
1444 1448
1445 1449 def copy(self):
1446 1450 memmf = memtreemanifestctx(self._repo, dir=self._dir)
1447 1451 memmf._treemanifest = self._treemanifest.copy()
1448 1452 return memmf
1449 1453
1450 1454 def read(self):
1451 1455 return self._treemanifest
1452 1456
1453 1457 def write(self, transaction, link, p1, p2, added, removed):
1454 1458 def readtree(dir, node):
1455 1459 return self._repo.manifestlog.get(dir, node).read()
1456 1460 return self._revlog().add(self._treemanifest, transaction, link, p1, p2,
1457 1461 added, removed, readtree=readtree)
1458 1462
1459 1463 class treemanifestctx(object):
1460 1464 def __init__(self, repo, dir, node):
1461 1465 self._repo = repo
1462 1466 self._dir = dir
1463 1467 self._data = None
1464 1468
1465 1469 self._node = node
1466 1470
1467 1471 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1468 1472 # we can instantiate treemanifestctx objects for directories we don't
1469 1473 # have on disk.
1470 1474 #self.p1, self.p2 = revlog.parents(node)
1471 1475 #rev = revlog.rev(node)
1472 1476 #self.linkrev = revlog.linkrev(rev)
1473 1477
1474 1478 def _revlog(self):
1475 1479 return self._repo.manifestlog._revlog.dirlog(self._dir)
1476 1480
1477 1481 def read(self):
1478 1482 if not self._data:
1479 1483 rl = self._revlog()
1480 1484 if self._node == revlog.nullid:
1481 1485 self._data = treemanifest()
1482 1486 elif rl._treeondisk:
1483 1487 m = treemanifest(dir=self._dir)
1484 1488 def gettext():
1485 1489 return rl.revision(self._node)
1486 1490 def readsubtree(dir, subm):
1487 1491 return treemanifestctx(self._repo, dir, subm).read()
1488 1492 m.read(gettext, readsubtree)
1489 1493 m.setnode(self._node)
1490 1494 self._data = m
1491 1495 else:
1492 1496 text = rl.revision(self._node)
1493 1497 arraytext = array.array('c', text)
1494 1498 rl.fulltextcache[self._node] = arraytext
1495 1499 self._data = treemanifest(dir=self._dir, text=text)
1496 1500
1497 1501 return self._data
1498 1502
1499 1503 def node(self):
1500 1504 return self._node
1501 1505
1502 1506 def new(self, dir=''):
1503 1507 return memtreemanifestctx(self._repo, dir=dir)
1504 1508
1505 1509 def copy(self):
1506 1510 memmf = memtreemanifestctx(self._repo, dir=self._dir)
1507 1511 memmf._treemanifest = self.read().copy()
1508 1512 return memmf
1509 1513
1510 1514 def readdelta(self, shallow=False):
1511 1515 '''Returns a manifest containing just the entries that are present
1512 1516 in this manifest, but not in its p1 manifest. This is efficient to read
1513 1517 if the revlog delta is already p1.
1514 1518
1515 1519 If `shallow` is True, this will read the delta for this directory,
1516 1520 without recursively reading subdirectory manifests. Instead, any
1517 1521 subdirectory entry will be reported as it appears in the manifest, i.e.
1518 1522 the subdirectory will be reported among files and distinguished only by
1519 1523 its 't' flag.
1520 1524 '''
1521 1525 revlog = self._revlog()
1522 1526 if shallow and not revlog._usemanifestv2:
1523 1527 r = revlog.rev(self._node)
1524 1528 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1525 1529 return manifestdict(d)
1526 1530 else:
1527 1531 # Need to perform a slow delta
1528 1532 r0 = revlog.deltaparent(revlog.rev(self._node))
1529 1533 m0 = treemanifestctx(self._repo, self._dir, revlog.node(r0)).read()
1530 1534 m1 = self.read()
1531 1535 md = treemanifest(dir=self._dir)
1532 1536 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1533 1537 if n1:
1534 1538 md[f] = n1
1535 1539 if fl1:
1536 1540 md.setflag(f, fl1)
1537 1541 return md
1538 1542
1539 1543 def readfast(self, shallow=False):
1540 1544 '''Calls either readdelta or read, based on which would be less work.
1541 1545 readdelta is called if the delta is against the p1, and therefore can be
1542 1546 read quickly.
1543 1547
1544 1548 If `shallow` is True, it only returns the entries from this manifest,
1545 1549 and not any submanifests.
1546 1550 '''
1547 1551 rl = self._revlog()
1548 1552 r = rl.rev(self._node)
1549 1553 deltaparent = rl.deltaparent(r)
1550 1554 if (deltaparent != revlog.nullrev and
1551 1555 deltaparent in rl.parentrevs(r)):
1552 1556 return self.readdelta(shallow=shallow)
1553 1557
1554 1558 if shallow:
1555 1559 return manifestdict(rl.revision(self._node))
1556 1560 else:
1557 1561 return self.read()
1558 1562
1559 1563 def find(self, key):
1560 1564 return self.read().find(key)
1561 1565
1562 1566 class manifest(manifestrevlog):
1563 1567 def __init__(self, opener, dir='', dirlogcache=None):
1564 1568 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1565 1569 manifest.manifest only. External users should create a root manifest
1566 1570 log with manifest.manifest(opener) and call dirlog() on it.
1567 1571 '''
1568 1572 # During normal operations, we expect to deal with not more than four
1569 1573 # revs at a time (such as during commit --amend). When rebasing large
1570 1574 # stacks of commits, the number can go up, hence the config knob below.
1571 1575 cachesize = 4
1572 1576 usetreemanifest = False
1573 1577 opts = getattr(opener, 'options', None)
1574 1578 if opts is not None:
1575 1579 cachesize = opts.get('manifestcachesize', cachesize)
1576 1580 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1577 1581 self._mancache = util.lrucachedict(cachesize)
1578 1582 self._treeinmem = usetreemanifest
1579 1583 super(manifest, self).__init__(opener, dir=dir, dirlogcache=dirlogcache)
1580 1584
1581 1585 def _newmanifest(self, data=''):
1582 1586 if self._treeinmem:
1583 1587 return treemanifest(self._dir, data)
1584 1588 return manifestdict(data)
1585 1589
1586 1590 def dirlog(self, dir):
1587 1591 """This overrides the base revlog implementation to allow construction
1588 1592 'manifest' types instead of manifestrevlog types. This is only needed
1589 1593 until we migrate off the 'manifest' type."""
1590 1594 if dir:
1591 1595 assert self._treeondisk
1592 1596 if dir not in self._dirlogcache:
1593 1597 self._dirlogcache[dir] = manifest(self.opener, dir,
1594 1598 self._dirlogcache)
1595 1599 return self._dirlogcache[dir]
1596
1597 def clearcaches(self):
1598 super(manifest, self).clearcaches()
1599 self._mancache.clear()
General Comments 0
You need to be logged in to leave comments. Login now