##// END OF EJS Templates
revlog: backed out changeset e9d325cfe071...
Augie Fackler -
r33203:664d6f6c stable
parent child Browse files
Show More
@@ -1,2061 +1,2058
1 1 # localrepo.py - read/write repository 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 errno
11 11 import hashlib
12 12 import inspect
13 13 import os
14 14 import random
15 15 import time
16 16 import weakref
17 17
18 18 from .i18n import _
19 19 from .node import (
20 20 hex,
21 21 nullid,
22 22 short,
23 23 wdirrev,
24 24 )
25 25 from . import (
26 26 bookmarks,
27 27 branchmap,
28 28 bundle2,
29 29 changegroup,
30 30 changelog,
31 31 color,
32 32 context,
33 33 dirstate,
34 34 dirstateguard,
35 35 encoding,
36 36 error,
37 37 exchange,
38 38 extensions,
39 39 filelog,
40 40 hook,
41 41 lock as lockmod,
42 42 manifest,
43 43 match as matchmod,
44 44 merge as mergemod,
45 45 mergeutil,
46 46 namespaces,
47 47 obsolete,
48 48 pathutil,
49 49 peer,
50 50 phases,
51 51 pushkey,
52 52 pycompat,
53 53 repoview,
54 54 revset,
55 55 revsetlang,
56 56 scmutil,
57 57 store,
58 58 subrepo,
59 59 tags as tagsmod,
60 60 transaction,
61 61 txnutil,
62 62 util,
63 63 vfs as vfsmod,
64 64 )
65 65
66 66 release = lockmod.release
67 67 urlerr = util.urlerr
68 68 urlreq = util.urlreq
69 69
70 70 class repofilecache(scmutil.filecache):
71 71 """All filecache usage on repo are done for logic that should be unfiltered
72 72 """
73 73
74 74 def join(self, obj, fname):
75 75 return obj.vfs.join(fname)
76 76 def __get__(self, repo, type=None):
77 77 if repo is None:
78 78 return self
79 79 return super(repofilecache, self).__get__(repo.unfiltered(), type)
80 80 def __set__(self, repo, value):
81 81 return super(repofilecache, self).__set__(repo.unfiltered(), value)
82 82 def __delete__(self, repo):
83 83 return super(repofilecache, self).__delete__(repo.unfiltered())
84 84
85 85 class storecache(repofilecache):
86 86 """filecache for files in the store"""
87 87 def join(self, obj, fname):
88 88 return obj.sjoin(fname)
89 89
90 90 class unfilteredpropertycache(util.propertycache):
91 91 """propertycache that apply to unfiltered repo only"""
92 92
93 93 def __get__(self, repo, type=None):
94 94 unfi = repo.unfiltered()
95 95 if unfi is repo:
96 96 return super(unfilteredpropertycache, self).__get__(unfi)
97 97 return getattr(unfi, self.name)
98 98
99 99 class filteredpropertycache(util.propertycache):
100 100 """propertycache that must take filtering in account"""
101 101
102 102 def cachevalue(self, obj, value):
103 103 object.__setattr__(obj, self.name, value)
104 104
105 105
106 106 def hasunfilteredcache(repo, name):
107 107 """check if a repo has an unfilteredpropertycache value for <name>"""
108 108 return name in vars(repo.unfiltered())
109 109
110 110 def unfilteredmethod(orig):
111 111 """decorate method that always need to be run on unfiltered version"""
112 112 def wrapper(repo, *args, **kwargs):
113 113 return orig(repo.unfiltered(), *args, **kwargs)
114 114 return wrapper
115 115
116 116 moderncaps = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
117 117 'unbundle'))
118 118 legacycaps = moderncaps.union(set(['changegroupsubset']))
119 119
120 120 class localpeer(peer.peerrepository):
121 121 '''peer for a local repo; reflects only the most recent API'''
122 122
123 123 def __init__(self, repo, caps=None):
124 124 if caps is None:
125 125 caps = moderncaps.copy()
126 126 peer.peerrepository.__init__(self)
127 127 self._repo = repo.filtered('served')
128 128 self.ui = repo.ui
129 129 self._caps = repo._restrictcapabilities(caps)
130 130 self.requirements = repo.requirements
131 131 self.supportedformats = repo.supportedformats
132 132
133 133 def close(self):
134 134 self._repo.close()
135 135
136 136 def _capabilities(self):
137 137 return self._caps
138 138
139 139 def local(self):
140 140 return self._repo
141 141
142 142 def canpush(self):
143 143 return True
144 144
145 145 def url(self):
146 146 return self._repo.url()
147 147
148 148 def lookup(self, key):
149 149 return self._repo.lookup(key)
150 150
151 151 def branchmap(self):
152 152 return self._repo.branchmap()
153 153
154 154 def heads(self):
155 155 return self._repo.heads()
156 156
157 157 def known(self, nodes):
158 158 return self._repo.known(nodes)
159 159
160 160 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
161 161 **kwargs):
162 162 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
163 163 common=common, bundlecaps=bundlecaps,
164 164 **kwargs)
165 165 cb = util.chunkbuffer(chunks)
166 166
167 167 if bundlecaps is not None and 'HG20' in bundlecaps:
168 168 # When requesting a bundle2, getbundle returns a stream to make the
169 169 # wire level function happier. We need to build a proper object
170 170 # from it in local peer.
171 171 return bundle2.getunbundler(self.ui, cb)
172 172 else:
173 173 return changegroup.getunbundler('01', cb, None)
174 174
175 175 # TODO We might want to move the next two calls into legacypeer and add
176 176 # unbundle instead.
177 177
178 178 def unbundle(self, cg, heads, url):
179 179 """apply a bundle on a repo
180 180
181 181 This function handles the repo locking itself."""
182 182 try:
183 183 try:
184 184 cg = exchange.readbundle(self.ui, cg, None)
185 185 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
186 186 if util.safehasattr(ret, 'getchunks'):
187 187 # This is a bundle20 object, turn it into an unbundler.
188 188 # This little dance should be dropped eventually when the
189 189 # API is finally improved.
190 190 stream = util.chunkbuffer(ret.getchunks())
191 191 ret = bundle2.getunbundler(self.ui, stream)
192 192 return ret
193 193 except Exception as exc:
194 194 # If the exception contains output salvaged from a bundle2
195 195 # reply, we need to make sure it is printed before continuing
196 196 # to fail. So we build a bundle2 with such output and consume
197 197 # it directly.
198 198 #
199 199 # This is not very elegant but allows a "simple" solution for
200 200 # issue4594
201 201 output = getattr(exc, '_bundle2salvagedoutput', ())
202 202 if output:
203 203 bundler = bundle2.bundle20(self._repo.ui)
204 204 for out in output:
205 205 bundler.addpart(out)
206 206 stream = util.chunkbuffer(bundler.getchunks())
207 207 b = bundle2.getunbundler(self.ui, stream)
208 208 bundle2.processbundle(self._repo, b)
209 209 raise
210 210 except error.PushRaced as exc:
211 211 raise error.ResponseError(_('push failed:'), str(exc))
212 212
213 213 def lock(self):
214 214 return self._repo.lock()
215 215
216 216 def addchangegroup(self, cg, source, url):
217 217 return cg.apply(self._repo, source, url)
218 218
219 219 def pushkey(self, namespace, key, old, new):
220 220 return self._repo.pushkey(namespace, key, old, new)
221 221
222 222 def listkeys(self, namespace):
223 223 return self._repo.listkeys(namespace)
224 224
225 225 def debugwireargs(self, one, two, three=None, four=None, five=None):
226 226 '''used to test argument passing over the wire'''
227 227 return "%s %s %s %s %s" % (one, two, three, four, five)
228 228
229 229 class locallegacypeer(localpeer):
230 230 '''peer extension which implements legacy methods too; used for tests with
231 231 restricted capabilities'''
232 232
233 233 def __init__(self, repo):
234 234 localpeer.__init__(self, repo, caps=legacycaps)
235 235
236 236 def branches(self, nodes):
237 237 return self._repo.branches(nodes)
238 238
239 239 def between(self, pairs):
240 240 return self._repo.between(pairs)
241 241
242 242 def changegroup(self, basenodes, source):
243 243 return changegroup.changegroup(self._repo, basenodes, source)
244 244
245 245 def changegroupsubset(self, bases, heads, source):
246 246 return changegroup.changegroupsubset(self._repo, bases, heads, source)
247 247
248 248 class localrepository(object):
249 249
250 250 supportedformats = set(('revlogv1', 'generaldelta', 'treemanifest',
251 251 'manifestv2'))
252 252 _basesupported = supportedformats | set(('store', 'fncache', 'shared',
253 253 'relshared', 'dotencode'))
254 254 openerreqs = set(('revlogv1', 'generaldelta', 'treemanifest', 'manifestv2'))
255 255 filtername = None
256 256
257 257 # a list of (ui, featureset) functions.
258 258 # only functions defined in module of enabled extensions are invoked
259 259 featuresetupfuncs = set()
260 260
261 261 def __init__(self, baseui, path, create=False):
262 262 self.requirements = set()
263 263 # wvfs: rooted at the repository root, used to access the working copy
264 264 self.wvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
265 265 # vfs: rooted at .hg, used to access repo files outside of .hg/store
266 266 self.vfs = None
267 267 # svfs: usually rooted at .hg/store, used to access repository history
268 268 # If this is a shared repository, this vfs may point to another
269 269 # repository's .hg/store directory.
270 270 self.svfs = None
271 271 self.root = self.wvfs.base
272 272 self.path = self.wvfs.join(".hg")
273 273 self.origroot = path
274 274 self.auditor = pathutil.pathauditor(self.root, self._checknested)
275 275 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
276 276 realfs=False)
277 277 self.vfs = vfsmod.vfs(self.path)
278 278 self.baseui = baseui
279 279 self.ui = baseui.copy()
280 280 self.ui.copy = baseui.copy # prevent copying repo configuration
281 281 # A list of callback to shape the phase if no data were found.
282 282 # Callback are in the form: func(repo, roots) --> processed root.
283 283 # This list it to be filled by extension during repo setup
284 284 self._phasedefaults = []
285 285 try:
286 286 self.ui.readconfig(self.vfs.join("hgrc"), self.root)
287 287 self._loadextensions()
288 288 except IOError:
289 289 pass
290 290
291 291 if self.featuresetupfuncs:
292 292 self.supported = set(self._basesupported) # use private copy
293 293 extmods = set(m.__name__ for n, m
294 294 in extensions.extensions(self.ui))
295 295 for setupfunc in self.featuresetupfuncs:
296 296 if setupfunc.__module__ in extmods:
297 297 setupfunc(self.ui, self.supported)
298 298 else:
299 299 self.supported = self._basesupported
300 300 color.setup(self.ui)
301 301
302 302 # Add compression engines.
303 303 for name in util.compengines:
304 304 engine = util.compengines[name]
305 305 if engine.revlogheader():
306 306 self.supported.add('exp-compression-%s' % name)
307 307
308 308 if not self.vfs.isdir():
309 309 if create:
310 310 self.requirements = newreporequirements(self)
311 311
312 312 if not self.wvfs.exists():
313 313 self.wvfs.makedirs()
314 314 self.vfs.makedir(notindexed=True)
315 315
316 316 if 'store' in self.requirements:
317 317 self.vfs.mkdir("store")
318 318
319 319 # create an invalid changelog
320 320 self.vfs.append(
321 321 "00changelog.i",
322 322 '\0\0\0\2' # represents revlogv2
323 323 ' dummy changelog to prevent using the old repo layout'
324 324 )
325 325 else:
326 326 raise error.RepoError(_("repository %s not found") % path)
327 327 elif create:
328 328 raise error.RepoError(_("repository %s already exists") % path)
329 329 else:
330 330 try:
331 331 self.requirements = scmutil.readrequires(
332 332 self.vfs, self.supported)
333 333 except IOError as inst:
334 334 if inst.errno != errno.ENOENT:
335 335 raise
336 336
337 337 self.sharedpath = self.path
338 338 try:
339 339 sharedpath = self.vfs.read("sharedpath").rstrip('\n')
340 340 if 'relshared' in self.requirements:
341 341 sharedpath = self.vfs.join(sharedpath)
342 342 vfs = vfsmod.vfs(sharedpath, realpath=True)
343 343 s = vfs.base
344 344 if not vfs.exists():
345 345 raise error.RepoError(
346 346 _('.hg/sharedpath points to nonexistent directory %s') % s)
347 347 self.sharedpath = s
348 348 except IOError as inst:
349 349 if inst.errno != errno.ENOENT:
350 350 raise
351 351
352 352 self.store = store.store(
353 353 self.requirements, self.sharedpath, vfsmod.vfs)
354 354 self.spath = self.store.path
355 355 self.svfs = self.store.vfs
356 356 self.sjoin = self.store.join
357 357 self.vfs.createmode = self.store.createmode
358 358 self._applyopenerreqs()
359 359 if create:
360 360 self._writerequirements()
361 361
362 362 self._dirstatevalidatewarned = False
363 363
364 364 self._branchcaches = {}
365 365 self._revbranchcache = None
366 366 self.filterpats = {}
367 367 self._datafilters = {}
368 368 self._transref = self._lockref = self._wlockref = None
369 369
370 370 # A cache for various files under .hg/ that tracks file changes,
371 371 # (used by the filecache decorator)
372 372 #
373 373 # Maps a property name to its util.filecacheentry
374 374 self._filecache = {}
375 375
376 376 # hold sets of revision to be filtered
377 377 # should be cleared when something might have changed the filter value:
378 378 # - new changesets,
379 379 # - phase change,
380 380 # - new obsolescence marker,
381 381 # - working directory parent change,
382 382 # - bookmark changes
383 383 self.filteredrevcache = {}
384 384
385 385 # generic mapping between names and nodes
386 386 self.names = namespaces.namespaces()
387 387
388 388 @property
389 389 def wopener(self):
390 390 self.ui.deprecwarn("use 'repo.wvfs' instead of 'repo.wopener'", '4.2')
391 391 return self.wvfs
392 392
393 393 @property
394 394 def opener(self):
395 395 self.ui.deprecwarn("use 'repo.vfs' instead of 'repo.opener'", '4.2')
396 396 return self.vfs
397 397
398 398 def close(self):
399 399 self._writecaches()
400 400
401 401 def _loadextensions(self):
402 402 extensions.loadall(self.ui)
403 403
404 404 def _writecaches(self):
405 405 if self._revbranchcache:
406 406 self._revbranchcache.write()
407 407
408 408 def _restrictcapabilities(self, caps):
409 409 if self.ui.configbool('experimental', 'bundle2-advertise', True):
410 410 caps = set(caps)
411 411 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
412 412 caps.add('bundle2=' + urlreq.quote(capsblob))
413 413 return caps
414 414
415 415 def _applyopenerreqs(self):
416 416 self.svfs.options = dict((r, 1) for r in self.requirements
417 417 if r in self.openerreqs)
418 418 # experimental config: format.chunkcachesize
419 419 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
420 420 if chunkcachesize is not None:
421 421 self.svfs.options['chunkcachesize'] = chunkcachesize
422 422 # experimental config: format.maxchainlen
423 423 maxchainlen = self.ui.configint('format', 'maxchainlen')
424 424 if maxchainlen is not None:
425 425 self.svfs.options['maxchainlen'] = maxchainlen
426 426 # experimental config: format.manifestcachesize
427 427 manifestcachesize = self.ui.configint('format', 'manifestcachesize')
428 428 if manifestcachesize is not None:
429 429 self.svfs.options['manifestcachesize'] = manifestcachesize
430 430 # experimental config: format.aggressivemergedeltas
431 431 aggressivemergedeltas = self.ui.configbool('format',
432 432 'aggressivemergedeltas', False)
433 433 self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
434 434 self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
435 chainspan = self.ui.configbytes('experimental', 'maxdeltachainspan', -1)
436 if 0 <= chainspan:
437 self.svfs.options['maxdeltachainspan'] = chainspan
438 435
439 436 for r in self.requirements:
440 437 if r.startswith('exp-compression-'):
441 438 self.svfs.options['compengine'] = r[len('exp-compression-'):]
442 439
443 440 def _writerequirements(self):
444 441 scmutil.writerequires(self.vfs, self.requirements)
445 442
446 443 def _checknested(self, path):
447 444 """Determine if path is a legal nested repository."""
448 445 if not path.startswith(self.root):
449 446 return False
450 447 subpath = path[len(self.root) + 1:]
451 448 normsubpath = util.pconvert(subpath)
452 449
453 450 # XXX: Checking against the current working copy is wrong in
454 451 # the sense that it can reject things like
455 452 #
456 453 # $ hg cat -r 10 sub/x.txt
457 454 #
458 455 # if sub/ is no longer a subrepository in the working copy
459 456 # parent revision.
460 457 #
461 458 # However, it can of course also allow things that would have
462 459 # been rejected before, such as the above cat command if sub/
463 460 # is a subrepository now, but was a normal directory before.
464 461 # The old path auditor would have rejected by mistake since it
465 462 # panics when it sees sub/.hg/.
466 463 #
467 464 # All in all, checking against the working copy seems sensible
468 465 # since we want to prevent access to nested repositories on
469 466 # the filesystem *now*.
470 467 ctx = self[None]
471 468 parts = util.splitpath(subpath)
472 469 while parts:
473 470 prefix = '/'.join(parts)
474 471 if prefix in ctx.substate:
475 472 if prefix == normsubpath:
476 473 return True
477 474 else:
478 475 sub = ctx.sub(prefix)
479 476 return sub.checknested(subpath[len(prefix) + 1:])
480 477 else:
481 478 parts.pop()
482 479 return False
483 480
484 481 def peer(self):
485 482 return localpeer(self) # not cached to avoid reference cycle
486 483
487 484 def unfiltered(self):
488 485 """Return unfiltered version of the repository
489 486
490 487 Intended to be overwritten by filtered repo."""
491 488 return self
492 489
493 490 def filtered(self, name):
494 491 """Return a filtered version of a repository"""
495 492 # build a new class with the mixin and the current class
496 493 # (possibly subclass of the repo)
497 494 class filteredrepo(repoview.repoview, self.unfiltered().__class__):
498 495 pass
499 496 return filteredrepo(self, name)
500 497
501 498 @repofilecache('bookmarks', 'bookmarks.current')
502 499 def _bookmarks(self):
503 500 return bookmarks.bmstore(self)
504 501
505 502 @property
506 503 def _activebookmark(self):
507 504 return self._bookmarks.active
508 505
509 506 def bookmarkheads(self, bookmark):
510 507 name = bookmark.split('@', 1)[0]
511 508 heads = []
512 509 for mark, n in self._bookmarks.iteritems():
513 510 if mark.split('@', 1)[0] == name:
514 511 heads.append(n)
515 512 return heads
516 513
517 514 # _phaserevs and _phasesets depend on changelog. what we need is to
518 515 # call _phasecache.invalidate() if '00changelog.i' was changed, but it
519 516 # can't be easily expressed in filecache mechanism.
520 517 @storecache('phaseroots', '00changelog.i')
521 518 def _phasecache(self):
522 519 return phases.phasecache(self, self._phasedefaults)
523 520
524 521 @storecache('obsstore')
525 522 def obsstore(self):
526 523 # read default format for new obsstore.
527 524 # developer config: format.obsstore-version
528 525 defaultformat = self.ui.configint('format', 'obsstore-version', None)
529 526 # rely on obsstore class default when possible.
530 527 kwargs = {}
531 528 if defaultformat is not None:
532 529 kwargs['defaultformat'] = defaultformat
533 530 readonly = not obsolete.isenabled(self, obsolete.createmarkersopt)
534 531 store = obsolete.obsstore(self.svfs, readonly=readonly,
535 532 **kwargs)
536 533 if store and readonly:
537 534 self.ui.warn(
538 535 _('obsolete feature not enabled but %i markers found!\n')
539 536 % len(list(store)))
540 537 return store
541 538
542 539 @storecache('00changelog.i')
543 540 def changelog(self):
544 541 c = changelog.changelog(self.svfs)
545 542 if txnutil.mayhavepending(self.root):
546 543 c.readpending('00changelog.i.a')
547 544 return c
548 545
549 546 def _constructmanifest(self):
550 547 # This is a temporary function while we migrate from manifest to
551 548 # manifestlog. It allows bundlerepo and unionrepo to intercept the
552 549 # manifest creation.
553 550 return manifest.manifestrevlog(self.svfs)
554 551
555 552 @storecache('00manifest.i')
556 553 def manifestlog(self):
557 554 return manifest.manifestlog(self.svfs, self)
558 555
559 556 @repofilecache('dirstate')
560 557 def dirstate(self):
561 558 return dirstate.dirstate(self.vfs, self.ui, self.root,
562 559 self._dirstatevalidate)
563 560
564 561 def _dirstatevalidate(self, node):
565 562 try:
566 563 self.changelog.rev(node)
567 564 return node
568 565 except error.LookupError:
569 566 if not self._dirstatevalidatewarned:
570 567 self._dirstatevalidatewarned = True
571 568 self.ui.warn(_("warning: ignoring unknown"
572 569 " working parent %s!\n") % short(node))
573 570 return nullid
574 571
575 572 def __getitem__(self, changeid):
576 573 if changeid is None or changeid == wdirrev:
577 574 return context.workingctx(self)
578 575 if isinstance(changeid, slice):
579 576 return [context.changectx(self, i)
580 577 for i in xrange(*changeid.indices(len(self)))
581 578 if i not in self.changelog.filteredrevs]
582 579 return context.changectx(self, changeid)
583 580
584 581 def __contains__(self, changeid):
585 582 try:
586 583 self[changeid]
587 584 return True
588 585 except error.RepoLookupError:
589 586 return False
590 587
591 588 def __nonzero__(self):
592 589 return True
593 590
594 591 __bool__ = __nonzero__
595 592
596 593 def __len__(self):
597 594 return len(self.changelog)
598 595
599 596 def __iter__(self):
600 597 return iter(self.changelog)
601 598
602 599 def revs(self, expr, *args):
603 600 '''Find revisions matching a revset.
604 601
605 602 The revset is specified as a string ``expr`` that may contain
606 603 %-formatting to escape certain types. See ``revsetlang.formatspec``.
607 604
608 605 Revset aliases from the configuration are not expanded. To expand
609 606 user aliases, consider calling ``scmutil.revrange()`` or
610 607 ``repo.anyrevs([expr], user=True)``.
611 608
612 609 Returns a revset.abstractsmartset, which is a list-like interface
613 610 that contains integer revisions.
614 611 '''
615 612 expr = revsetlang.formatspec(expr, *args)
616 613 m = revset.match(None, expr)
617 614 return m(self)
618 615
619 616 def set(self, expr, *args):
620 617 '''Find revisions matching a revset and emit changectx instances.
621 618
622 619 This is a convenience wrapper around ``revs()`` that iterates the
623 620 result and is a generator of changectx instances.
624 621
625 622 Revset aliases from the configuration are not expanded. To expand
626 623 user aliases, consider calling ``scmutil.revrange()``.
627 624 '''
628 625 for r in self.revs(expr, *args):
629 626 yield self[r]
630 627
631 628 def anyrevs(self, specs, user=False):
632 629 '''Find revisions matching one of the given revsets.
633 630
634 631 Revset aliases from the configuration are not expanded by default. To
635 632 expand user aliases, specify ``user=True``.
636 633 '''
637 634 if user:
638 635 m = revset.matchany(self.ui, specs, repo=self)
639 636 else:
640 637 m = revset.matchany(None, specs)
641 638 return m(self)
642 639
643 640 def url(self):
644 641 return 'file:' + self.root
645 642
646 643 def hook(self, name, throw=False, **args):
647 644 """Call a hook, passing this repo instance.
648 645
649 646 This a convenience method to aid invoking hooks. Extensions likely
650 647 won't call this unless they have registered a custom hook or are
651 648 replacing code that is expected to call a hook.
652 649 """
653 650 return hook.hook(self.ui, self, name, throw, **args)
654 651
655 652 def tag(self, names, node, message, local, user, date, editor=False):
656 653 self.ui.deprecwarn("use 'tagsmod.tag' instead of 'repo.tag'", '4.2')
657 654 tagsmod.tag(self, names, node, message, local, user, date,
658 655 editor=editor)
659 656
660 657 @filteredpropertycache
661 658 def _tagscache(self):
662 659 '''Returns a tagscache object that contains various tags related
663 660 caches.'''
664 661
665 662 # This simplifies its cache management by having one decorated
666 663 # function (this one) and the rest simply fetch things from it.
667 664 class tagscache(object):
668 665 def __init__(self):
669 666 # These two define the set of tags for this repository. tags
670 667 # maps tag name to node; tagtypes maps tag name to 'global' or
671 668 # 'local'. (Global tags are defined by .hgtags across all
672 669 # heads, and local tags are defined in .hg/localtags.)
673 670 # They constitute the in-memory cache of tags.
674 671 self.tags = self.tagtypes = None
675 672
676 673 self.nodetagscache = self.tagslist = None
677 674
678 675 cache = tagscache()
679 676 cache.tags, cache.tagtypes = self._findtags()
680 677
681 678 return cache
682 679
683 680 def tags(self):
684 681 '''return a mapping of tag to node'''
685 682 t = {}
686 683 if self.changelog.filteredrevs:
687 684 tags, tt = self._findtags()
688 685 else:
689 686 tags = self._tagscache.tags
690 687 for k, v in tags.iteritems():
691 688 try:
692 689 # ignore tags to unknown nodes
693 690 self.changelog.rev(v)
694 691 t[k] = v
695 692 except (error.LookupError, ValueError):
696 693 pass
697 694 return t
698 695
699 696 def _findtags(self):
700 697 '''Do the hard work of finding tags. Return a pair of dicts
701 698 (tags, tagtypes) where tags maps tag name to node, and tagtypes
702 699 maps tag name to a string like \'global\' or \'local\'.
703 700 Subclasses or extensions are free to add their own tags, but
704 701 should be aware that the returned dicts will be retained for the
705 702 duration of the localrepo object.'''
706 703
707 704 # XXX what tagtype should subclasses/extensions use? Currently
708 705 # mq and bookmarks add tags, but do not set the tagtype at all.
709 706 # Should each extension invent its own tag type? Should there
710 707 # be one tagtype for all such "virtual" tags? Or is the status
711 708 # quo fine?
712 709
713 710
714 711 # map tag name to (node, hist)
715 712 alltags = tagsmod.findglobaltags(self.ui, self)
716 713 # map tag name to tag type
717 714 tagtypes = dict((tag, 'global') for tag in alltags)
718 715
719 716 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
720 717
721 718 # Build the return dicts. Have to re-encode tag names because
722 719 # the tags module always uses UTF-8 (in order not to lose info
723 720 # writing to the cache), but the rest of Mercurial wants them in
724 721 # local encoding.
725 722 tags = {}
726 723 for (name, (node, hist)) in alltags.iteritems():
727 724 if node != nullid:
728 725 tags[encoding.tolocal(name)] = node
729 726 tags['tip'] = self.changelog.tip()
730 727 tagtypes = dict([(encoding.tolocal(name), value)
731 728 for (name, value) in tagtypes.iteritems()])
732 729 return (tags, tagtypes)
733 730
734 731 def tagtype(self, tagname):
735 732 '''
736 733 return the type of the given tag. result can be:
737 734
738 735 'local' : a local tag
739 736 'global' : a global tag
740 737 None : tag does not exist
741 738 '''
742 739
743 740 return self._tagscache.tagtypes.get(tagname)
744 741
745 742 def tagslist(self):
746 743 '''return a list of tags ordered by revision'''
747 744 if not self._tagscache.tagslist:
748 745 l = []
749 746 for t, n in self.tags().iteritems():
750 747 l.append((self.changelog.rev(n), t, n))
751 748 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
752 749
753 750 return self._tagscache.tagslist
754 751
755 752 def nodetags(self, node):
756 753 '''return the tags associated with a node'''
757 754 if not self._tagscache.nodetagscache:
758 755 nodetagscache = {}
759 756 for t, n in self._tagscache.tags.iteritems():
760 757 nodetagscache.setdefault(n, []).append(t)
761 758 for tags in nodetagscache.itervalues():
762 759 tags.sort()
763 760 self._tagscache.nodetagscache = nodetagscache
764 761 return self._tagscache.nodetagscache.get(node, [])
765 762
766 763 def nodebookmarks(self, node):
767 764 """return the list of bookmarks pointing to the specified node"""
768 765 marks = []
769 766 for bookmark, n in self._bookmarks.iteritems():
770 767 if n == node:
771 768 marks.append(bookmark)
772 769 return sorted(marks)
773 770
774 771 def branchmap(self):
775 772 '''returns a dictionary {branch: [branchheads]} with branchheads
776 773 ordered by increasing revision number'''
777 774 branchmap.updatecache(self)
778 775 return self._branchcaches[self.filtername]
779 776
780 777 @unfilteredmethod
781 778 def revbranchcache(self):
782 779 if not self._revbranchcache:
783 780 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
784 781 return self._revbranchcache
785 782
786 783 def branchtip(self, branch, ignoremissing=False):
787 784 '''return the tip node for a given branch
788 785
789 786 If ignoremissing is True, then this method will not raise an error.
790 787 This is helpful for callers that only expect None for a missing branch
791 788 (e.g. namespace).
792 789
793 790 '''
794 791 try:
795 792 return self.branchmap().branchtip(branch)
796 793 except KeyError:
797 794 if not ignoremissing:
798 795 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
799 796 else:
800 797 pass
801 798
802 799 def lookup(self, key):
803 800 return self[key].node()
804 801
805 802 def lookupbranch(self, key, remote=None):
806 803 repo = remote or self
807 804 if key in repo.branchmap():
808 805 return key
809 806
810 807 repo = (remote and remote.local()) and remote or self
811 808 return repo[key].branch()
812 809
813 810 def known(self, nodes):
814 811 cl = self.changelog
815 812 nm = cl.nodemap
816 813 filtered = cl.filteredrevs
817 814 result = []
818 815 for n in nodes:
819 816 r = nm.get(n)
820 817 resp = not (r is None or r in filtered)
821 818 result.append(resp)
822 819 return result
823 820
824 821 def local(self):
825 822 return self
826 823
827 824 def publishing(self):
828 825 # it's safe (and desirable) to trust the publish flag unconditionally
829 826 # so that we don't finalize changes shared between users via ssh or nfs
830 827 return self.ui.configbool('phases', 'publish', True, untrusted=True)
831 828
832 829 def cancopy(self):
833 830 # so statichttprepo's override of local() works
834 831 if not self.local():
835 832 return False
836 833 if not self.publishing():
837 834 return True
838 835 # if publishing we can't copy if there is filtered content
839 836 return not self.filtered('visible').changelog.filteredrevs
840 837
841 838 def shared(self):
842 839 '''the type of shared repository (None if not shared)'''
843 840 if self.sharedpath != self.path:
844 841 return 'store'
845 842 return None
846 843
847 844 def join(self, f, *insidef):
848 845 self.ui.deprecwarn("use 'repo.vfs.join' instead of 'repo.join'", '4.2')
849 846 return self.vfs.join(os.path.join(f, *insidef))
850 847
851 848 def wjoin(self, f, *insidef):
852 849 return self.vfs.reljoin(self.root, f, *insidef)
853 850
854 851 def file(self, f):
855 852 if f[0] == '/':
856 853 f = f[1:]
857 854 return filelog.filelog(self.svfs, f)
858 855
859 856 def changectx(self, changeid):
860 857 return self[changeid]
861 858
862 859 def setparents(self, p1, p2=nullid):
863 860 self.dirstate.beginparentchange()
864 861 copies = self.dirstate.setparents(p1, p2)
865 862 pctx = self[p1]
866 863 if copies:
867 864 # Adjust copy records, the dirstate cannot do it, it
868 865 # requires access to parents manifests. Preserve them
869 866 # only for entries added to first parent.
870 867 for f in copies:
871 868 if f not in pctx and copies[f] in pctx:
872 869 self.dirstate.copy(copies[f], f)
873 870 if p2 == nullid:
874 871 for f, s in sorted(self.dirstate.copies().items()):
875 872 if f not in pctx and s not in pctx:
876 873 self.dirstate.copy(None, f)
877 874 self.dirstate.endparentchange()
878 875
879 876 def filectx(self, path, changeid=None, fileid=None):
880 877 """changeid can be a changeset revision, node, or tag.
881 878 fileid can be a file revision or node."""
882 879 return context.filectx(self, path, changeid, fileid)
883 880
884 881 def getcwd(self):
885 882 return self.dirstate.getcwd()
886 883
887 884 def pathto(self, f, cwd=None):
888 885 return self.dirstate.pathto(f, cwd)
889 886
890 887 def wfile(self, f, mode='r'):
891 888 self.ui.deprecwarn("use 'repo.wvfs' instead of 'repo.wfile'", '4.2')
892 889 return self.wvfs(f, mode)
893 890
894 891 def _link(self, f):
895 892 self.ui.deprecwarn("use 'repo.wvfs.islink' instead of 'repo._link'",
896 893 '4.2')
897 894 return self.wvfs.islink(f)
898 895
899 896 def _loadfilter(self, filter):
900 897 if filter not in self.filterpats:
901 898 l = []
902 899 for pat, cmd in self.ui.configitems(filter):
903 900 if cmd == '!':
904 901 continue
905 902 mf = matchmod.match(self.root, '', [pat])
906 903 fn = None
907 904 params = cmd
908 905 for name, filterfn in self._datafilters.iteritems():
909 906 if cmd.startswith(name):
910 907 fn = filterfn
911 908 params = cmd[len(name):].lstrip()
912 909 break
913 910 if not fn:
914 911 fn = lambda s, c, **kwargs: util.filter(s, c)
915 912 # Wrap old filters not supporting keyword arguments
916 913 if not inspect.getargspec(fn)[2]:
917 914 oldfn = fn
918 915 fn = lambda s, c, **kwargs: oldfn(s, c)
919 916 l.append((mf, fn, params))
920 917 self.filterpats[filter] = l
921 918 return self.filterpats[filter]
922 919
923 920 def _filter(self, filterpats, filename, data):
924 921 for mf, fn, cmd in filterpats:
925 922 if mf(filename):
926 923 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
927 924 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
928 925 break
929 926
930 927 return data
931 928
932 929 @unfilteredpropertycache
933 930 def _encodefilterpats(self):
934 931 return self._loadfilter('encode')
935 932
936 933 @unfilteredpropertycache
937 934 def _decodefilterpats(self):
938 935 return self._loadfilter('decode')
939 936
940 937 def adddatafilter(self, name, filter):
941 938 self._datafilters[name] = filter
942 939
943 940 def wread(self, filename):
944 941 if self.wvfs.islink(filename):
945 942 data = self.wvfs.readlink(filename)
946 943 else:
947 944 data = self.wvfs.read(filename)
948 945 return self._filter(self._encodefilterpats, filename, data)
949 946
950 947 def wwrite(self, filename, data, flags, backgroundclose=False):
951 948 """write ``data`` into ``filename`` in the working directory
952 949
953 950 This returns length of written (maybe decoded) data.
954 951 """
955 952 data = self._filter(self._decodefilterpats, filename, data)
956 953 if 'l' in flags:
957 954 self.wvfs.symlink(data, filename)
958 955 else:
959 956 self.wvfs.write(filename, data, backgroundclose=backgroundclose)
960 957 if 'x' in flags:
961 958 self.wvfs.setflags(filename, False, True)
962 959 return len(data)
963 960
964 961 def wwritedata(self, filename, data):
965 962 return self._filter(self._decodefilterpats, filename, data)
966 963
967 964 def currenttransaction(self):
968 965 """return the current transaction or None if non exists"""
969 966 if self._transref:
970 967 tr = self._transref()
971 968 else:
972 969 tr = None
973 970
974 971 if tr and tr.running():
975 972 return tr
976 973 return None
977 974
978 975 def transaction(self, desc, report=None):
979 976 if (self.ui.configbool('devel', 'all-warnings')
980 977 or self.ui.configbool('devel', 'check-locks')):
981 978 if self._currentlock(self._lockref) is None:
982 979 raise error.ProgrammingError('transaction requires locking')
983 980 tr = self.currenttransaction()
984 981 if tr is not None:
985 982 return tr.nest()
986 983
987 984 # abort here if the journal already exists
988 985 if self.svfs.exists("journal"):
989 986 raise error.RepoError(
990 987 _("abandoned transaction found"),
991 988 hint=_("run 'hg recover' to clean up transaction"))
992 989
993 990 idbase = "%.40f#%f" % (random.random(), time.time())
994 991 ha = hex(hashlib.sha1(idbase).digest())
995 992 txnid = 'TXN:' + ha
996 993 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
997 994
998 995 self._writejournal(desc)
999 996 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1000 997 if report:
1001 998 rp = report
1002 999 else:
1003 1000 rp = self.ui.warn
1004 1001 vfsmap = {'plain': self.vfs} # root of .hg/
1005 1002 # we must avoid cyclic reference between repo and transaction.
1006 1003 reporef = weakref.ref(self)
1007 1004 # Code to track tag movement
1008 1005 #
1009 1006 # Since tags are all handled as file content, it is actually quite hard
1010 1007 # to track these movement from a code perspective. So we fallback to a
1011 1008 # tracking at the repository level. One could envision to track changes
1012 1009 # to the '.hgtags' file through changegroup apply but that fails to
1013 1010 # cope with case where transaction expose new heads without changegroup
1014 1011 # being involved (eg: phase movement).
1015 1012 #
1016 1013 # For now, We gate the feature behind a flag since this likely comes
1017 1014 # with performance impacts. The current code run more often than needed
1018 1015 # and do not use caches as much as it could. The current focus is on
1019 1016 # the behavior of the feature so we disable it by default. The flag
1020 1017 # will be removed when we are happy with the performance impact.
1021 1018 #
1022 1019 # Once this feature is no longer experimental move the following
1023 1020 # documentation to the appropriate help section:
1024 1021 #
1025 1022 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1026 1023 # tags (new or changed or deleted tags). In addition the details of
1027 1024 # these changes are made available in a file at:
1028 1025 # ``REPOROOT/.hg/changes/tags.changes``.
1029 1026 # Make sure you check for HG_TAG_MOVED before reading that file as it
1030 1027 # might exist from a previous transaction even if no tag were touched
1031 1028 # in this one. Changes are recorded in a line base format::
1032 1029 #
1033 1030 # <action> <hex-node> <tag-name>\n
1034 1031 #
1035 1032 # Actions are defined as follow:
1036 1033 # "-R": tag is removed,
1037 1034 # "+A": tag is added,
1038 1035 # "-M": tag is moved (old value),
1039 1036 # "+M": tag is moved (new value),
1040 1037 tracktags = lambda x: None
1041 1038 # experimental config: experimental.hook-track-tags
1042 1039 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags',
1043 1040 False)
1044 1041 if desc != 'strip' and shouldtracktags:
1045 1042 oldheads = self.changelog.headrevs()
1046 1043 def tracktags(tr2):
1047 1044 repo = reporef()
1048 1045 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1049 1046 newheads = repo.changelog.headrevs()
1050 1047 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1051 1048 # notes: we compare lists here.
1052 1049 # As we do it only once buiding set would not be cheaper
1053 1050 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1054 1051 if changes:
1055 1052 tr2.hookargs['tag_moved'] = '1'
1056 1053 with repo.vfs('changes/tags.changes', 'w',
1057 1054 atomictemp=True) as changesfile:
1058 1055 # note: we do not register the file to the transaction
1059 1056 # because we needs it to still exist on the transaction
1060 1057 # is close (for txnclose hooks)
1061 1058 tagsmod.writediff(changesfile, changes)
1062 1059 def validate(tr2):
1063 1060 """will run pre-closing hooks"""
1064 1061 # XXX the transaction API is a bit lacking here so we take a hacky
1065 1062 # path for now
1066 1063 #
1067 1064 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1068 1065 # dict is copied before these run. In addition we needs the data
1069 1066 # available to in memory hooks too.
1070 1067 #
1071 1068 # Moreover, we also need to make sure this runs before txnclose
1072 1069 # hooks and there is no "pending" mechanism that would execute
1073 1070 # logic only if hooks are about to run.
1074 1071 #
1075 1072 # Fixing this limitation of the transaction is also needed to track
1076 1073 # other families of changes (bookmarks, phases, obsolescence).
1077 1074 #
1078 1075 # This will have to be fixed before we remove the experimental
1079 1076 # gating.
1080 1077 tracktags(tr2)
1081 1078 reporef().hook('pretxnclose', throw=True,
1082 1079 txnname=desc, **pycompat.strkwargs(tr.hookargs))
1083 1080 def releasefn(tr, success):
1084 1081 repo = reporef()
1085 1082 if success:
1086 1083 # this should be explicitly invoked here, because
1087 1084 # in-memory changes aren't written out at closing
1088 1085 # transaction, if tr.addfilegenerator (via
1089 1086 # dirstate.write or so) isn't invoked while
1090 1087 # transaction running
1091 1088 repo.dirstate.write(None)
1092 1089 else:
1093 1090 # discard all changes (including ones already written
1094 1091 # out) in this transaction
1095 1092 repo.dirstate.restorebackup(None, prefix='journal.')
1096 1093
1097 1094 repo.invalidate(clearfilecache=True)
1098 1095
1099 1096 tr = transaction.transaction(rp, self.svfs, vfsmap,
1100 1097 "journal",
1101 1098 "undo",
1102 1099 aftertrans(renames),
1103 1100 self.store.createmode,
1104 1101 validator=validate,
1105 1102 releasefn=releasefn)
1106 1103
1107 1104 tr.hookargs['txnid'] = txnid
1108 1105 # note: writing the fncache only during finalize mean that the file is
1109 1106 # outdated when running hooks. As fncache is used for streaming clone,
1110 1107 # this is not expected to break anything that happen during the hooks.
1111 1108 tr.addfinalize('flush-fncache', self.store.write)
1112 1109 def txnclosehook(tr2):
1113 1110 """To be run if transaction is successful, will schedule a hook run
1114 1111 """
1115 1112 # Don't reference tr2 in hook() so we don't hold a reference.
1116 1113 # This reduces memory consumption when there are multiple
1117 1114 # transactions per lock. This can likely go away if issue5045
1118 1115 # fixes the function accumulation.
1119 1116 hookargs = tr2.hookargs
1120 1117
1121 1118 def hook():
1122 1119 reporef().hook('txnclose', throw=False, txnname=desc,
1123 1120 **pycompat.strkwargs(hookargs))
1124 1121 reporef()._afterlock(hook)
1125 1122 tr.addfinalize('txnclose-hook', txnclosehook)
1126 1123 def txnaborthook(tr2):
1127 1124 """To be run if transaction is aborted
1128 1125 """
1129 1126 reporef().hook('txnabort', throw=False, txnname=desc,
1130 1127 **tr2.hookargs)
1131 1128 tr.addabort('txnabort-hook', txnaborthook)
1132 1129 # avoid eager cache invalidation. in-memory data should be identical
1133 1130 # to stored data if transaction has no error.
1134 1131 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1135 1132 self._transref = weakref.ref(tr)
1136 1133 return tr
1137 1134
1138 1135 def _journalfiles(self):
1139 1136 return ((self.svfs, 'journal'),
1140 1137 (self.vfs, 'journal.dirstate'),
1141 1138 (self.vfs, 'journal.branch'),
1142 1139 (self.vfs, 'journal.desc'),
1143 1140 (self.vfs, 'journal.bookmarks'),
1144 1141 (self.svfs, 'journal.phaseroots'))
1145 1142
1146 1143 def undofiles(self):
1147 1144 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1148 1145
1149 1146 def _writejournal(self, desc):
1150 1147 self.dirstate.savebackup(None, prefix='journal.')
1151 1148 self.vfs.write("journal.branch",
1152 1149 encoding.fromlocal(self.dirstate.branch()))
1153 1150 self.vfs.write("journal.desc",
1154 1151 "%d\n%s\n" % (len(self), desc))
1155 1152 self.vfs.write("journal.bookmarks",
1156 1153 self.vfs.tryread("bookmarks"))
1157 1154 self.svfs.write("journal.phaseroots",
1158 1155 self.svfs.tryread("phaseroots"))
1159 1156
1160 1157 def recover(self):
1161 1158 with self.lock():
1162 1159 if self.svfs.exists("journal"):
1163 1160 self.ui.status(_("rolling back interrupted transaction\n"))
1164 1161 vfsmap = {'': self.svfs,
1165 1162 'plain': self.vfs,}
1166 1163 transaction.rollback(self.svfs, vfsmap, "journal",
1167 1164 self.ui.warn)
1168 1165 self.invalidate()
1169 1166 return True
1170 1167 else:
1171 1168 self.ui.warn(_("no interrupted transaction available\n"))
1172 1169 return False
1173 1170
1174 1171 def rollback(self, dryrun=False, force=False):
1175 1172 wlock = lock = dsguard = None
1176 1173 try:
1177 1174 wlock = self.wlock()
1178 1175 lock = self.lock()
1179 1176 if self.svfs.exists("undo"):
1180 1177 dsguard = dirstateguard.dirstateguard(self, 'rollback')
1181 1178
1182 1179 return self._rollback(dryrun, force, dsguard)
1183 1180 else:
1184 1181 self.ui.warn(_("no rollback information available\n"))
1185 1182 return 1
1186 1183 finally:
1187 1184 release(dsguard, lock, wlock)
1188 1185
1189 1186 @unfilteredmethod # Until we get smarter cache management
1190 1187 def _rollback(self, dryrun, force, dsguard):
1191 1188 ui = self.ui
1192 1189 try:
1193 1190 args = self.vfs.read('undo.desc').splitlines()
1194 1191 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1195 1192 if len(args) >= 3:
1196 1193 detail = args[2]
1197 1194 oldtip = oldlen - 1
1198 1195
1199 1196 if detail and ui.verbose:
1200 1197 msg = (_('repository tip rolled back to revision %s'
1201 1198 ' (undo %s: %s)\n')
1202 1199 % (oldtip, desc, detail))
1203 1200 else:
1204 1201 msg = (_('repository tip rolled back to revision %s'
1205 1202 ' (undo %s)\n')
1206 1203 % (oldtip, desc))
1207 1204 except IOError:
1208 1205 msg = _('rolling back unknown transaction\n')
1209 1206 desc = None
1210 1207
1211 1208 if not force and self['.'] != self['tip'] and desc == 'commit':
1212 1209 raise error.Abort(
1213 1210 _('rollback of last commit while not checked out '
1214 1211 'may lose data'), hint=_('use -f to force'))
1215 1212
1216 1213 ui.status(msg)
1217 1214 if dryrun:
1218 1215 return 0
1219 1216
1220 1217 parents = self.dirstate.parents()
1221 1218 self.destroying()
1222 1219 vfsmap = {'plain': self.vfs, '': self.svfs}
1223 1220 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn)
1224 1221 if self.vfs.exists('undo.bookmarks'):
1225 1222 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1226 1223 if self.svfs.exists('undo.phaseroots'):
1227 1224 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1228 1225 self.invalidate()
1229 1226
1230 1227 parentgone = (parents[0] not in self.changelog.nodemap or
1231 1228 parents[1] not in self.changelog.nodemap)
1232 1229 if parentgone:
1233 1230 # prevent dirstateguard from overwriting already restored one
1234 1231 dsguard.close()
1235 1232
1236 1233 self.dirstate.restorebackup(None, prefix='undo.')
1237 1234 try:
1238 1235 branch = self.vfs.read('undo.branch')
1239 1236 self.dirstate.setbranch(encoding.tolocal(branch))
1240 1237 except IOError:
1241 1238 ui.warn(_('named branch could not be reset: '
1242 1239 'current branch is still \'%s\'\n')
1243 1240 % self.dirstate.branch())
1244 1241
1245 1242 parents = tuple([p.rev() for p in self[None].parents()])
1246 1243 if len(parents) > 1:
1247 1244 ui.status(_('working directory now based on '
1248 1245 'revisions %d and %d\n') % parents)
1249 1246 else:
1250 1247 ui.status(_('working directory now based on '
1251 1248 'revision %d\n') % parents)
1252 1249 mergemod.mergestate.clean(self, self['.'].node())
1253 1250
1254 1251 # TODO: if we know which new heads may result from this rollback, pass
1255 1252 # them to destroy(), which will prevent the branchhead cache from being
1256 1253 # invalidated.
1257 1254 self.destroyed()
1258 1255 return 0
1259 1256
1260 1257 def invalidatecaches(self):
1261 1258
1262 1259 if '_tagscache' in vars(self):
1263 1260 # can't use delattr on proxy
1264 1261 del self.__dict__['_tagscache']
1265 1262
1266 1263 self.unfiltered()._branchcaches.clear()
1267 1264 self.invalidatevolatilesets()
1268 1265
1269 1266 def invalidatevolatilesets(self):
1270 1267 self.filteredrevcache.clear()
1271 1268 obsolete.clearobscaches(self)
1272 1269
1273 1270 def invalidatedirstate(self):
1274 1271 '''Invalidates the dirstate, causing the next call to dirstate
1275 1272 to check if it was modified since the last time it was read,
1276 1273 rereading it if it has.
1277 1274
1278 1275 This is different to dirstate.invalidate() that it doesn't always
1279 1276 rereads the dirstate. Use dirstate.invalidate() if you want to
1280 1277 explicitly read the dirstate again (i.e. restoring it to a previous
1281 1278 known good state).'''
1282 1279 if hasunfilteredcache(self, 'dirstate'):
1283 1280 for k in self.dirstate._filecache:
1284 1281 try:
1285 1282 delattr(self.dirstate, k)
1286 1283 except AttributeError:
1287 1284 pass
1288 1285 delattr(self.unfiltered(), 'dirstate')
1289 1286
1290 1287 def invalidate(self, clearfilecache=False):
1291 1288 '''Invalidates both store and non-store parts other than dirstate
1292 1289
1293 1290 If a transaction is running, invalidation of store is omitted,
1294 1291 because discarding in-memory changes might cause inconsistency
1295 1292 (e.g. incomplete fncache causes unintentional failure, but
1296 1293 redundant one doesn't).
1297 1294 '''
1298 1295 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1299 1296 for k in list(self._filecache.keys()):
1300 1297 # dirstate is invalidated separately in invalidatedirstate()
1301 1298 if k == 'dirstate':
1302 1299 continue
1303 1300
1304 1301 if clearfilecache:
1305 1302 del self._filecache[k]
1306 1303 try:
1307 1304 delattr(unfiltered, k)
1308 1305 except AttributeError:
1309 1306 pass
1310 1307 self.invalidatecaches()
1311 1308 if not self.currenttransaction():
1312 1309 # TODO: Changing contents of store outside transaction
1313 1310 # causes inconsistency. We should make in-memory store
1314 1311 # changes detectable, and abort if changed.
1315 1312 self.store.invalidatecaches()
1316 1313
1317 1314 def invalidateall(self):
1318 1315 '''Fully invalidates both store and non-store parts, causing the
1319 1316 subsequent operation to reread any outside changes.'''
1320 1317 # extension should hook this to invalidate its caches
1321 1318 self.invalidate()
1322 1319 self.invalidatedirstate()
1323 1320
1324 1321 @unfilteredmethod
1325 1322 def _refreshfilecachestats(self, tr):
1326 1323 """Reload stats of cached files so that they are flagged as valid"""
1327 1324 for k, ce in self._filecache.items():
1328 1325 if k == 'dirstate' or k not in self.__dict__:
1329 1326 continue
1330 1327 ce.refresh()
1331 1328
1332 1329 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
1333 1330 inheritchecker=None, parentenvvar=None):
1334 1331 parentlock = None
1335 1332 # the contents of parentenvvar are used by the underlying lock to
1336 1333 # determine whether it can be inherited
1337 1334 if parentenvvar is not None:
1338 1335 parentlock = encoding.environ.get(parentenvvar)
1339 1336 try:
1340 1337 l = lockmod.lock(vfs, lockname, 0, releasefn=releasefn,
1341 1338 acquirefn=acquirefn, desc=desc,
1342 1339 inheritchecker=inheritchecker,
1343 1340 parentlock=parentlock)
1344 1341 except error.LockHeld as inst:
1345 1342 if not wait:
1346 1343 raise
1347 1344 # show more details for new-style locks
1348 1345 if ':' in inst.locker:
1349 1346 host, pid = inst.locker.split(":", 1)
1350 1347 self.ui.warn(
1351 1348 _("waiting for lock on %s held by process %r "
1352 1349 "on host %r\n") % (desc, pid, host))
1353 1350 else:
1354 1351 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1355 1352 (desc, inst.locker))
1356 1353 # default to 600 seconds timeout
1357 1354 l = lockmod.lock(vfs, lockname,
1358 1355 int(self.ui.config("ui", "timeout", "600")),
1359 1356 releasefn=releasefn, acquirefn=acquirefn,
1360 1357 desc=desc)
1361 1358 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1362 1359 return l
1363 1360
1364 1361 def _afterlock(self, callback):
1365 1362 """add a callback to be run when the repository is fully unlocked
1366 1363
1367 1364 The callback will be executed when the outermost lock is released
1368 1365 (with wlock being higher level than 'lock')."""
1369 1366 for ref in (self._wlockref, self._lockref):
1370 1367 l = ref and ref()
1371 1368 if l and l.held:
1372 1369 l.postrelease.append(callback)
1373 1370 break
1374 1371 else: # no lock have been found.
1375 1372 callback()
1376 1373
1377 1374 def lock(self, wait=True):
1378 1375 '''Lock the repository store (.hg/store) and return a weak reference
1379 1376 to the lock. Use this before modifying the store (e.g. committing or
1380 1377 stripping). If you are opening a transaction, get a lock as well.)
1381 1378
1382 1379 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1383 1380 'wlock' first to avoid a dead-lock hazard.'''
1384 1381 l = self._currentlock(self._lockref)
1385 1382 if l is not None:
1386 1383 l.lock()
1387 1384 return l
1388 1385
1389 1386 l = self._lock(self.svfs, "lock", wait, None,
1390 1387 self.invalidate, _('repository %s') % self.origroot)
1391 1388 self._lockref = weakref.ref(l)
1392 1389 return l
1393 1390
1394 1391 def _wlockchecktransaction(self):
1395 1392 if self.currenttransaction() is not None:
1396 1393 raise error.LockInheritanceContractViolation(
1397 1394 'wlock cannot be inherited in the middle of a transaction')
1398 1395
1399 1396 def wlock(self, wait=True):
1400 1397 '''Lock the non-store parts of the repository (everything under
1401 1398 .hg except .hg/store) and return a weak reference to the lock.
1402 1399
1403 1400 Use this before modifying files in .hg.
1404 1401
1405 1402 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1406 1403 'wlock' first to avoid a dead-lock hazard.'''
1407 1404 l = self._wlockref and self._wlockref()
1408 1405 if l is not None and l.held:
1409 1406 l.lock()
1410 1407 return l
1411 1408
1412 1409 # We do not need to check for non-waiting lock acquisition. Such
1413 1410 # acquisition would not cause dead-lock as they would just fail.
1414 1411 if wait and (self.ui.configbool('devel', 'all-warnings')
1415 1412 or self.ui.configbool('devel', 'check-locks')):
1416 1413 if self._currentlock(self._lockref) is not None:
1417 1414 self.ui.develwarn('"wlock" acquired after "lock"')
1418 1415
1419 1416 def unlock():
1420 1417 if self.dirstate.pendingparentchange():
1421 1418 self.dirstate.invalidate()
1422 1419 else:
1423 1420 self.dirstate.write(None)
1424 1421
1425 1422 self._filecache['dirstate'].refresh()
1426 1423
1427 1424 l = self._lock(self.vfs, "wlock", wait, unlock,
1428 1425 self.invalidatedirstate, _('working directory of %s') %
1429 1426 self.origroot,
1430 1427 inheritchecker=self._wlockchecktransaction,
1431 1428 parentenvvar='HG_WLOCK_LOCKER')
1432 1429 self._wlockref = weakref.ref(l)
1433 1430 return l
1434 1431
1435 1432 def _currentlock(self, lockref):
1436 1433 """Returns the lock if it's held, or None if it's not."""
1437 1434 if lockref is None:
1438 1435 return None
1439 1436 l = lockref()
1440 1437 if l is None or not l.held:
1441 1438 return None
1442 1439 return l
1443 1440
1444 1441 def currentwlock(self):
1445 1442 """Returns the wlock if it's held, or None if it's not."""
1446 1443 return self._currentlock(self._wlockref)
1447 1444
1448 1445 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1449 1446 """
1450 1447 commit an individual file as part of a larger transaction
1451 1448 """
1452 1449
1453 1450 fname = fctx.path()
1454 1451 fparent1 = manifest1.get(fname, nullid)
1455 1452 fparent2 = manifest2.get(fname, nullid)
1456 1453 if isinstance(fctx, context.filectx):
1457 1454 node = fctx.filenode()
1458 1455 if node in [fparent1, fparent2]:
1459 1456 self.ui.debug('reusing %s filelog entry\n' % fname)
1460 1457 if manifest1.flags(fname) != fctx.flags():
1461 1458 changelist.append(fname)
1462 1459 return node
1463 1460
1464 1461 flog = self.file(fname)
1465 1462 meta = {}
1466 1463 copy = fctx.renamed()
1467 1464 if copy and copy[0] != fname:
1468 1465 # Mark the new revision of this file as a copy of another
1469 1466 # file. This copy data will effectively act as a parent
1470 1467 # of this new revision. If this is a merge, the first
1471 1468 # parent will be the nullid (meaning "look up the copy data")
1472 1469 # and the second one will be the other parent. For example:
1473 1470 #
1474 1471 # 0 --- 1 --- 3 rev1 changes file foo
1475 1472 # \ / rev2 renames foo to bar and changes it
1476 1473 # \- 2 -/ rev3 should have bar with all changes and
1477 1474 # should record that bar descends from
1478 1475 # bar in rev2 and foo in rev1
1479 1476 #
1480 1477 # this allows this merge to succeed:
1481 1478 #
1482 1479 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1483 1480 # \ / merging rev3 and rev4 should use bar@rev2
1484 1481 # \- 2 --- 4 as the merge base
1485 1482 #
1486 1483
1487 1484 cfname = copy[0]
1488 1485 crev = manifest1.get(cfname)
1489 1486 newfparent = fparent2
1490 1487
1491 1488 if manifest2: # branch merge
1492 1489 if fparent2 == nullid or crev is None: # copied on remote side
1493 1490 if cfname in manifest2:
1494 1491 crev = manifest2[cfname]
1495 1492 newfparent = fparent1
1496 1493
1497 1494 # Here, we used to search backwards through history to try to find
1498 1495 # where the file copy came from if the source of a copy was not in
1499 1496 # the parent directory. However, this doesn't actually make sense to
1500 1497 # do (what does a copy from something not in your working copy even
1501 1498 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
1502 1499 # the user that copy information was dropped, so if they didn't
1503 1500 # expect this outcome it can be fixed, but this is the correct
1504 1501 # behavior in this circumstance.
1505 1502
1506 1503 if crev:
1507 1504 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1508 1505 meta["copy"] = cfname
1509 1506 meta["copyrev"] = hex(crev)
1510 1507 fparent1, fparent2 = nullid, newfparent
1511 1508 else:
1512 1509 self.ui.warn(_("warning: can't find ancestor for '%s' "
1513 1510 "copied from '%s'!\n") % (fname, cfname))
1514 1511
1515 1512 elif fparent1 == nullid:
1516 1513 fparent1, fparent2 = fparent2, nullid
1517 1514 elif fparent2 != nullid:
1518 1515 # is one parent an ancestor of the other?
1519 1516 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1520 1517 if fparent1 in fparentancestors:
1521 1518 fparent1, fparent2 = fparent2, nullid
1522 1519 elif fparent2 in fparentancestors:
1523 1520 fparent2 = nullid
1524 1521
1525 1522 # is the file changed?
1526 1523 text = fctx.data()
1527 1524 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1528 1525 changelist.append(fname)
1529 1526 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1530 1527 # are just the flags changed during merge?
1531 1528 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1532 1529 changelist.append(fname)
1533 1530
1534 1531 return fparent1
1535 1532
1536 1533 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
1537 1534 """check for commit arguments that aren't committable"""
1538 1535 if match.isexact() or match.prefix():
1539 1536 matched = set(status.modified + status.added + status.removed)
1540 1537
1541 1538 for f in match.files():
1542 1539 f = self.dirstate.normalize(f)
1543 1540 if f == '.' or f in matched or f in wctx.substate:
1544 1541 continue
1545 1542 if f in status.deleted:
1546 1543 fail(f, _('file not found!'))
1547 1544 if f in vdirs: # visited directory
1548 1545 d = f + '/'
1549 1546 for mf in matched:
1550 1547 if mf.startswith(d):
1551 1548 break
1552 1549 else:
1553 1550 fail(f, _("no match under directory!"))
1554 1551 elif f not in self.dirstate:
1555 1552 fail(f, _("file not tracked!"))
1556 1553
1557 1554 @unfilteredmethod
1558 1555 def commit(self, text="", user=None, date=None, match=None, force=False,
1559 1556 editor=False, extra=None):
1560 1557 """Add a new revision to current repository.
1561 1558
1562 1559 Revision information is gathered from the working directory,
1563 1560 match can be used to filter the committed files. If editor is
1564 1561 supplied, it is called to get a commit message.
1565 1562 """
1566 1563 if extra is None:
1567 1564 extra = {}
1568 1565
1569 1566 def fail(f, msg):
1570 1567 raise error.Abort('%s: %s' % (f, msg))
1571 1568
1572 1569 if not match:
1573 1570 match = matchmod.always(self.root, '')
1574 1571
1575 1572 if not force:
1576 1573 vdirs = []
1577 1574 match.explicitdir = vdirs.append
1578 1575 match.bad = fail
1579 1576
1580 1577 wlock = lock = tr = None
1581 1578 try:
1582 1579 wlock = self.wlock()
1583 1580 lock = self.lock() # for recent changelog (see issue4368)
1584 1581
1585 1582 wctx = self[None]
1586 1583 merge = len(wctx.parents()) > 1
1587 1584
1588 1585 if not force and merge and match.ispartial():
1589 1586 raise error.Abort(_('cannot partially commit a merge '
1590 1587 '(do not specify files or patterns)'))
1591 1588
1592 1589 status = self.status(match=match, clean=force)
1593 1590 if force:
1594 1591 status.modified.extend(status.clean) # mq may commit clean files
1595 1592
1596 1593 # check subrepos
1597 1594 subs = []
1598 1595 commitsubs = set()
1599 1596 newstate = wctx.substate.copy()
1600 1597 # only manage subrepos and .hgsubstate if .hgsub is present
1601 1598 if '.hgsub' in wctx:
1602 1599 # we'll decide whether to track this ourselves, thanks
1603 1600 for c in status.modified, status.added, status.removed:
1604 1601 if '.hgsubstate' in c:
1605 1602 c.remove('.hgsubstate')
1606 1603
1607 1604 # compare current state to last committed state
1608 1605 # build new substate based on last committed state
1609 1606 oldstate = wctx.p1().substate
1610 1607 for s in sorted(newstate.keys()):
1611 1608 if not match(s):
1612 1609 # ignore working copy, use old state if present
1613 1610 if s in oldstate:
1614 1611 newstate[s] = oldstate[s]
1615 1612 continue
1616 1613 if not force:
1617 1614 raise error.Abort(
1618 1615 _("commit with new subrepo %s excluded") % s)
1619 1616 dirtyreason = wctx.sub(s).dirtyreason(True)
1620 1617 if dirtyreason:
1621 1618 if not self.ui.configbool('ui', 'commitsubrepos'):
1622 1619 raise error.Abort(dirtyreason,
1623 1620 hint=_("use --subrepos for recursive commit"))
1624 1621 subs.append(s)
1625 1622 commitsubs.add(s)
1626 1623 else:
1627 1624 bs = wctx.sub(s).basestate()
1628 1625 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1629 1626 if oldstate.get(s, (None, None, None))[1] != bs:
1630 1627 subs.append(s)
1631 1628
1632 1629 # check for removed subrepos
1633 1630 for p in wctx.parents():
1634 1631 r = [s for s in p.substate if s not in newstate]
1635 1632 subs += [s for s in r if match(s)]
1636 1633 if subs:
1637 1634 if (not match('.hgsub') and
1638 1635 '.hgsub' in (wctx.modified() + wctx.added())):
1639 1636 raise error.Abort(
1640 1637 _("can't commit subrepos without .hgsub"))
1641 1638 status.modified.insert(0, '.hgsubstate')
1642 1639
1643 1640 elif '.hgsub' in status.removed:
1644 1641 # clean up .hgsubstate when .hgsub is removed
1645 1642 if ('.hgsubstate' in wctx and
1646 1643 '.hgsubstate' not in (status.modified + status.added +
1647 1644 status.removed)):
1648 1645 status.removed.insert(0, '.hgsubstate')
1649 1646
1650 1647 # make sure all explicit patterns are matched
1651 1648 if not force:
1652 1649 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
1653 1650
1654 1651 cctx = context.workingcommitctx(self, status,
1655 1652 text, user, date, extra)
1656 1653
1657 1654 # internal config: ui.allowemptycommit
1658 1655 allowemptycommit = (wctx.branch() != wctx.p1().branch()
1659 1656 or extra.get('close') or merge or cctx.files()
1660 1657 or self.ui.configbool('ui', 'allowemptycommit'))
1661 1658 if not allowemptycommit:
1662 1659 return None
1663 1660
1664 1661 if merge and cctx.deleted():
1665 1662 raise error.Abort(_("cannot commit merge with missing files"))
1666 1663
1667 1664 ms = mergemod.mergestate.read(self)
1668 1665 mergeutil.checkunresolved(ms)
1669 1666
1670 1667 if editor:
1671 1668 cctx._text = editor(self, cctx, subs)
1672 1669 edited = (text != cctx._text)
1673 1670
1674 1671 # Save commit message in case this transaction gets rolled back
1675 1672 # (e.g. by a pretxncommit hook). Leave the content alone on
1676 1673 # the assumption that the user will use the same editor again.
1677 1674 msgfn = self.savecommitmessage(cctx._text)
1678 1675
1679 1676 # commit subs and write new state
1680 1677 if subs:
1681 1678 for s in sorted(commitsubs):
1682 1679 sub = wctx.sub(s)
1683 1680 self.ui.status(_('committing subrepository %s\n') %
1684 1681 subrepo.subrelpath(sub))
1685 1682 sr = sub.commit(cctx._text, user, date)
1686 1683 newstate[s] = (newstate[s][0], sr)
1687 1684 subrepo.writestate(self, newstate)
1688 1685
1689 1686 p1, p2 = self.dirstate.parents()
1690 1687 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1691 1688 try:
1692 1689 self.hook("precommit", throw=True, parent1=hookp1,
1693 1690 parent2=hookp2)
1694 1691 tr = self.transaction('commit')
1695 1692 ret = self.commitctx(cctx, True)
1696 1693 except: # re-raises
1697 1694 if edited:
1698 1695 self.ui.write(
1699 1696 _('note: commit message saved in %s\n') % msgfn)
1700 1697 raise
1701 1698 # update bookmarks, dirstate and mergestate
1702 1699 bookmarks.update(self, [p1, p2], ret)
1703 1700 cctx.markcommitted(ret)
1704 1701 ms.reset()
1705 1702 tr.close()
1706 1703
1707 1704 finally:
1708 1705 lockmod.release(tr, lock, wlock)
1709 1706
1710 1707 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1711 1708 # hack for command that use a temporary commit (eg: histedit)
1712 1709 # temporary commit got stripped before hook release
1713 1710 if self.changelog.hasnode(ret):
1714 1711 self.hook("commit", node=node, parent1=parent1,
1715 1712 parent2=parent2)
1716 1713 self._afterlock(commithook)
1717 1714 return ret
1718 1715
1719 1716 @unfilteredmethod
1720 1717 def commitctx(self, ctx, error=False):
1721 1718 """Add a new revision to current repository.
1722 1719 Revision information is passed via the context argument.
1723 1720 """
1724 1721
1725 1722 tr = None
1726 1723 p1, p2 = ctx.p1(), ctx.p2()
1727 1724 user = ctx.user()
1728 1725
1729 1726 lock = self.lock()
1730 1727 try:
1731 1728 tr = self.transaction("commit")
1732 1729 trp = weakref.proxy(tr)
1733 1730
1734 1731 if ctx.manifestnode():
1735 1732 # reuse an existing manifest revision
1736 1733 mn = ctx.manifestnode()
1737 1734 files = ctx.files()
1738 1735 elif ctx.files():
1739 1736 m1ctx = p1.manifestctx()
1740 1737 m2ctx = p2.manifestctx()
1741 1738 mctx = m1ctx.copy()
1742 1739
1743 1740 m = mctx.read()
1744 1741 m1 = m1ctx.read()
1745 1742 m2 = m2ctx.read()
1746 1743
1747 1744 # check in files
1748 1745 added = []
1749 1746 changed = []
1750 1747 removed = list(ctx.removed())
1751 1748 linkrev = len(self)
1752 1749 self.ui.note(_("committing files:\n"))
1753 1750 for f in sorted(ctx.modified() + ctx.added()):
1754 1751 self.ui.note(f + "\n")
1755 1752 try:
1756 1753 fctx = ctx[f]
1757 1754 if fctx is None:
1758 1755 removed.append(f)
1759 1756 else:
1760 1757 added.append(f)
1761 1758 m[f] = self._filecommit(fctx, m1, m2, linkrev,
1762 1759 trp, changed)
1763 1760 m.setflag(f, fctx.flags())
1764 1761 except OSError as inst:
1765 1762 self.ui.warn(_("trouble committing %s!\n") % f)
1766 1763 raise
1767 1764 except IOError as inst:
1768 1765 errcode = getattr(inst, 'errno', errno.ENOENT)
1769 1766 if error or errcode and errcode != errno.ENOENT:
1770 1767 self.ui.warn(_("trouble committing %s!\n") % f)
1771 1768 raise
1772 1769
1773 1770 # update manifest
1774 1771 self.ui.note(_("committing manifest\n"))
1775 1772 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1776 1773 drop = [f for f in removed if f in m]
1777 1774 for f in drop:
1778 1775 del m[f]
1779 1776 mn = mctx.write(trp, linkrev,
1780 1777 p1.manifestnode(), p2.manifestnode(),
1781 1778 added, drop)
1782 1779 files = changed + removed
1783 1780 else:
1784 1781 mn = p1.manifestnode()
1785 1782 files = []
1786 1783
1787 1784 # update changelog
1788 1785 self.ui.note(_("committing changelog\n"))
1789 1786 self.changelog.delayupdate(tr)
1790 1787 n = self.changelog.add(mn, files, ctx.description(),
1791 1788 trp, p1.node(), p2.node(),
1792 1789 user, ctx.date(), ctx.extra().copy())
1793 1790 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1794 1791 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1795 1792 parent2=xp2)
1796 1793 # set the new commit is proper phase
1797 1794 targetphase = subrepo.newcommitphase(self.ui, ctx)
1798 1795 if targetphase:
1799 1796 # retract boundary do not alter parent changeset.
1800 1797 # if a parent have higher the resulting phase will
1801 1798 # be compliant anyway
1802 1799 #
1803 1800 # if minimal phase was 0 we don't need to retract anything
1804 1801 phases.retractboundary(self, tr, targetphase, [n])
1805 1802 tr.close()
1806 1803 branchmap.updatecache(self.filtered('served'))
1807 1804 return n
1808 1805 finally:
1809 1806 if tr:
1810 1807 tr.release()
1811 1808 lock.release()
1812 1809
1813 1810 @unfilteredmethod
1814 1811 def destroying(self):
1815 1812 '''Inform the repository that nodes are about to be destroyed.
1816 1813 Intended for use by strip and rollback, so there's a common
1817 1814 place for anything that has to be done before destroying history.
1818 1815
1819 1816 This is mostly useful for saving state that is in memory and waiting
1820 1817 to be flushed when the current lock is released. Because a call to
1821 1818 destroyed is imminent, the repo will be invalidated causing those
1822 1819 changes to stay in memory (waiting for the next unlock), or vanish
1823 1820 completely.
1824 1821 '''
1825 1822 # When using the same lock to commit and strip, the phasecache is left
1826 1823 # dirty after committing. Then when we strip, the repo is invalidated,
1827 1824 # causing those changes to disappear.
1828 1825 if '_phasecache' in vars(self):
1829 1826 self._phasecache.write()
1830 1827
1831 1828 @unfilteredmethod
1832 1829 def destroyed(self):
1833 1830 '''Inform the repository that nodes have been destroyed.
1834 1831 Intended for use by strip and rollback, so there's a common
1835 1832 place for anything that has to be done after destroying history.
1836 1833 '''
1837 1834 # When one tries to:
1838 1835 # 1) destroy nodes thus calling this method (e.g. strip)
1839 1836 # 2) use phasecache somewhere (e.g. commit)
1840 1837 #
1841 1838 # then 2) will fail because the phasecache contains nodes that were
1842 1839 # removed. We can either remove phasecache from the filecache,
1843 1840 # causing it to reload next time it is accessed, or simply filter
1844 1841 # the removed nodes now and write the updated cache.
1845 1842 self._phasecache.filterunknown(self)
1846 1843 self._phasecache.write()
1847 1844
1848 1845 # update the 'served' branch cache to help read only server process
1849 1846 # Thanks to branchcache collaboration this is done from the nearest
1850 1847 # filtered subset and it is expected to be fast.
1851 1848 branchmap.updatecache(self.filtered('served'))
1852 1849
1853 1850 # Ensure the persistent tag cache is updated. Doing it now
1854 1851 # means that the tag cache only has to worry about destroyed
1855 1852 # heads immediately after a strip/rollback. That in turn
1856 1853 # guarantees that "cachetip == currenttip" (comparing both rev
1857 1854 # and node) always means no nodes have been added or destroyed.
1858 1855
1859 1856 # XXX this is suboptimal when qrefresh'ing: we strip the current
1860 1857 # head, refresh the tag cache, then immediately add a new head.
1861 1858 # But I think doing it this way is necessary for the "instant
1862 1859 # tag cache retrieval" case to work.
1863 1860 self.invalidate()
1864 1861
1865 1862 def walk(self, match, node=None):
1866 1863 '''
1867 1864 walk recursively through the directory tree or a given
1868 1865 changeset, finding all files matched by the match
1869 1866 function
1870 1867 '''
1871 1868 return self[node].walk(match)
1872 1869
1873 1870 def status(self, node1='.', node2=None, match=None,
1874 1871 ignored=False, clean=False, unknown=False,
1875 1872 listsubrepos=False):
1876 1873 '''a convenience method that calls node1.status(node2)'''
1877 1874 return self[node1].status(node2, match, ignored, clean, unknown,
1878 1875 listsubrepos)
1879 1876
1880 1877 def heads(self, start=None):
1881 1878 if start is None:
1882 1879 cl = self.changelog
1883 1880 headrevs = reversed(cl.headrevs())
1884 1881 return [cl.node(rev) for rev in headrevs]
1885 1882
1886 1883 heads = self.changelog.heads(start)
1887 1884 # sort the output in rev descending order
1888 1885 return sorted(heads, key=self.changelog.rev, reverse=True)
1889 1886
1890 1887 def branchheads(self, branch=None, start=None, closed=False):
1891 1888 '''return a (possibly filtered) list of heads for the given branch
1892 1889
1893 1890 Heads are returned in topological order, from newest to oldest.
1894 1891 If branch is None, use the dirstate branch.
1895 1892 If start is not None, return only heads reachable from start.
1896 1893 If closed is True, return heads that are marked as closed as well.
1897 1894 '''
1898 1895 if branch is None:
1899 1896 branch = self[None].branch()
1900 1897 branches = self.branchmap()
1901 1898 if branch not in branches:
1902 1899 return []
1903 1900 # the cache returns heads ordered lowest to highest
1904 1901 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
1905 1902 if start is not None:
1906 1903 # filter out the heads that cannot be reached from startrev
1907 1904 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1908 1905 bheads = [h for h in bheads if h in fbheads]
1909 1906 return bheads
1910 1907
1911 1908 def branches(self, nodes):
1912 1909 if not nodes:
1913 1910 nodes = [self.changelog.tip()]
1914 1911 b = []
1915 1912 for n in nodes:
1916 1913 t = n
1917 1914 while True:
1918 1915 p = self.changelog.parents(n)
1919 1916 if p[1] != nullid or p[0] == nullid:
1920 1917 b.append((t, n, p[0], p[1]))
1921 1918 break
1922 1919 n = p[0]
1923 1920 return b
1924 1921
1925 1922 def between(self, pairs):
1926 1923 r = []
1927 1924
1928 1925 for top, bottom in pairs:
1929 1926 n, l, i = top, [], 0
1930 1927 f = 1
1931 1928
1932 1929 while n != bottom and n != nullid:
1933 1930 p = self.changelog.parents(n)[0]
1934 1931 if i == f:
1935 1932 l.append(n)
1936 1933 f = f * 2
1937 1934 n = p
1938 1935 i += 1
1939 1936
1940 1937 r.append(l)
1941 1938
1942 1939 return r
1943 1940
1944 1941 def checkpush(self, pushop):
1945 1942 """Extensions can override this function if additional checks have
1946 1943 to be performed before pushing, or call it if they override push
1947 1944 command.
1948 1945 """
1949 1946 pass
1950 1947
1951 1948 @unfilteredpropertycache
1952 1949 def prepushoutgoinghooks(self):
1953 1950 """Return util.hooks consists of a pushop with repo, remote, outgoing
1954 1951 methods, which are called before pushing changesets.
1955 1952 """
1956 1953 return util.hooks()
1957 1954
1958 1955 def pushkey(self, namespace, key, old, new):
1959 1956 try:
1960 1957 tr = self.currenttransaction()
1961 1958 hookargs = {}
1962 1959 if tr is not None:
1963 1960 hookargs.update(tr.hookargs)
1964 1961 hookargs['namespace'] = namespace
1965 1962 hookargs['key'] = key
1966 1963 hookargs['old'] = old
1967 1964 hookargs['new'] = new
1968 1965 self.hook('prepushkey', throw=True, **hookargs)
1969 1966 except error.HookAbort as exc:
1970 1967 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
1971 1968 if exc.hint:
1972 1969 self.ui.write_err(_("(%s)\n") % exc.hint)
1973 1970 return False
1974 1971 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
1975 1972 ret = pushkey.push(self, namespace, key, old, new)
1976 1973 def runhook():
1977 1974 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
1978 1975 ret=ret)
1979 1976 self._afterlock(runhook)
1980 1977 return ret
1981 1978
1982 1979 def listkeys(self, namespace):
1983 1980 self.hook('prelistkeys', throw=True, namespace=namespace)
1984 1981 self.ui.debug('listing keys for "%s"\n' % namespace)
1985 1982 values = pushkey.list(self, namespace)
1986 1983 self.hook('listkeys', namespace=namespace, values=values)
1987 1984 return values
1988 1985
1989 1986 def debugwireargs(self, one, two, three=None, four=None, five=None):
1990 1987 '''used to test argument passing over the wire'''
1991 1988 return "%s %s %s %s %s" % (one, two, three, four, five)
1992 1989
1993 1990 def savecommitmessage(self, text):
1994 1991 fp = self.vfs('last-message.txt', 'wb')
1995 1992 try:
1996 1993 fp.write(text)
1997 1994 finally:
1998 1995 fp.close()
1999 1996 return self.pathto(fp.name[len(self.root) + 1:])
2000 1997
2001 1998 # used to avoid circular references so destructors work
2002 1999 def aftertrans(files):
2003 2000 renamefiles = [tuple(t) for t in files]
2004 2001 def a():
2005 2002 for vfs, src, dest in renamefiles:
2006 2003 # if src and dest refer to a same file, vfs.rename is a no-op,
2007 2004 # leaving both src and dest on disk. delete dest to make sure
2008 2005 # the rename couldn't be such a no-op.
2009 2006 vfs.tryunlink(dest)
2010 2007 try:
2011 2008 vfs.rename(src, dest)
2012 2009 except OSError: # journal file does not yet exist
2013 2010 pass
2014 2011 return a
2015 2012
2016 2013 def undoname(fn):
2017 2014 base, name = os.path.split(fn)
2018 2015 assert name.startswith('journal')
2019 2016 return os.path.join(base, name.replace('journal', 'undo', 1))
2020 2017
2021 2018 def instance(ui, path, create):
2022 2019 return localrepository(ui, util.urllocalpath(path), create)
2023 2020
2024 2021 def islocal(path):
2025 2022 return True
2026 2023
2027 2024 def newreporequirements(repo):
2028 2025 """Determine the set of requirements for a new local repository.
2029 2026
2030 2027 Extensions can wrap this function to specify custom requirements for
2031 2028 new repositories.
2032 2029 """
2033 2030 ui = repo.ui
2034 2031 requirements = set(['revlogv1'])
2035 2032 if ui.configbool('format', 'usestore', True):
2036 2033 requirements.add('store')
2037 2034 if ui.configbool('format', 'usefncache', True):
2038 2035 requirements.add('fncache')
2039 2036 if ui.configbool('format', 'dotencode', True):
2040 2037 requirements.add('dotencode')
2041 2038
2042 2039 compengine = ui.config('experimental', 'format.compression', 'zlib')
2043 2040 if compengine not in util.compengines:
2044 2041 raise error.Abort(_('compression engine %s defined by '
2045 2042 'experimental.format.compression not available') %
2046 2043 compengine,
2047 2044 hint=_('run "hg debuginstall" to list available '
2048 2045 'compression engines'))
2049 2046
2050 2047 # zlib is the historical default and doesn't need an explicit requirement.
2051 2048 if compengine != 'zlib':
2052 2049 requirements.add('exp-compression-%s' % compengine)
2053 2050
2054 2051 if scmutil.gdinitconfig(ui):
2055 2052 requirements.add('generaldelta')
2056 2053 if ui.configbool('experimental', 'treemanifest', False):
2057 2054 requirements.add('treemanifest')
2058 2055 if ui.configbool('experimental', 'manifestv2', False):
2059 2056 requirements.add('manifestv2')
2060 2057
2061 2058 return requirements
@@ -1,2147 +1,2138
1 1 # revlog.py - storage back-end 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 """Storage back-end for Mercurial.
9 9
10 10 This provides efficient delta storage with O(1) retrieve and append
11 11 and O(changes) merge between branches.
12 12 """
13 13
14 14 from __future__ import absolute_import
15 15
16 16 import collections
17 17 import errno
18 18 import hashlib
19 19 import os
20 20 import struct
21 21 import zlib
22 22
23 23 # import stuff from node for others to import from revlog
24 24 from .node import (
25 25 bin,
26 26 hex,
27 27 nullid,
28 28 nullrev,
29 29 )
30 30 from .i18n import _
31 31 from . import (
32 32 ancestor,
33 33 error,
34 34 mdiff,
35 35 parsers,
36 36 pycompat,
37 37 templatefilters,
38 38 util,
39 39 )
40 40
41 41 _pack = struct.pack
42 42 _unpack = struct.unpack
43 43 # Aliased for performance.
44 44 _zlibdecompress = zlib.decompress
45 45
46 46 # revlog header flags
47 47 REVLOGV0 = 0
48 48 REVLOGNG = 1
49 49 REVLOGNGINLINEDATA = (1 << 16)
50 50 REVLOGGENERALDELTA = (1 << 17)
51 51 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
52 52 REVLOG_DEFAULT_FORMAT = REVLOGNG
53 53 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
54 54 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
55 55
56 56 # revlog index flags
57 57 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
58 58 REVIDX_ELLIPSIS = (1 << 14) # revision hash does not match data (narrowhg)
59 59 REVIDX_EXTSTORED = (1 << 13) # revision data is stored externally
60 60 REVIDX_DEFAULT_FLAGS = 0
61 61 # stable order in which flags need to be processed and their processors applied
62 62 REVIDX_FLAGS_ORDER = [
63 63 REVIDX_ISCENSORED,
64 64 REVIDX_ELLIPSIS,
65 65 REVIDX_EXTSTORED,
66 66 ]
67 67 REVIDX_KNOWN_FLAGS = util.bitsfrom(REVIDX_FLAGS_ORDER)
68 68
69 69 # max size of revlog with inline data
70 70 _maxinline = 131072
71 71 _chunksize = 1048576
72 72
73 73 RevlogError = error.RevlogError
74 74 LookupError = error.LookupError
75 75 CensoredNodeError = error.CensoredNodeError
76 76 ProgrammingError = error.ProgrammingError
77 77
78 78 # Store flag processors (cf. 'addflagprocessor()' to register)
79 79 _flagprocessors = {
80 80 REVIDX_ISCENSORED: None,
81 81 }
82 82
83 83 def addflagprocessor(flag, processor):
84 84 """Register a flag processor on a revision data flag.
85 85
86 86 Invariant:
87 87 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER.
88 88 - Only one flag processor can be registered on a specific flag.
89 89 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
90 90 following signatures:
91 91 - (read) f(self, rawtext) -> text, bool
92 92 - (write) f(self, text) -> rawtext, bool
93 93 - (raw) f(self, rawtext) -> bool
94 94 "text" is presented to the user. "rawtext" is stored in revlog data, not
95 95 directly visible to the user.
96 96 The boolean returned by these transforms is used to determine whether
97 97 the returned text can be used for hash integrity checking. For example,
98 98 if "write" returns False, then "text" is used to generate hash. If
99 99 "write" returns True, that basically means "rawtext" returned by "write"
100 100 should be used to generate hash. Usually, "write" and "read" return
101 101 different booleans. And "raw" returns a same boolean as "write".
102 102
103 103 Note: The 'raw' transform is used for changegroup generation and in some
104 104 debug commands. In this case the transform only indicates whether the
105 105 contents can be used for hash integrity checks.
106 106 """
107 107 if not flag & REVIDX_KNOWN_FLAGS:
108 108 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
109 109 raise ProgrammingError(msg)
110 110 if flag not in REVIDX_FLAGS_ORDER:
111 111 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
112 112 raise ProgrammingError(msg)
113 113 if flag in _flagprocessors:
114 114 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
115 115 raise error.Abort(msg)
116 116 _flagprocessors[flag] = processor
117 117
118 118 def getoffset(q):
119 119 return int(q >> 16)
120 120
121 121 def gettype(q):
122 122 return int(q & 0xFFFF)
123 123
124 124 def offset_type(offset, type):
125 125 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
126 126 raise ValueError('unknown revlog index flags')
127 127 return int(int(offset) << 16 | type)
128 128
129 129 _nullhash = hashlib.sha1(nullid)
130 130
131 131 def hash(text, p1, p2):
132 132 """generate a hash from the given text and its parent hashes
133 133
134 134 This hash combines both the current file contents and its history
135 135 in a manner that makes it easy to distinguish nodes with the same
136 136 content in the revision graph.
137 137 """
138 138 # As of now, if one of the parent node is null, p2 is null
139 139 if p2 == nullid:
140 140 # deep copy of a hash is faster than creating one
141 141 s = _nullhash.copy()
142 142 s.update(p1)
143 143 else:
144 144 # none of the parent nodes are nullid
145 145 l = [p1, p2]
146 146 l.sort()
147 147 s = hashlib.sha1(l[0])
148 148 s.update(l[1])
149 149 s.update(text)
150 150 return s.digest()
151 151
152 152 # index v0:
153 153 # 4 bytes: offset
154 154 # 4 bytes: compressed length
155 155 # 4 bytes: base rev
156 156 # 4 bytes: link rev
157 157 # 20 bytes: parent 1 nodeid
158 158 # 20 bytes: parent 2 nodeid
159 159 # 20 bytes: nodeid
160 160 indexformatv0 = ">4l20s20s20s"
161 161
162 162 class revlogoldio(object):
163 163 def __init__(self):
164 164 self.size = struct.calcsize(indexformatv0)
165 165
166 166 def parseindex(self, data, inline):
167 167 s = self.size
168 168 index = []
169 169 nodemap = {nullid: nullrev}
170 170 n = off = 0
171 171 l = len(data)
172 172 while off + s <= l:
173 173 cur = data[off:off + s]
174 174 off += s
175 175 e = _unpack(indexformatv0, cur)
176 176 # transform to revlogv1 format
177 177 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
178 178 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
179 179 index.append(e2)
180 180 nodemap[e[6]] = n
181 181 n += 1
182 182
183 183 # add the magic null revision at -1
184 184 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
185 185
186 186 return index, nodemap, None
187 187
188 188 def packentry(self, entry, node, version, rev):
189 189 if gettype(entry[0]):
190 190 raise RevlogError(_("index entry flags need RevlogNG"))
191 191 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
192 192 node(entry[5]), node(entry[6]), entry[7])
193 193 return _pack(indexformatv0, *e2)
194 194
195 195 # index ng:
196 196 # 6 bytes: offset
197 197 # 2 bytes: flags
198 198 # 4 bytes: compressed length
199 199 # 4 bytes: uncompressed length
200 200 # 4 bytes: base rev
201 201 # 4 bytes: link rev
202 202 # 4 bytes: parent 1 rev
203 203 # 4 bytes: parent 2 rev
204 204 # 32 bytes: nodeid
205 205 indexformatng = ">Qiiiiii20s12x"
206 206 versionformat = ">I"
207 207
208 208 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
209 209 # signed integer)
210 210 _maxentrysize = 0x7fffffff
211 211
212 212 class revlogio(object):
213 213 def __init__(self):
214 214 self.size = struct.calcsize(indexformatng)
215 215
216 216 def parseindex(self, data, inline):
217 217 # call the C implementation to parse the index data
218 218 index, cache = parsers.parse_index2(data, inline)
219 219 return index, getattr(index, 'nodemap', None), cache
220 220
221 221 def packentry(self, entry, node, version, rev):
222 222 p = _pack(indexformatng, *entry)
223 223 if rev == 0:
224 224 p = _pack(versionformat, version) + p[4:]
225 225 return p
226 226
227 227 class revlog(object):
228 228 """
229 229 the underlying revision storage object
230 230
231 231 A revlog consists of two parts, an index and the revision data.
232 232
233 233 The index is a file with a fixed record size containing
234 234 information on each revision, including its nodeid (hash), the
235 235 nodeids of its parents, the position and offset of its data within
236 236 the data file, and the revision it's based on. Finally, each entry
237 237 contains a linkrev entry that can serve as a pointer to external
238 238 data.
239 239
240 240 The revision data itself is a linear collection of data chunks.
241 241 Each chunk represents a revision and is usually represented as a
242 242 delta against the previous chunk. To bound lookup time, runs of
243 243 deltas are limited to about 2 times the length of the original
244 244 version data. This makes retrieval of a version proportional to
245 245 its size, or O(1) relative to the number of revisions.
246 246
247 247 Both pieces of the revlog are written to in an append-only
248 248 fashion, which means we never need to rewrite a file to insert or
249 249 remove data, and can use some simple techniques to avoid the need
250 250 for locking while reading.
251 251
252 252 If checkambig, indexfile is opened with checkambig=True at
253 253 writing, to avoid file stat ambiguity.
254 254 """
255 255 def __init__(self, opener, indexfile, checkambig=False):
256 256 """
257 257 create a revlog object
258 258
259 259 opener is a function that abstracts the file opening operation
260 260 and can be used to implement COW semantics or the like.
261 261 """
262 262 self.indexfile = indexfile
263 263 self.datafile = indexfile[:-2] + ".d"
264 264 self.opener = opener
265 265 # When True, indexfile is opened with checkambig=True at writing, to
266 266 # avoid file stat ambiguity.
267 267 self._checkambig = checkambig
268 268 # 3-tuple of (node, rev, text) for a raw revision.
269 269 self._cache = None
270 270 # Maps rev to chain base rev.
271 271 self._chainbasecache = util.lrucachedict(100)
272 272 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
273 273 self._chunkcache = (0, '')
274 274 # How much data to read and cache into the raw revlog data cache.
275 275 self._chunkcachesize = 65536
276 276 self._maxchainlen = None
277 277 self._aggressivemergedeltas = False
278 278 self.index = []
279 279 # Mapping of partial identifiers to full nodes.
280 280 self._pcache = {}
281 281 # Mapping of revision integer to full node.
282 282 self._nodecache = {nullid: nullrev}
283 283 self._nodepos = None
284 284 self._compengine = 'zlib'
285 self._maxdeltachainspan = -1
286 285
287 286 v = REVLOG_DEFAULT_VERSION
288 287 opts = getattr(opener, 'options', None)
289 288 if opts is not None:
290 289 if 'revlogv1' in opts:
291 290 if 'generaldelta' in opts:
292 291 v |= REVLOGGENERALDELTA
293 292 else:
294 293 v = 0
295 294 if 'chunkcachesize' in opts:
296 295 self._chunkcachesize = opts['chunkcachesize']
297 296 if 'maxchainlen' in opts:
298 297 self._maxchainlen = opts['maxchainlen']
299 298 if 'aggressivemergedeltas' in opts:
300 299 self._aggressivemergedeltas = opts['aggressivemergedeltas']
301 300 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
302 301 if 'compengine' in opts:
303 302 self._compengine = opts['compengine']
304 if 'maxdeltachainspan' in opts:
305 self._maxdeltachainspan = opts['maxdeltachainspan']
306 303
307 304 if self._chunkcachesize <= 0:
308 305 raise RevlogError(_('revlog chunk cache size %r is not greater '
309 306 'than 0') % self._chunkcachesize)
310 307 elif self._chunkcachesize & (self._chunkcachesize - 1):
311 308 raise RevlogError(_('revlog chunk cache size %r is not a power '
312 309 'of 2') % self._chunkcachesize)
313 310
314 311 indexdata = ''
315 312 self._initempty = True
316 313 try:
317 314 f = self.opener(self.indexfile)
318 315 indexdata = f.read()
319 316 f.close()
320 317 if len(indexdata) > 0:
321 318 v = struct.unpack(versionformat, indexdata[:4])[0]
322 319 self._initempty = False
323 320 except IOError as inst:
324 321 if inst.errno != errno.ENOENT:
325 322 raise
326 323
327 324 self.version = v
328 325 self._inline = v & REVLOGNGINLINEDATA
329 326 self._generaldelta = v & REVLOGGENERALDELTA
330 327 flags = v & ~0xFFFF
331 328 fmt = v & 0xFFFF
332 329 if fmt == REVLOGV0 and flags:
333 330 raise RevlogError(_("index %s unknown flags %#04x for format v0")
334 331 % (self.indexfile, flags >> 16))
335 332 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
336 333 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
337 334 % (self.indexfile, flags >> 16))
338 335 elif fmt > REVLOGNG:
339 336 raise RevlogError(_("index %s unknown format %d")
340 337 % (self.indexfile, fmt))
341 338
342 339 self.storedeltachains = True
343 340
344 341 self._io = revlogio()
345 342 if self.version == REVLOGV0:
346 343 self._io = revlogoldio()
347 344 try:
348 345 d = self._io.parseindex(indexdata, self._inline)
349 346 except (ValueError, IndexError):
350 347 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
351 348 self.index, nodemap, self._chunkcache = d
352 349 if nodemap is not None:
353 350 self.nodemap = self._nodecache = nodemap
354 351 if not self._chunkcache:
355 352 self._chunkclear()
356 353 # revnum -> (chain-length, sum-delta-length)
357 354 self._chaininfocache = {}
358 355 # revlog header -> revlog compressor
359 356 self._decompressors = {}
360 357
361 358 @util.propertycache
362 359 def _compressor(self):
363 360 return util.compengines[self._compengine].revlogcompressor()
364 361
365 362 def tip(self):
366 363 return self.node(len(self.index) - 2)
367 364 def __contains__(self, rev):
368 365 return 0 <= rev < len(self)
369 366 def __len__(self):
370 367 return len(self.index) - 1
371 368 def __iter__(self):
372 369 return iter(xrange(len(self)))
373 370 def revs(self, start=0, stop=None):
374 371 """iterate over all rev in this revlog (from start to stop)"""
375 372 step = 1
376 373 if stop is not None:
377 374 if start > stop:
378 375 step = -1
379 376 stop += step
380 377 else:
381 378 stop = len(self)
382 379 return xrange(start, stop, step)
383 380
384 381 @util.propertycache
385 382 def nodemap(self):
386 383 self.rev(self.node(0))
387 384 return self._nodecache
388 385
389 386 def hasnode(self, node):
390 387 try:
391 388 self.rev(node)
392 389 return True
393 390 except KeyError:
394 391 return False
395 392
396 393 def clearcaches(self):
397 394 self._cache = None
398 395 self._chainbasecache.clear()
399 396 self._chunkcache = (0, '')
400 397 self._pcache = {}
401 398
402 399 try:
403 400 self._nodecache.clearcaches()
404 401 except AttributeError:
405 402 self._nodecache = {nullid: nullrev}
406 403 self._nodepos = None
407 404
408 405 def rev(self, node):
409 406 try:
410 407 return self._nodecache[node]
411 408 except TypeError:
412 409 raise
413 410 except RevlogError:
414 411 # parsers.c radix tree lookup failed
415 412 raise LookupError(node, self.indexfile, _('no node'))
416 413 except KeyError:
417 414 # pure python cache lookup failed
418 415 n = self._nodecache
419 416 i = self.index
420 417 p = self._nodepos
421 418 if p is None:
422 419 p = len(i) - 2
423 420 for r in xrange(p, -1, -1):
424 421 v = i[r][7]
425 422 n[v] = r
426 423 if v == node:
427 424 self._nodepos = r - 1
428 425 return r
429 426 raise LookupError(node, self.indexfile, _('no node'))
430 427
431 428 # Accessors for index entries.
432 429
433 430 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
434 431 # are flags.
435 432 def start(self, rev):
436 433 return int(self.index[rev][0] >> 16)
437 434
438 435 def flags(self, rev):
439 436 return self.index[rev][0] & 0xFFFF
440 437
441 438 def length(self, rev):
442 439 return self.index[rev][1]
443 440
444 441 def rawsize(self, rev):
445 442 """return the length of the uncompressed text for a given revision"""
446 443 l = self.index[rev][2]
447 444 if l >= 0:
448 445 return l
449 446
450 447 t = self.revision(rev, raw=True)
451 448 return len(t)
452 449
453 450 def size(self, rev):
454 451 """length of non-raw text (processed by a "read" flag processor)"""
455 452 # fast path: if no "read" flag processor could change the content,
456 453 # size is rawsize. note: ELLIPSIS is known to not change the content.
457 454 flags = self.flags(rev)
458 455 if flags & (REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
459 456 return self.rawsize(rev)
460 457
461 458 return len(self.revision(rev, raw=False))
462 459
463 460 def chainbase(self, rev):
464 461 base = self._chainbasecache.get(rev)
465 462 if base is not None:
466 463 return base
467 464
468 465 index = self.index
469 466 base = index[rev][3]
470 467 while base != rev:
471 468 rev = base
472 469 base = index[rev][3]
473 470
474 471 self._chainbasecache[rev] = base
475 472 return base
476 473
477 474 def linkrev(self, rev):
478 475 return self.index[rev][4]
479 476
480 477 def parentrevs(self, rev):
481 478 return self.index[rev][5:7]
482 479
483 480 def node(self, rev):
484 481 return self.index[rev][7]
485 482
486 483 # Derived from index values.
487 484
488 485 def end(self, rev):
489 486 return self.start(rev) + self.length(rev)
490 487
491 488 def parents(self, node):
492 489 i = self.index
493 490 d = i[self.rev(node)]
494 491 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
495 492
496 493 def chainlen(self, rev):
497 494 return self._chaininfo(rev)[0]
498 495
499 496 def _chaininfo(self, rev):
500 497 chaininfocache = self._chaininfocache
501 498 if rev in chaininfocache:
502 499 return chaininfocache[rev]
503 500 index = self.index
504 501 generaldelta = self._generaldelta
505 502 iterrev = rev
506 503 e = index[iterrev]
507 504 clen = 0
508 505 compresseddeltalen = 0
509 506 while iterrev != e[3]:
510 507 clen += 1
511 508 compresseddeltalen += e[1]
512 509 if generaldelta:
513 510 iterrev = e[3]
514 511 else:
515 512 iterrev -= 1
516 513 if iterrev in chaininfocache:
517 514 t = chaininfocache[iterrev]
518 515 clen += t[0]
519 516 compresseddeltalen += t[1]
520 517 break
521 518 e = index[iterrev]
522 519 else:
523 520 # Add text length of base since decompressing that also takes
524 521 # work. For cache hits the length is already included.
525 522 compresseddeltalen += e[1]
526 523 r = (clen, compresseddeltalen)
527 524 chaininfocache[rev] = r
528 525 return r
529 526
530 527 def _deltachain(self, rev, stoprev=None):
531 528 """Obtain the delta chain for a revision.
532 529
533 530 ``stoprev`` specifies a revision to stop at. If not specified, we
534 531 stop at the base of the chain.
535 532
536 533 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
537 534 revs in ascending order and ``stopped`` is a bool indicating whether
538 535 ``stoprev`` was hit.
539 536 """
540 537 chain = []
541 538
542 539 # Alias to prevent attribute lookup in tight loop.
543 540 index = self.index
544 541 generaldelta = self._generaldelta
545 542
546 543 iterrev = rev
547 544 e = index[iterrev]
548 545 while iterrev != e[3] and iterrev != stoprev:
549 546 chain.append(iterrev)
550 547 if generaldelta:
551 548 iterrev = e[3]
552 549 else:
553 550 iterrev -= 1
554 551 e = index[iterrev]
555 552
556 553 if iterrev == stoprev:
557 554 stopped = True
558 555 else:
559 556 chain.append(iterrev)
560 557 stopped = False
561 558
562 559 chain.reverse()
563 560 return chain, stopped
564 561
565 562 def ancestors(self, revs, stoprev=0, inclusive=False):
566 563 """Generate the ancestors of 'revs' in reverse topological order.
567 564 Does not generate revs lower than stoprev.
568 565
569 566 See the documentation for ancestor.lazyancestors for more details."""
570 567
571 568 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
572 569 inclusive=inclusive)
573 570
574 571 def descendants(self, revs):
575 572 """Generate the descendants of 'revs' in revision order.
576 573
577 574 Yield a sequence of revision numbers starting with a child of
578 575 some rev in revs, i.e., each revision is *not* considered a
579 576 descendant of itself. Results are ordered by revision number (a
580 577 topological sort)."""
581 578 first = min(revs)
582 579 if first == nullrev:
583 580 for i in self:
584 581 yield i
585 582 return
586 583
587 584 seen = set(revs)
588 585 for i in self.revs(start=first + 1):
589 586 for x in self.parentrevs(i):
590 587 if x != nullrev and x in seen:
591 588 seen.add(i)
592 589 yield i
593 590 break
594 591
595 592 def findcommonmissing(self, common=None, heads=None):
596 593 """Return a tuple of the ancestors of common and the ancestors of heads
597 594 that are not ancestors of common. In revset terminology, we return the
598 595 tuple:
599 596
600 597 ::common, (::heads) - (::common)
601 598
602 599 The list is sorted by revision number, meaning it is
603 600 topologically sorted.
604 601
605 602 'heads' and 'common' are both lists of node IDs. If heads is
606 603 not supplied, uses all of the revlog's heads. If common is not
607 604 supplied, uses nullid."""
608 605 if common is None:
609 606 common = [nullid]
610 607 if heads is None:
611 608 heads = self.heads()
612 609
613 610 common = [self.rev(n) for n in common]
614 611 heads = [self.rev(n) for n in heads]
615 612
616 613 # we want the ancestors, but inclusive
617 614 class lazyset(object):
618 615 def __init__(self, lazyvalues):
619 616 self.addedvalues = set()
620 617 self.lazyvalues = lazyvalues
621 618
622 619 def __contains__(self, value):
623 620 return value in self.addedvalues or value in self.lazyvalues
624 621
625 622 def __iter__(self):
626 623 added = self.addedvalues
627 624 for r in added:
628 625 yield r
629 626 for r in self.lazyvalues:
630 627 if not r in added:
631 628 yield r
632 629
633 630 def add(self, value):
634 631 self.addedvalues.add(value)
635 632
636 633 def update(self, values):
637 634 self.addedvalues.update(values)
638 635
639 636 has = lazyset(self.ancestors(common))
640 637 has.add(nullrev)
641 638 has.update(common)
642 639
643 640 # take all ancestors from heads that aren't in has
644 641 missing = set()
645 642 visit = collections.deque(r for r in heads if r not in has)
646 643 while visit:
647 644 r = visit.popleft()
648 645 if r in missing:
649 646 continue
650 647 else:
651 648 missing.add(r)
652 649 for p in self.parentrevs(r):
653 650 if p not in has:
654 651 visit.append(p)
655 652 missing = list(missing)
656 653 missing.sort()
657 654 return has, [self.node(miss) for miss in missing]
658 655
659 656 def incrementalmissingrevs(self, common=None):
660 657 """Return an object that can be used to incrementally compute the
661 658 revision numbers of the ancestors of arbitrary sets that are not
662 659 ancestors of common. This is an ancestor.incrementalmissingancestors
663 660 object.
664 661
665 662 'common' is a list of revision numbers. If common is not supplied, uses
666 663 nullrev.
667 664 """
668 665 if common is None:
669 666 common = [nullrev]
670 667
671 668 return ancestor.incrementalmissingancestors(self.parentrevs, common)
672 669
673 670 def findmissingrevs(self, common=None, heads=None):
674 671 """Return the revision numbers of the ancestors of heads that
675 672 are not ancestors of common.
676 673
677 674 More specifically, return a list of revision numbers corresponding to
678 675 nodes N such that every N satisfies the following constraints:
679 676
680 677 1. N is an ancestor of some node in 'heads'
681 678 2. N is not an ancestor of any node in 'common'
682 679
683 680 The list is sorted by revision number, meaning it is
684 681 topologically sorted.
685 682
686 683 'heads' and 'common' are both lists of revision numbers. If heads is
687 684 not supplied, uses all of the revlog's heads. If common is not
688 685 supplied, uses nullid."""
689 686 if common is None:
690 687 common = [nullrev]
691 688 if heads is None:
692 689 heads = self.headrevs()
693 690
694 691 inc = self.incrementalmissingrevs(common=common)
695 692 return inc.missingancestors(heads)
696 693
697 694 def findmissing(self, common=None, heads=None):
698 695 """Return the ancestors of heads that are not ancestors of common.
699 696
700 697 More specifically, return a list of nodes N such that every N
701 698 satisfies the following constraints:
702 699
703 700 1. N is an ancestor of some node in 'heads'
704 701 2. N is not an ancestor of any node in 'common'
705 702
706 703 The list is sorted by revision number, meaning it is
707 704 topologically sorted.
708 705
709 706 'heads' and 'common' are both lists of node IDs. If heads is
710 707 not supplied, uses all of the revlog's heads. If common is not
711 708 supplied, uses nullid."""
712 709 if common is None:
713 710 common = [nullid]
714 711 if heads is None:
715 712 heads = self.heads()
716 713
717 714 common = [self.rev(n) for n in common]
718 715 heads = [self.rev(n) for n in heads]
719 716
720 717 inc = self.incrementalmissingrevs(common=common)
721 718 return [self.node(r) for r in inc.missingancestors(heads)]
722 719
723 720 def nodesbetween(self, roots=None, heads=None):
724 721 """Return a topological path from 'roots' to 'heads'.
725 722
726 723 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
727 724 topologically sorted list of all nodes N that satisfy both of
728 725 these constraints:
729 726
730 727 1. N is a descendant of some node in 'roots'
731 728 2. N is an ancestor of some node in 'heads'
732 729
733 730 Every node is considered to be both a descendant and an ancestor
734 731 of itself, so every reachable node in 'roots' and 'heads' will be
735 732 included in 'nodes'.
736 733
737 734 'outroots' is the list of reachable nodes in 'roots', i.e., the
738 735 subset of 'roots' that is returned in 'nodes'. Likewise,
739 736 'outheads' is the subset of 'heads' that is also in 'nodes'.
740 737
741 738 'roots' and 'heads' are both lists of node IDs. If 'roots' is
742 739 unspecified, uses nullid as the only root. If 'heads' is
743 740 unspecified, uses list of all of the revlog's heads."""
744 741 nonodes = ([], [], [])
745 742 if roots is not None:
746 743 roots = list(roots)
747 744 if not roots:
748 745 return nonodes
749 746 lowestrev = min([self.rev(n) for n in roots])
750 747 else:
751 748 roots = [nullid] # Everybody's a descendant of nullid
752 749 lowestrev = nullrev
753 750 if (lowestrev == nullrev) and (heads is None):
754 751 # We want _all_ the nodes!
755 752 return ([self.node(r) for r in self], [nullid], list(self.heads()))
756 753 if heads is None:
757 754 # All nodes are ancestors, so the latest ancestor is the last
758 755 # node.
759 756 highestrev = len(self) - 1
760 757 # Set ancestors to None to signal that every node is an ancestor.
761 758 ancestors = None
762 759 # Set heads to an empty dictionary for later discovery of heads
763 760 heads = {}
764 761 else:
765 762 heads = list(heads)
766 763 if not heads:
767 764 return nonodes
768 765 ancestors = set()
769 766 # Turn heads into a dictionary so we can remove 'fake' heads.
770 767 # Also, later we will be using it to filter out the heads we can't
771 768 # find from roots.
772 769 heads = dict.fromkeys(heads, False)
773 770 # Start at the top and keep marking parents until we're done.
774 771 nodestotag = set(heads)
775 772 # Remember where the top was so we can use it as a limit later.
776 773 highestrev = max([self.rev(n) for n in nodestotag])
777 774 while nodestotag:
778 775 # grab a node to tag
779 776 n = nodestotag.pop()
780 777 # Never tag nullid
781 778 if n == nullid:
782 779 continue
783 780 # A node's revision number represents its place in a
784 781 # topologically sorted list of nodes.
785 782 r = self.rev(n)
786 783 if r >= lowestrev:
787 784 if n not in ancestors:
788 785 # If we are possibly a descendant of one of the roots
789 786 # and we haven't already been marked as an ancestor
790 787 ancestors.add(n) # Mark as ancestor
791 788 # Add non-nullid parents to list of nodes to tag.
792 789 nodestotag.update([p for p in self.parents(n) if
793 790 p != nullid])
794 791 elif n in heads: # We've seen it before, is it a fake head?
795 792 # So it is, real heads should not be the ancestors of
796 793 # any other heads.
797 794 heads.pop(n)
798 795 if not ancestors:
799 796 return nonodes
800 797 # Now that we have our set of ancestors, we want to remove any
801 798 # roots that are not ancestors.
802 799
803 800 # If one of the roots was nullid, everything is included anyway.
804 801 if lowestrev > nullrev:
805 802 # But, since we weren't, let's recompute the lowest rev to not
806 803 # include roots that aren't ancestors.
807 804
808 805 # Filter out roots that aren't ancestors of heads
809 806 roots = [root for root in roots if root in ancestors]
810 807 # Recompute the lowest revision
811 808 if roots:
812 809 lowestrev = min([self.rev(root) for root in roots])
813 810 else:
814 811 # No more roots? Return empty list
815 812 return nonodes
816 813 else:
817 814 # We are descending from nullid, and don't need to care about
818 815 # any other roots.
819 816 lowestrev = nullrev
820 817 roots = [nullid]
821 818 # Transform our roots list into a set.
822 819 descendants = set(roots)
823 820 # Also, keep the original roots so we can filter out roots that aren't
824 821 # 'real' roots (i.e. are descended from other roots).
825 822 roots = descendants.copy()
826 823 # Our topologically sorted list of output nodes.
827 824 orderedout = []
828 825 # Don't start at nullid since we don't want nullid in our output list,
829 826 # and if nullid shows up in descendants, empty parents will look like
830 827 # they're descendants.
831 828 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
832 829 n = self.node(r)
833 830 isdescendant = False
834 831 if lowestrev == nullrev: # Everybody is a descendant of nullid
835 832 isdescendant = True
836 833 elif n in descendants:
837 834 # n is already a descendant
838 835 isdescendant = True
839 836 # This check only needs to be done here because all the roots
840 837 # will start being marked is descendants before the loop.
841 838 if n in roots:
842 839 # If n was a root, check if it's a 'real' root.
843 840 p = tuple(self.parents(n))
844 841 # If any of its parents are descendants, it's not a root.
845 842 if (p[0] in descendants) or (p[1] in descendants):
846 843 roots.remove(n)
847 844 else:
848 845 p = tuple(self.parents(n))
849 846 # A node is a descendant if either of its parents are
850 847 # descendants. (We seeded the dependents list with the roots
851 848 # up there, remember?)
852 849 if (p[0] in descendants) or (p[1] in descendants):
853 850 descendants.add(n)
854 851 isdescendant = True
855 852 if isdescendant and ((ancestors is None) or (n in ancestors)):
856 853 # Only include nodes that are both descendants and ancestors.
857 854 orderedout.append(n)
858 855 if (ancestors is not None) and (n in heads):
859 856 # We're trying to figure out which heads are reachable
860 857 # from roots.
861 858 # Mark this head as having been reached
862 859 heads[n] = True
863 860 elif ancestors is None:
864 861 # Otherwise, we're trying to discover the heads.
865 862 # Assume this is a head because if it isn't, the next step
866 863 # will eventually remove it.
867 864 heads[n] = True
868 865 # But, obviously its parents aren't.
869 866 for p in self.parents(n):
870 867 heads.pop(p, None)
871 868 heads = [head for head, flag in heads.iteritems() if flag]
872 869 roots = list(roots)
873 870 assert orderedout
874 871 assert roots
875 872 assert heads
876 873 return (orderedout, roots, heads)
877 874
878 875 def headrevs(self):
879 876 try:
880 877 return self.index.headrevs()
881 878 except AttributeError:
882 879 return self._headrevs()
883 880
884 881 def computephases(self, roots):
885 882 return self.index.computephasesmapsets(roots)
886 883
887 884 def _headrevs(self):
888 885 count = len(self)
889 886 if not count:
890 887 return [nullrev]
891 888 # we won't iter over filtered rev so nobody is a head at start
892 889 ishead = [0] * (count + 1)
893 890 index = self.index
894 891 for r in self:
895 892 ishead[r] = 1 # I may be an head
896 893 e = index[r]
897 894 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
898 895 return [r for r, val in enumerate(ishead) if val]
899 896
900 897 def heads(self, start=None, stop=None):
901 898 """return the list of all nodes that have no children
902 899
903 900 if start is specified, only heads that are descendants of
904 901 start will be returned
905 902 if stop is specified, it will consider all the revs from stop
906 903 as if they had no children
907 904 """
908 905 if start is None and stop is None:
909 906 if not len(self):
910 907 return [nullid]
911 908 return [self.node(r) for r in self.headrevs()]
912 909
913 910 if start is None:
914 911 start = nullid
915 912 if stop is None:
916 913 stop = []
917 914 stoprevs = set([self.rev(n) for n in stop])
918 915 startrev = self.rev(start)
919 916 reachable = set((startrev,))
920 917 heads = set((startrev,))
921 918
922 919 parentrevs = self.parentrevs
923 920 for r in self.revs(start=startrev + 1):
924 921 for p in parentrevs(r):
925 922 if p in reachable:
926 923 if r not in stoprevs:
927 924 reachable.add(r)
928 925 heads.add(r)
929 926 if p in heads and p not in stoprevs:
930 927 heads.remove(p)
931 928
932 929 return [self.node(r) for r in heads]
933 930
934 931 def children(self, node):
935 932 """find the children of a given node"""
936 933 c = []
937 934 p = self.rev(node)
938 935 for r in self.revs(start=p + 1):
939 936 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
940 937 if prevs:
941 938 for pr in prevs:
942 939 if pr == p:
943 940 c.append(self.node(r))
944 941 elif p == nullrev:
945 942 c.append(self.node(r))
946 943 return c
947 944
948 945 def descendant(self, start, end):
949 946 if start == nullrev:
950 947 return True
951 948 for i in self.descendants([start]):
952 949 if i == end:
953 950 return True
954 951 elif i > end:
955 952 break
956 953 return False
957 954
958 955 def commonancestorsheads(self, a, b):
959 956 """calculate all the heads of the common ancestors of nodes a and b"""
960 957 a, b = self.rev(a), self.rev(b)
961 958 try:
962 959 ancs = self.index.commonancestorsheads(a, b)
963 960 except (AttributeError, OverflowError): # C implementation failed
964 961 ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
965 962 return pycompat.maplist(self.node, ancs)
966 963
967 964 def isancestor(self, a, b):
968 965 """return True if node a is an ancestor of node b
969 966
970 967 The implementation of this is trivial but the use of
971 968 commonancestorsheads is not."""
972 969 return a in self.commonancestorsheads(a, b)
973 970
974 971 def ancestor(self, a, b):
975 972 """calculate the "best" common ancestor of nodes a and b"""
976 973
977 974 a, b = self.rev(a), self.rev(b)
978 975 try:
979 976 ancs = self.index.ancestors(a, b)
980 977 except (AttributeError, OverflowError):
981 978 ancs = ancestor.ancestors(self.parentrevs, a, b)
982 979 if ancs:
983 980 # choose a consistent winner when there's a tie
984 981 return min(map(self.node, ancs))
985 982 return nullid
986 983
987 984 def _match(self, id):
988 985 if isinstance(id, int):
989 986 # rev
990 987 return self.node(id)
991 988 if len(id) == 20:
992 989 # possibly a binary node
993 990 # odds of a binary node being all hex in ASCII are 1 in 10**25
994 991 try:
995 992 node = id
996 993 self.rev(node) # quick search the index
997 994 return node
998 995 except LookupError:
999 996 pass # may be partial hex id
1000 997 try:
1001 998 # str(rev)
1002 999 rev = int(id)
1003 1000 if str(rev) != id:
1004 1001 raise ValueError
1005 1002 if rev < 0:
1006 1003 rev = len(self) + rev
1007 1004 if rev < 0 or rev >= len(self):
1008 1005 raise ValueError
1009 1006 return self.node(rev)
1010 1007 except (ValueError, OverflowError):
1011 1008 pass
1012 1009 if len(id) == 40:
1013 1010 try:
1014 1011 # a full hex nodeid?
1015 1012 node = bin(id)
1016 1013 self.rev(node)
1017 1014 return node
1018 1015 except (TypeError, LookupError):
1019 1016 pass
1020 1017
1021 1018 def _partialmatch(self, id):
1022 1019 try:
1023 1020 partial = self.index.partialmatch(id)
1024 1021 if partial and self.hasnode(partial):
1025 1022 return partial
1026 1023 return None
1027 1024 except RevlogError:
1028 1025 # parsers.c radix tree lookup gave multiple matches
1029 1026 # fast path: for unfiltered changelog, radix tree is accurate
1030 1027 if not getattr(self, 'filteredrevs', None):
1031 1028 raise LookupError(id, self.indexfile,
1032 1029 _('ambiguous identifier'))
1033 1030 # fall through to slow path that filters hidden revisions
1034 1031 except (AttributeError, ValueError):
1035 1032 # we are pure python, or key was too short to search radix tree
1036 1033 pass
1037 1034
1038 1035 if id in self._pcache:
1039 1036 return self._pcache[id]
1040 1037
1041 1038 if len(id) < 40:
1042 1039 try:
1043 1040 # hex(node)[:...]
1044 1041 l = len(id) // 2 # grab an even number of digits
1045 1042 prefix = bin(id[:l * 2])
1046 1043 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1047 1044 nl = [n for n in nl if hex(n).startswith(id) and
1048 1045 self.hasnode(n)]
1049 1046 if len(nl) > 0:
1050 1047 if len(nl) == 1:
1051 1048 self._pcache[id] = nl[0]
1052 1049 return nl[0]
1053 1050 raise LookupError(id, self.indexfile,
1054 1051 _('ambiguous identifier'))
1055 1052 return None
1056 1053 except TypeError:
1057 1054 pass
1058 1055
1059 1056 def lookup(self, id):
1060 1057 """locate a node based on:
1061 1058 - revision number or str(revision number)
1062 1059 - nodeid or subset of hex nodeid
1063 1060 """
1064 1061 n = self._match(id)
1065 1062 if n is not None:
1066 1063 return n
1067 1064 n = self._partialmatch(id)
1068 1065 if n:
1069 1066 return n
1070 1067
1071 1068 raise LookupError(id, self.indexfile, _('no match found'))
1072 1069
1073 1070 def cmp(self, node, text):
1074 1071 """compare text with a given file revision
1075 1072
1076 1073 returns True if text is different than what is stored.
1077 1074 """
1078 1075 p1, p2 = self.parents(node)
1079 1076 return hash(text, p1, p2) != node
1080 1077
1081 1078 def _addchunk(self, offset, data):
1082 1079 """Add a segment to the revlog cache.
1083 1080
1084 1081 Accepts an absolute offset and the data that is at that location.
1085 1082 """
1086 1083 o, d = self._chunkcache
1087 1084 # try to add to existing cache
1088 1085 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1089 1086 self._chunkcache = o, d + data
1090 1087 else:
1091 1088 self._chunkcache = offset, data
1092 1089
1093 1090 def _loadchunk(self, offset, length, df=None):
1094 1091 """Load a segment of raw data from the revlog.
1095 1092
1096 1093 Accepts an absolute offset, length to read, and an optional existing
1097 1094 file handle to read from.
1098 1095
1099 1096 If an existing file handle is passed, it will be seeked and the
1100 1097 original seek position will NOT be restored.
1101 1098
1102 1099 Returns a str or buffer of raw byte data.
1103 1100 """
1104 1101 if df is not None:
1105 1102 closehandle = False
1106 1103 else:
1107 1104 if self._inline:
1108 1105 df = self.opener(self.indexfile)
1109 1106 else:
1110 1107 df = self.opener(self.datafile)
1111 1108 closehandle = True
1112 1109
1113 1110 # Cache data both forward and backward around the requested
1114 1111 # data, in a fixed size window. This helps speed up operations
1115 1112 # involving reading the revlog backwards.
1116 1113 cachesize = self._chunkcachesize
1117 1114 realoffset = offset & ~(cachesize - 1)
1118 1115 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1119 1116 - realoffset)
1120 1117 df.seek(realoffset)
1121 1118 d = df.read(reallength)
1122 1119 if closehandle:
1123 1120 df.close()
1124 1121 self._addchunk(realoffset, d)
1125 1122 if offset != realoffset or reallength != length:
1126 1123 return util.buffer(d, offset - realoffset, length)
1127 1124 return d
1128 1125
1129 1126 def _getchunk(self, offset, length, df=None):
1130 1127 """Obtain a segment of raw data from the revlog.
1131 1128
1132 1129 Accepts an absolute offset, length of bytes to obtain, and an
1133 1130 optional file handle to the already-opened revlog. If the file
1134 1131 handle is used, it's original seek position will not be preserved.
1135 1132
1136 1133 Requests for data may be returned from a cache.
1137 1134
1138 1135 Returns a str or a buffer instance of raw byte data.
1139 1136 """
1140 1137 o, d = self._chunkcache
1141 1138 l = len(d)
1142 1139
1143 1140 # is it in the cache?
1144 1141 cachestart = offset - o
1145 1142 cacheend = cachestart + length
1146 1143 if cachestart >= 0 and cacheend <= l:
1147 1144 if cachestart == 0 and cacheend == l:
1148 1145 return d # avoid a copy
1149 1146 return util.buffer(d, cachestart, cacheend - cachestart)
1150 1147
1151 1148 return self._loadchunk(offset, length, df=df)
1152 1149
1153 1150 def _chunkraw(self, startrev, endrev, df=None):
1154 1151 """Obtain a segment of raw data corresponding to a range of revisions.
1155 1152
1156 1153 Accepts the start and end revisions and an optional already-open
1157 1154 file handle to be used for reading. If the file handle is read, its
1158 1155 seek position will not be preserved.
1159 1156
1160 1157 Requests for data may be satisfied by a cache.
1161 1158
1162 1159 Returns a 2-tuple of (offset, data) for the requested range of
1163 1160 revisions. Offset is the integer offset from the beginning of the
1164 1161 revlog and data is a str or buffer of the raw byte data.
1165 1162
1166 1163 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1167 1164 to determine where each revision's data begins and ends.
1168 1165 """
1169 1166 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1170 1167 # (functions are expensive).
1171 1168 index = self.index
1172 1169 istart = index[startrev]
1173 1170 start = int(istart[0] >> 16)
1174 1171 if startrev == endrev:
1175 1172 end = start + istart[1]
1176 1173 else:
1177 1174 iend = index[endrev]
1178 1175 end = int(iend[0] >> 16) + iend[1]
1179 1176
1180 1177 if self._inline:
1181 1178 start += (startrev + 1) * self._io.size
1182 1179 end += (endrev + 1) * self._io.size
1183 1180 length = end - start
1184 1181
1185 1182 return start, self._getchunk(start, length, df=df)
1186 1183
1187 1184 def _chunk(self, rev, df=None):
1188 1185 """Obtain a single decompressed chunk for a revision.
1189 1186
1190 1187 Accepts an integer revision and an optional already-open file handle
1191 1188 to be used for reading. If used, the seek position of the file will not
1192 1189 be preserved.
1193 1190
1194 1191 Returns a str holding uncompressed data for the requested revision.
1195 1192 """
1196 1193 return self.decompress(self._chunkraw(rev, rev, df=df)[1])
1197 1194
1198 1195 def _chunks(self, revs, df=None):
1199 1196 """Obtain decompressed chunks for the specified revisions.
1200 1197
1201 1198 Accepts an iterable of numeric revisions that are assumed to be in
1202 1199 ascending order. Also accepts an optional already-open file handle
1203 1200 to be used for reading. If used, the seek position of the file will
1204 1201 not be preserved.
1205 1202
1206 1203 This function is similar to calling ``self._chunk()`` multiple times,
1207 1204 but is faster.
1208 1205
1209 1206 Returns a list with decompressed data for each requested revision.
1210 1207 """
1211 1208 if not revs:
1212 1209 return []
1213 1210 start = self.start
1214 1211 length = self.length
1215 1212 inline = self._inline
1216 1213 iosize = self._io.size
1217 1214 buffer = util.buffer
1218 1215
1219 1216 l = []
1220 1217 ladd = l.append
1221 1218
1222 1219 try:
1223 1220 offset, data = self._chunkraw(revs[0], revs[-1], df=df)
1224 1221 except OverflowError:
1225 1222 # issue4215 - we can't cache a run of chunks greater than
1226 1223 # 2G on Windows
1227 1224 return [self._chunk(rev, df=df) for rev in revs]
1228 1225
1229 1226 decomp = self.decompress
1230 1227 for rev in revs:
1231 1228 chunkstart = start(rev)
1232 1229 if inline:
1233 1230 chunkstart += (rev + 1) * iosize
1234 1231 chunklength = length(rev)
1235 1232 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1236 1233
1237 1234 return l
1238 1235
1239 1236 def _chunkclear(self):
1240 1237 """Clear the raw chunk cache."""
1241 1238 self._chunkcache = (0, '')
1242 1239
1243 1240 def deltaparent(self, rev):
1244 1241 """return deltaparent of the given revision"""
1245 1242 base = self.index[rev][3]
1246 1243 if base == rev:
1247 1244 return nullrev
1248 1245 elif self._generaldelta:
1249 1246 return base
1250 1247 else:
1251 1248 return rev - 1
1252 1249
1253 1250 def revdiff(self, rev1, rev2):
1254 1251 """return or calculate a delta between two revisions
1255 1252
1256 1253 The delta calculated is in binary form and is intended to be written to
1257 1254 revlog data directly. So this function needs raw revision data.
1258 1255 """
1259 1256 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1260 1257 return bytes(self._chunk(rev2))
1261 1258
1262 1259 return mdiff.textdiff(self.revision(rev1, raw=True),
1263 1260 self.revision(rev2, raw=True))
1264 1261
1265 1262 def revision(self, nodeorrev, _df=None, raw=False):
1266 1263 """return an uncompressed revision of a given node or revision
1267 1264 number.
1268 1265
1269 1266 _df - an existing file handle to read from. (internal-only)
1270 1267 raw - an optional argument specifying if the revision data is to be
1271 1268 treated as raw data when applying flag transforms. 'raw' should be set
1272 1269 to True when generating changegroups or in debug commands.
1273 1270 """
1274 1271 if isinstance(nodeorrev, int):
1275 1272 rev = nodeorrev
1276 1273 node = self.node(rev)
1277 1274 else:
1278 1275 node = nodeorrev
1279 1276 rev = None
1280 1277
1281 1278 cachedrev = None
1282 1279 flags = None
1283 1280 rawtext = None
1284 1281 if node == nullid:
1285 1282 return ""
1286 1283 if self._cache:
1287 1284 if self._cache[0] == node:
1288 1285 # _cache only stores rawtext
1289 1286 if raw:
1290 1287 return self._cache[2]
1291 1288 # duplicated, but good for perf
1292 1289 if rev is None:
1293 1290 rev = self.rev(node)
1294 1291 if flags is None:
1295 1292 flags = self.flags(rev)
1296 1293 # no extra flags set, no flag processor runs, text = rawtext
1297 1294 if flags == REVIDX_DEFAULT_FLAGS:
1298 1295 return self._cache[2]
1299 1296 # rawtext is reusable. need to run flag processor
1300 1297 rawtext = self._cache[2]
1301 1298
1302 1299 cachedrev = self._cache[1]
1303 1300
1304 1301 # look up what we need to read
1305 1302 if rawtext is None:
1306 1303 if rev is None:
1307 1304 rev = self.rev(node)
1308 1305
1309 1306 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1310 1307 if stopped:
1311 1308 rawtext = self._cache[2]
1312 1309
1313 1310 # drop cache to save memory
1314 1311 self._cache = None
1315 1312
1316 1313 bins = self._chunks(chain, df=_df)
1317 1314 if rawtext is None:
1318 1315 rawtext = bytes(bins[0])
1319 1316 bins = bins[1:]
1320 1317
1321 1318 rawtext = mdiff.patches(rawtext, bins)
1322 1319 self._cache = (node, rev, rawtext)
1323 1320
1324 1321 if flags is None:
1325 1322 if rev is None:
1326 1323 rev = self.rev(node)
1327 1324 flags = self.flags(rev)
1328 1325
1329 1326 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
1330 1327 if validatehash:
1331 1328 self.checkhash(text, node, rev=rev)
1332 1329
1333 1330 return text
1334 1331
1335 1332 def hash(self, text, p1, p2):
1336 1333 """Compute a node hash.
1337 1334
1338 1335 Available as a function so that subclasses can replace the hash
1339 1336 as needed.
1340 1337 """
1341 1338 return hash(text, p1, p2)
1342 1339
1343 1340 def _processflags(self, text, flags, operation, raw=False):
1344 1341 """Inspect revision data flags and applies transforms defined by
1345 1342 registered flag processors.
1346 1343
1347 1344 ``text`` - the revision data to process
1348 1345 ``flags`` - the revision flags
1349 1346 ``operation`` - the operation being performed (read or write)
1350 1347 ``raw`` - an optional argument describing if the raw transform should be
1351 1348 applied.
1352 1349
1353 1350 This method processes the flags in the order (or reverse order if
1354 1351 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
1355 1352 flag processors registered for present flags. The order of flags defined
1356 1353 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
1357 1354
1358 1355 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
1359 1356 processed text and ``validatehash`` is a bool indicating whether the
1360 1357 returned text should be checked for hash integrity.
1361 1358
1362 1359 Note: If the ``raw`` argument is set, it has precedence over the
1363 1360 operation and will only update the value of ``validatehash``.
1364 1361 """
1365 1362 if not operation in ('read', 'write'):
1366 1363 raise ProgrammingError(_("invalid '%s' operation ") % (operation))
1367 1364 # Check all flags are known.
1368 1365 if flags & ~REVIDX_KNOWN_FLAGS:
1369 1366 raise RevlogError(_("incompatible revision flag '%#x'") %
1370 1367 (flags & ~REVIDX_KNOWN_FLAGS))
1371 1368 validatehash = True
1372 1369 # Depending on the operation (read or write), the order might be
1373 1370 # reversed due to non-commutative transforms.
1374 1371 orderedflags = REVIDX_FLAGS_ORDER
1375 1372 if operation == 'write':
1376 1373 orderedflags = reversed(orderedflags)
1377 1374
1378 1375 for flag in orderedflags:
1379 1376 # If a flagprocessor has been registered for a known flag, apply the
1380 1377 # related operation transform and update result tuple.
1381 1378 if flag & flags:
1382 1379 vhash = True
1383 1380
1384 1381 if flag not in _flagprocessors:
1385 1382 message = _("missing processor for flag '%#x'") % (flag)
1386 1383 raise RevlogError(message)
1387 1384
1388 1385 processor = _flagprocessors[flag]
1389 1386 if processor is not None:
1390 1387 readtransform, writetransform, rawtransform = processor
1391 1388
1392 1389 if raw:
1393 1390 vhash = rawtransform(self, text)
1394 1391 elif operation == 'read':
1395 1392 text, vhash = readtransform(self, text)
1396 1393 else: # write operation
1397 1394 text, vhash = writetransform(self, text)
1398 1395 validatehash = validatehash and vhash
1399 1396
1400 1397 return text, validatehash
1401 1398
1402 1399 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1403 1400 """Check node hash integrity.
1404 1401
1405 1402 Available as a function so that subclasses can extend hash mismatch
1406 1403 behaviors as needed.
1407 1404 """
1408 1405 if p1 is None and p2 is None:
1409 1406 p1, p2 = self.parents(node)
1410 1407 if node != self.hash(text, p1, p2):
1411 1408 revornode = rev
1412 1409 if revornode is None:
1413 1410 revornode = templatefilters.short(hex(node))
1414 1411 raise RevlogError(_("integrity check failed on %s:%s")
1415 1412 % (self.indexfile, revornode))
1416 1413
1417 1414 def checkinlinesize(self, tr, fp=None):
1418 1415 """Check if the revlog is too big for inline and convert if so.
1419 1416
1420 1417 This should be called after revisions are added to the revlog. If the
1421 1418 revlog has grown too large to be an inline revlog, it will convert it
1422 1419 to use multiple index and data files.
1423 1420 """
1424 1421 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1425 1422 return
1426 1423
1427 1424 trinfo = tr.find(self.indexfile)
1428 1425 if trinfo is None:
1429 1426 raise RevlogError(_("%s not found in the transaction")
1430 1427 % self.indexfile)
1431 1428
1432 1429 trindex = trinfo[2]
1433 1430 if trindex is not None:
1434 1431 dataoff = self.start(trindex)
1435 1432 else:
1436 1433 # revlog was stripped at start of transaction, use all leftover data
1437 1434 trindex = len(self) - 1
1438 1435 dataoff = self.end(-2)
1439 1436
1440 1437 tr.add(self.datafile, dataoff)
1441 1438
1442 1439 if fp:
1443 1440 fp.flush()
1444 1441 fp.close()
1445 1442
1446 1443 df = self.opener(self.datafile, 'w')
1447 1444 try:
1448 1445 for r in self:
1449 1446 df.write(self._chunkraw(r, r)[1])
1450 1447 finally:
1451 1448 df.close()
1452 1449
1453 1450 fp = self.opener(self.indexfile, 'w', atomictemp=True,
1454 1451 checkambig=self._checkambig)
1455 1452 self.version &= ~(REVLOGNGINLINEDATA)
1456 1453 self._inline = False
1457 1454 for i in self:
1458 1455 e = self._io.packentry(self.index[i], self.node, self.version, i)
1459 1456 fp.write(e)
1460 1457
1461 1458 # if we don't call close, the temp file will never replace the
1462 1459 # real index
1463 1460 fp.close()
1464 1461
1465 1462 tr.replace(self.indexfile, trindex * self._io.size)
1466 1463 self._chunkclear()
1467 1464
1468 1465 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1469 1466 node=None, flags=REVIDX_DEFAULT_FLAGS):
1470 1467 """add a revision to the log
1471 1468
1472 1469 text - the revision data to add
1473 1470 transaction - the transaction object used for rollback
1474 1471 link - the linkrev data to add
1475 1472 p1, p2 - the parent nodeids of the revision
1476 1473 cachedelta - an optional precomputed delta
1477 1474 node - nodeid of revision; typically node is not specified, and it is
1478 1475 computed by default as hash(text, p1, p2), however subclasses might
1479 1476 use different hashing method (and override checkhash() in such case)
1480 1477 flags - the known flags to set on the revision
1481 1478 """
1482 1479 if link == nullrev:
1483 1480 raise RevlogError(_("attempted to add linkrev -1 to %s")
1484 1481 % self.indexfile)
1485 1482
1486 1483 if flags:
1487 1484 node = node or self.hash(text, p1, p2)
1488 1485
1489 1486 rawtext, validatehash = self._processflags(text, flags, 'write')
1490 1487
1491 1488 # If the flag processor modifies the revision data, ignore any provided
1492 1489 # cachedelta.
1493 1490 if rawtext != text:
1494 1491 cachedelta = None
1495 1492
1496 1493 if len(rawtext) > _maxentrysize:
1497 1494 raise RevlogError(
1498 1495 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1499 1496 % (self.indexfile, len(rawtext)))
1500 1497
1501 1498 node = node or self.hash(rawtext, p1, p2)
1502 1499 if node in self.nodemap:
1503 1500 return node
1504 1501
1505 1502 if validatehash:
1506 1503 self.checkhash(rawtext, node, p1=p1, p2=p2)
1507 1504
1508 1505 dfh = None
1509 1506 if not self._inline:
1510 1507 dfh = self.opener(self.datafile, "a+")
1511 1508 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1512 1509 try:
1513 1510 return self._addrevision(node, rawtext, transaction, link, p1, p2,
1514 1511 flags, cachedelta, ifh, dfh)
1515 1512 finally:
1516 1513 if dfh:
1517 1514 dfh.close()
1518 1515 ifh.close()
1519 1516
1520 1517 def compress(self, data):
1521 1518 """Generate a possibly-compressed representation of data."""
1522 1519 if not data:
1523 1520 return '', data
1524 1521
1525 1522 compressed = self._compressor.compress(data)
1526 1523
1527 1524 if compressed:
1528 1525 # The revlog compressor added the header in the returned data.
1529 1526 return '', compressed
1530 1527
1531 1528 if data[0:1] == '\0':
1532 1529 return '', data
1533 1530 return 'u', data
1534 1531
1535 1532 def decompress(self, data):
1536 1533 """Decompress a revlog chunk.
1537 1534
1538 1535 The chunk is expected to begin with a header identifying the
1539 1536 format type so it can be routed to an appropriate decompressor.
1540 1537 """
1541 1538 if not data:
1542 1539 return data
1543 1540
1544 1541 # Revlogs are read much more frequently than they are written and many
1545 1542 # chunks only take microseconds to decompress, so performance is
1546 1543 # important here.
1547 1544 #
1548 1545 # We can make a few assumptions about revlogs:
1549 1546 #
1550 1547 # 1) the majority of chunks will be compressed (as opposed to inline
1551 1548 # raw data).
1552 1549 # 2) decompressing *any* data will likely by at least 10x slower than
1553 1550 # returning raw inline data.
1554 1551 # 3) we want to prioritize common and officially supported compression
1555 1552 # engines
1556 1553 #
1557 1554 # It follows that we want to optimize for "decompress compressed data
1558 1555 # when encoded with common and officially supported compression engines"
1559 1556 # case over "raw data" and "data encoded by less common or non-official
1560 1557 # compression engines." That is why we have the inline lookup first
1561 1558 # followed by the compengines lookup.
1562 1559 #
1563 1560 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
1564 1561 # compressed chunks. And this matters for changelog and manifest reads.
1565 1562 t = data[0:1]
1566 1563
1567 1564 if t == 'x':
1568 1565 try:
1569 1566 return _zlibdecompress(data)
1570 1567 except zlib.error as e:
1571 1568 raise RevlogError(_('revlog decompress error: %s') % str(e))
1572 1569 # '\0' is more common than 'u' so it goes first.
1573 1570 elif t == '\0':
1574 1571 return data
1575 1572 elif t == 'u':
1576 1573 return util.buffer(data, 1)
1577 1574
1578 1575 try:
1579 1576 compressor = self._decompressors[t]
1580 1577 except KeyError:
1581 1578 try:
1582 1579 engine = util.compengines.forrevlogheader(t)
1583 1580 compressor = engine.revlogcompressor()
1584 1581 self._decompressors[t] = compressor
1585 1582 except KeyError:
1586 1583 raise RevlogError(_('unknown compression type %r') % t)
1587 1584
1588 1585 return compressor.decompress(data)
1589 1586
1590 1587 def _isgooddelta(self, d, textlen):
1591 1588 """Returns True if the given delta is good. Good means that it is within
1592 1589 the disk span, disk size, and chain length bounds that we know to be
1593 1590 performant."""
1594 1591 if d is None:
1595 1592 return False
1596 1593
1597 1594 # - 'dist' is the distance from the base revision -- bounding it limits
1598 1595 # the amount of I/O we need to do.
1599 1596 # - 'compresseddeltalen' is the sum of the total size of deltas we need
1600 1597 # to apply -- bounding it limits the amount of CPU we consume.
1601 1598 dist, l, data, base, chainbase, chainlen, compresseddeltalen = d
1602
1603 defaultmax = textlen * 4
1604 maxdist = self._maxdeltachainspan
1605 if not maxdist:
1606 maxdist = dist # ensure the conditional pass
1607 maxdist = max(maxdist, defaultmax)
1608 if (dist > maxdist or l > textlen or
1599 if (dist > textlen * 4 or l > textlen or
1609 1600 compresseddeltalen > textlen * 2 or
1610 1601 (self._maxchainlen and chainlen > self._maxchainlen)):
1611 1602 return False
1612 1603
1613 1604 return True
1614 1605
1615 1606 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
1616 1607 cachedelta, ifh, dfh, alwayscache=False):
1617 1608 """internal function to add revisions to the log
1618 1609
1619 1610 see addrevision for argument descriptions.
1620 1611
1621 1612 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
1622 1613
1623 1614 invariants:
1624 1615 - rawtext is optional (can be None); if not set, cachedelta must be set.
1625 1616 if both are set, they must correspond to each other.
1626 1617 """
1627 1618 btext = [rawtext]
1628 1619 def buildtext():
1629 1620 if btext[0] is not None:
1630 1621 return btext[0]
1631 1622 baserev = cachedelta[0]
1632 1623 delta = cachedelta[1]
1633 1624 # special case deltas which replace entire base; no need to decode
1634 1625 # base revision. this neatly avoids censored bases, which throw when
1635 1626 # they're decoded.
1636 1627 hlen = struct.calcsize(">lll")
1637 1628 if delta[:hlen] == mdiff.replacediffheader(self.rawsize(baserev),
1638 1629 len(delta) - hlen):
1639 1630 btext[0] = delta[hlen:]
1640 1631 else:
1641 1632 if self._inline:
1642 1633 fh = ifh
1643 1634 else:
1644 1635 fh = dfh
1645 1636 basetext = self.revision(baserev, _df=fh, raw=True)
1646 1637 btext[0] = mdiff.patch(basetext, delta)
1647 1638
1648 1639 try:
1649 1640 res = self._processflags(btext[0], flags, 'read', raw=True)
1650 1641 btext[0], validatehash = res
1651 1642 if validatehash:
1652 1643 self.checkhash(btext[0], node, p1=p1, p2=p2)
1653 1644 if flags & REVIDX_ISCENSORED:
1654 1645 raise RevlogError(_('node %s is not censored') % node)
1655 1646 except CensoredNodeError:
1656 1647 # must pass the censored index flag to add censored revisions
1657 1648 if not flags & REVIDX_ISCENSORED:
1658 1649 raise
1659 1650 return btext[0]
1660 1651
1661 1652 def builddelta(rev):
1662 1653 # can we use the cached delta?
1663 1654 if cachedelta and cachedelta[0] == rev:
1664 1655 delta = cachedelta[1]
1665 1656 else:
1666 1657 t = buildtext()
1667 1658 if self.iscensored(rev):
1668 1659 # deltas based on a censored revision must replace the
1669 1660 # full content in one patch, so delta works everywhere
1670 1661 header = mdiff.replacediffheader(self.rawsize(rev), len(t))
1671 1662 delta = header + t
1672 1663 else:
1673 1664 if self._inline:
1674 1665 fh = ifh
1675 1666 else:
1676 1667 fh = dfh
1677 1668 ptext = self.revision(rev, _df=fh, raw=True)
1678 1669 delta = mdiff.textdiff(ptext, t)
1679 1670 header, data = self.compress(delta)
1680 1671 deltalen = len(header) + len(data)
1681 1672 chainbase = self.chainbase(rev)
1682 1673 dist = deltalen + offset - self.start(chainbase)
1683 1674 if self._generaldelta:
1684 1675 base = rev
1685 1676 else:
1686 1677 base = chainbase
1687 1678 chainlen, compresseddeltalen = self._chaininfo(rev)
1688 1679 chainlen += 1
1689 1680 compresseddeltalen += deltalen
1690 1681 return (dist, deltalen, (header, data), base,
1691 1682 chainbase, chainlen, compresseddeltalen)
1692 1683
1693 1684 curr = len(self)
1694 1685 prev = curr - 1
1695 1686 offset = self.end(prev)
1696 1687 delta = None
1697 1688 p1r, p2r = self.rev(p1), self.rev(p2)
1698 1689
1699 1690 # full versions are inserted when the needed deltas
1700 1691 # become comparable to the uncompressed text
1701 1692 if rawtext is None:
1702 1693 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1703 1694 cachedelta[1])
1704 1695 else:
1705 1696 textlen = len(rawtext)
1706 1697
1707 1698 # should we try to build a delta?
1708 1699 if prev != nullrev and self.storedeltachains:
1709 1700 tested = set()
1710 1701 # This condition is true most of the time when processing
1711 1702 # changegroup data into a generaldelta repo. The only time it
1712 1703 # isn't true is if this is the first revision in a delta chain
1713 1704 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
1714 1705 if cachedelta and self._generaldelta and self._lazydeltabase:
1715 1706 # Assume what we received from the server is a good choice
1716 1707 # build delta will reuse the cache
1717 1708 candidatedelta = builddelta(cachedelta[0])
1718 1709 tested.add(cachedelta[0])
1719 1710 if self._isgooddelta(candidatedelta, textlen):
1720 1711 delta = candidatedelta
1721 1712 if delta is None and self._generaldelta:
1722 1713 # exclude already lazy tested base if any
1723 1714 parents = [p for p in (p1r, p2r)
1724 1715 if p != nullrev and p not in tested]
1725 1716 if parents and not self._aggressivemergedeltas:
1726 1717 # Pick whichever parent is closer to us (to minimize the
1727 1718 # chance of having to build a fulltext).
1728 1719 parents = [max(parents)]
1729 1720 tested.update(parents)
1730 1721 pdeltas = []
1731 1722 for p in parents:
1732 1723 pd = builddelta(p)
1733 1724 if self._isgooddelta(pd, textlen):
1734 1725 pdeltas.append(pd)
1735 1726 if pdeltas:
1736 1727 delta = min(pdeltas, key=lambda x: x[1])
1737 1728 if delta is None and prev not in tested:
1738 1729 # other approach failed try against prev to hopefully save us a
1739 1730 # fulltext.
1740 1731 candidatedelta = builddelta(prev)
1741 1732 if self._isgooddelta(candidatedelta, textlen):
1742 1733 delta = candidatedelta
1743 1734 if delta is not None:
1744 1735 dist, l, data, base, chainbase, chainlen, compresseddeltalen = delta
1745 1736 else:
1746 1737 rawtext = buildtext()
1747 1738 data = self.compress(rawtext)
1748 1739 l = len(data[1]) + len(data[0])
1749 1740 base = chainbase = curr
1750 1741
1751 1742 e = (offset_type(offset, flags), l, textlen,
1752 1743 base, link, p1r, p2r, node)
1753 1744 self.index.insert(-1, e)
1754 1745 self.nodemap[node] = curr
1755 1746
1756 1747 entry = self._io.packentry(e, self.node, self.version, curr)
1757 1748 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
1758 1749
1759 1750 if alwayscache and rawtext is None:
1760 1751 rawtext = buildtext()
1761 1752
1762 1753 if type(rawtext) == str: # only accept immutable objects
1763 1754 self._cache = (node, curr, rawtext)
1764 1755 self._chainbasecache[curr] = chainbase
1765 1756 return node
1766 1757
1767 1758 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1768 1759 # Files opened in a+ mode have inconsistent behavior on various
1769 1760 # platforms. Windows requires that a file positioning call be made
1770 1761 # when the file handle transitions between reads and writes. See
1771 1762 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
1772 1763 # platforms, Python or the platform itself can be buggy. Some versions
1773 1764 # of Solaris have been observed to not append at the end of the file
1774 1765 # if the file was seeked to before the end. See issue4943 for more.
1775 1766 #
1776 1767 # We work around this issue by inserting a seek() before writing.
1777 1768 # Note: This is likely not necessary on Python 3.
1778 1769 ifh.seek(0, os.SEEK_END)
1779 1770 if dfh:
1780 1771 dfh.seek(0, os.SEEK_END)
1781 1772
1782 1773 curr = len(self) - 1
1783 1774 if not self._inline:
1784 1775 transaction.add(self.datafile, offset)
1785 1776 transaction.add(self.indexfile, curr * len(entry))
1786 1777 if data[0]:
1787 1778 dfh.write(data[0])
1788 1779 dfh.write(data[1])
1789 1780 ifh.write(entry)
1790 1781 else:
1791 1782 offset += curr * self._io.size
1792 1783 transaction.add(self.indexfile, offset, curr)
1793 1784 ifh.write(entry)
1794 1785 ifh.write(data[0])
1795 1786 ifh.write(data[1])
1796 1787 self.checkinlinesize(transaction, ifh)
1797 1788
1798 1789 def addgroup(self, cg, linkmapper, transaction, addrevisioncb=None):
1799 1790 """
1800 1791 add a delta group
1801 1792
1802 1793 given a set of deltas, add them to the revision log. the
1803 1794 first delta is against its parent, which should be in our
1804 1795 log, the rest are against the previous delta.
1805 1796
1806 1797 If ``addrevisioncb`` is defined, it will be called with arguments of
1807 1798 this revlog and the node that was added.
1808 1799 """
1809 1800
1810 1801 # track the base of the current delta log
1811 1802 content = []
1812 1803 node = None
1813 1804
1814 1805 r = len(self)
1815 1806 end = 0
1816 1807 if r:
1817 1808 end = self.end(r - 1)
1818 1809 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1819 1810 isize = r * self._io.size
1820 1811 if self._inline:
1821 1812 transaction.add(self.indexfile, end + isize, r)
1822 1813 dfh = None
1823 1814 else:
1824 1815 transaction.add(self.indexfile, isize, r)
1825 1816 transaction.add(self.datafile, end)
1826 1817 dfh = self.opener(self.datafile, "a+")
1827 1818 def flush():
1828 1819 if dfh:
1829 1820 dfh.flush()
1830 1821 ifh.flush()
1831 1822 try:
1832 1823 # loop through our set of deltas
1833 1824 chain = None
1834 1825 for chunkdata in iter(lambda: cg.deltachunk(chain), {}):
1835 1826 node = chunkdata['node']
1836 1827 p1 = chunkdata['p1']
1837 1828 p2 = chunkdata['p2']
1838 1829 cs = chunkdata['cs']
1839 1830 deltabase = chunkdata['deltabase']
1840 1831 delta = chunkdata['delta']
1841 1832 flags = chunkdata['flags'] or REVIDX_DEFAULT_FLAGS
1842 1833
1843 1834 content.append(node)
1844 1835
1845 1836 link = linkmapper(cs)
1846 1837 if node in self.nodemap:
1847 1838 # this can happen if two branches make the same change
1848 1839 chain = node
1849 1840 continue
1850 1841
1851 1842 for p in (p1, p2):
1852 1843 if p not in self.nodemap:
1853 1844 raise LookupError(p, self.indexfile,
1854 1845 _('unknown parent'))
1855 1846
1856 1847 if deltabase not in self.nodemap:
1857 1848 raise LookupError(deltabase, self.indexfile,
1858 1849 _('unknown delta base'))
1859 1850
1860 1851 baserev = self.rev(deltabase)
1861 1852
1862 1853 if baserev != nullrev and self.iscensored(baserev):
1863 1854 # if base is censored, delta must be full replacement in a
1864 1855 # single patch operation
1865 1856 hlen = struct.calcsize(">lll")
1866 1857 oldlen = self.rawsize(baserev)
1867 1858 newlen = len(delta) - hlen
1868 1859 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
1869 1860 raise error.CensoredBaseError(self.indexfile,
1870 1861 self.node(baserev))
1871 1862
1872 1863 if not flags and self._peek_iscensored(baserev, delta, flush):
1873 1864 flags |= REVIDX_ISCENSORED
1874 1865
1875 1866 # We assume consumers of addrevisioncb will want to retrieve
1876 1867 # the added revision, which will require a call to
1877 1868 # revision(). revision() will fast path if there is a cache
1878 1869 # hit. So, we tell _addrevision() to always cache in this case.
1879 1870 # We're only using addgroup() in the context of changegroup
1880 1871 # generation so the revision data can always be handled as raw
1881 1872 # by the flagprocessor.
1882 1873 chain = self._addrevision(node, None, transaction, link,
1883 1874 p1, p2, flags, (baserev, delta),
1884 1875 ifh, dfh,
1885 1876 alwayscache=bool(addrevisioncb))
1886 1877
1887 1878 if addrevisioncb:
1888 1879 addrevisioncb(self, chain)
1889 1880
1890 1881 if not dfh and not self._inline:
1891 1882 # addrevision switched from inline to conventional
1892 1883 # reopen the index
1893 1884 ifh.close()
1894 1885 dfh = self.opener(self.datafile, "a+")
1895 1886 ifh = self.opener(self.indexfile, "a+",
1896 1887 checkambig=self._checkambig)
1897 1888 finally:
1898 1889 if dfh:
1899 1890 dfh.close()
1900 1891 ifh.close()
1901 1892
1902 1893 return content
1903 1894
1904 1895 def iscensored(self, rev):
1905 1896 """Check if a file revision is censored."""
1906 1897 return False
1907 1898
1908 1899 def _peek_iscensored(self, baserev, delta, flush):
1909 1900 """Quickly check if a delta produces a censored revision."""
1910 1901 return False
1911 1902
1912 1903 def getstrippoint(self, minlink):
1913 1904 """find the minimum rev that must be stripped to strip the linkrev
1914 1905
1915 1906 Returns a tuple containing the minimum rev and a set of all revs that
1916 1907 have linkrevs that will be broken by this strip.
1917 1908 """
1918 1909 brokenrevs = set()
1919 1910 strippoint = len(self)
1920 1911
1921 1912 heads = {}
1922 1913 futurelargelinkrevs = set()
1923 1914 for head in self.headrevs():
1924 1915 headlinkrev = self.linkrev(head)
1925 1916 heads[head] = headlinkrev
1926 1917 if headlinkrev >= minlink:
1927 1918 futurelargelinkrevs.add(headlinkrev)
1928 1919
1929 1920 # This algorithm involves walking down the rev graph, starting at the
1930 1921 # heads. Since the revs are topologically sorted according to linkrev,
1931 1922 # once all head linkrevs are below the minlink, we know there are
1932 1923 # no more revs that could have a linkrev greater than minlink.
1933 1924 # So we can stop walking.
1934 1925 while futurelargelinkrevs:
1935 1926 strippoint -= 1
1936 1927 linkrev = heads.pop(strippoint)
1937 1928
1938 1929 if linkrev < minlink:
1939 1930 brokenrevs.add(strippoint)
1940 1931 else:
1941 1932 futurelargelinkrevs.remove(linkrev)
1942 1933
1943 1934 for p in self.parentrevs(strippoint):
1944 1935 if p != nullrev:
1945 1936 plinkrev = self.linkrev(p)
1946 1937 heads[p] = plinkrev
1947 1938 if plinkrev >= minlink:
1948 1939 futurelargelinkrevs.add(plinkrev)
1949 1940
1950 1941 return strippoint, brokenrevs
1951 1942
1952 1943 def strip(self, minlink, transaction):
1953 1944 """truncate the revlog on the first revision with a linkrev >= minlink
1954 1945
1955 1946 This function is called when we're stripping revision minlink and
1956 1947 its descendants from the repository.
1957 1948
1958 1949 We have to remove all revisions with linkrev >= minlink, because
1959 1950 the equivalent changelog revisions will be renumbered after the
1960 1951 strip.
1961 1952
1962 1953 So we truncate the revlog on the first of these revisions, and
1963 1954 trust that the caller has saved the revisions that shouldn't be
1964 1955 removed and that it'll re-add them after this truncation.
1965 1956 """
1966 1957 if len(self) == 0:
1967 1958 return
1968 1959
1969 1960 rev, _ = self.getstrippoint(minlink)
1970 1961 if rev == len(self):
1971 1962 return
1972 1963
1973 1964 # first truncate the files on disk
1974 1965 end = self.start(rev)
1975 1966 if not self._inline:
1976 1967 transaction.add(self.datafile, end)
1977 1968 end = rev * self._io.size
1978 1969 else:
1979 1970 end += rev * self._io.size
1980 1971
1981 1972 transaction.add(self.indexfile, end)
1982 1973
1983 1974 # then reset internal state in memory to forget those revisions
1984 1975 self._cache = None
1985 1976 self._chaininfocache = {}
1986 1977 self._chunkclear()
1987 1978 for x in xrange(rev, len(self)):
1988 1979 del self.nodemap[self.node(x)]
1989 1980
1990 1981 del self.index[rev:-1]
1991 1982
1992 1983 def checksize(self):
1993 1984 expected = 0
1994 1985 if len(self):
1995 1986 expected = max(0, self.end(len(self) - 1))
1996 1987
1997 1988 try:
1998 1989 f = self.opener(self.datafile)
1999 1990 f.seek(0, 2)
2000 1991 actual = f.tell()
2001 1992 f.close()
2002 1993 dd = actual - expected
2003 1994 except IOError as inst:
2004 1995 if inst.errno != errno.ENOENT:
2005 1996 raise
2006 1997 dd = 0
2007 1998
2008 1999 try:
2009 2000 f = self.opener(self.indexfile)
2010 2001 f.seek(0, 2)
2011 2002 actual = f.tell()
2012 2003 f.close()
2013 2004 s = self._io.size
2014 2005 i = max(0, actual // s)
2015 2006 di = actual - (i * s)
2016 2007 if self._inline:
2017 2008 databytes = 0
2018 2009 for r in self:
2019 2010 databytes += max(0, self.length(r))
2020 2011 dd = 0
2021 2012 di = actual - len(self) * s - databytes
2022 2013 except IOError as inst:
2023 2014 if inst.errno != errno.ENOENT:
2024 2015 raise
2025 2016 di = 0
2026 2017
2027 2018 return (dd, di)
2028 2019
2029 2020 def files(self):
2030 2021 res = [self.indexfile]
2031 2022 if not self._inline:
2032 2023 res.append(self.datafile)
2033 2024 return res
2034 2025
2035 2026 DELTAREUSEALWAYS = 'always'
2036 2027 DELTAREUSESAMEREVS = 'samerevs'
2037 2028 DELTAREUSENEVER = 'never'
2038 2029
2039 2030 DELTAREUSEALL = set(['always', 'samerevs', 'never'])
2040 2031
2041 2032 def clone(self, tr, destrevlog, addrevisioncb=None,
2042 2033 deltareuse=DELTAREUSESAMEREVS, aggressivemergedeltas=None):
2043 2034 """Copy this revlog to another, possibly with format changes.
2044 2035
2045 2036 The destination revlog will contain the same revisions and nodes.
2046 2037 However, it may not be bit-for-bit identical due to e.g. delta encoding
2047 2038 differences.
2048 2039
2049 2040 The ``deltareuse`` argument control how deltas from the existing revlog
2050 2041 are preserved in the destination revlog. The argument can have the
2051 2042 following values:
2052 2043
2053 2044 DELTAREUSEALWAYS
2054 2045 Deltas will always be reused (if possible), even if the destination
2055 2046 revlog would not select the same revisions for the delta. This is the
2056 2047 fastest mode of operation.
2057 2048 DELTAREUSESAMEREVS
2058 2049 Deltas will be reused if the destination revlog would pick the same
2059 2050 revisions for the delta. This mode strikes a balance between speed
2060 2051 and optimization.
2061 2052 DELTAREUSENEVER
2062 2053 Deltas will never be reused. This is the slowest mode of execution.
2063 2054 This mode can be used to recompute deltas (e.g. if the diff/delta
2064 2055 algorithm changes).
2065 2056
2066 2057 Delta computation can be slow, so the choice of delta reuse policy can
2067 2058 significantly affect run time.
2068 2059
2069 2060 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2070 2061 two extremes. Deltas will be reused if they are appropriate. But if the
2071 2062 delta could choose a better revision, it will do so. This means if you
2072 2063 are converting a non-generaldelta revlog to a generaldelta revlog,
2073 2064 deltas will be recomputed if the delta's parent isn't a parent of the
2074 2065 revision.
2075 2066
2076 2067 In addition to the delta policy, the ``aggressivemergedeltas`` argument
2077 2068 controls whether to compute deltas against both parents for merges.
2078 2069 By default, the current default is used.
2079 2070 """
2080 2071 if deltareuse not in self.DELTAREUSEALL:
2081 2072 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2082 2073
2083 2074 if len(destrevlog):
2084 2075 raise ValueError(_('destination revlog is not empty'))
2085 2076
2086 2077 if getattr(self, 'filteredrevs', None):
2087 2078 raise ValueError(_('source revlog has filtered revisions'))
2088 2079 if getattr(destrevlog, 'filteredrevs', None):
2089 2080 raise ValueError(_('destination revlog has filtered revisions'))
2090 2081
2091 2082 # lazydeltabase controls whether to reuse a cached delta, if possible.
2092 2083 oldlazydeltabase = destrevlog._lazydeltabase
2093 2084 oldamd = destrevlog._aggressivemergedeltas
2094 2085
2095 2086 try:
2096 2087 if deltareuse == self.DELTAREUSEALWAYS:
2097 2088 destrevlog._lazydeltabase = True
2098 2089 elif deltareuse == self.DELTAREUSESAMEREVS:
2099 2090 destrevlog._lazydeltabase = False
2100 2091
2101 2092 destrevlog._aggressivemergedeltas = aggressivemergedeltas or oldamd
2102 2093
2103 2094 populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
2104 2095 self.DELTAREUSESAMEREVS)
2105 2096
2106 2097 index = self.index
2107 2098 for rev in self:
2108 2099 entry = index[rev]
2109 2100
2110 2101 # Some classes override linkrev to take filtered revs into
2111 2102 # account. Use raw entry from index.
2112 2103 flags = entry[0] & 0xffff
2113 2104 linkrev = entry[4]
2114 2105 p1 = index[entry[5]][7]
2115 2106 p2 = index[entry[6]][7]
2116 2107 node = entry[7]
2117 2108
2118 2109 # (Possibly) reuse the delta from the revlog if allowed and
2119 2110 # the revlog chunk is a delta.
2120 2111 cachedelta = None
2121 2112 rawtext = None
2122 2113 if populatecachedelta:
2123 2114 dp = self.deltaparent(rev)
2124 2115 if dp != nullrev:
2125 2116 cachedelta = (dp, str(self._chunk(rev)))
2126 2117
2127 2118 if not cachedelta:
2128 2119 rawtext = self.revision(rev, raw=True)
2129 2120
2130 2121 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2131 2122 checkambig=False)
2132 2123 dfh = None
2133 2124 if not destrevlog._inline:
2134 2125 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2135 2126 try:
2136 2127 destrevlog._addrevision(node, rawtext, tr, linkrev, p1, p2,
2137 2128 flags, cachedelta, ifh, dfh)
2138 2129 finally:
2139 2130 if dfh:
2140 2131 dfh.close()
2141 2132 ifh.close()
2142 2133
2143 2134 if addrevisioncb:
2144 2135 addrevisioncb(self, rev, node)
2145 2136 finally:
2146 2137 destrevlog._lazydeltabase = oldlazydeltabase
2147 2138 destrevlog._aggressivemergedeltas = oldamd
@@ -1,349 +1,161
1 1 Check whether size of generaldelta revlog is not bigger than its
2 2 regular equivalent. Test would fail if generaldelta was naive
3 3 implementation of parentdelta: third manifest revision would be fully
4 4 inserted due to big distance from its paren revision (zero).
5 5
6 6 $ hg init repo --config format.generaldelta=no --config format.usegeneraldelta=no
7 7 $ cd repo
8 8 $ echo foo > foo
9 9 $ echo bar > bar
10 10 $ echo baz > baz
11 11 $ hg commit -q -Am boo
12 12 $ hg clone --pull . ../gdrepo -q --config format.generaldelta=yes
13 13 $ for r in 1 2 3; do
14 14 > echo $r > foo
15 15 > hg commit -q -m $r
16 16 > hg up -q -r 0
17 17 > hg pull . -q -r $r -R ../gdrepo
18 18 > done
19 19
20 20 $ cd ..
21 21 >>> from __future__ import print_function
22 22 >>> import os
23 23 >>> regsize = os.stat("repo/.hg/store/00manifest.i").st_size
24 24 >>> gdsize = os.stat("gdrepo/.hg/store/00manifest.i").st_size
25 25 >>> if regsize < gdsize:
26 26 ... print('generaldata increased size of manifest')
27 27
28 28 Verify rev reordering doesnt create invalid bundles (issue4462)
29 29 This requires a commit tree that when pulled will reorder manifest revs such
30 30 that the second manifest to create a file rev will be ordered before the first
31 31 manifest to create that file rev. We also need to do a partial pull to ensure
32 32 reordering happens. At the end we verify the linkrev points at the earliest
33 33 commit.
34 34
35 35 $ hg init server --config format.generaldelta=True
36 36 $ cd server
37 37 $ touch a
38 38 $ hg commit -Aqm a
39 39 $ echo x > x
40 40 $ echo y > y
41 41 $ hg commit -Aqm xy
42 42 $ hg up -q '.^'
43 43 $ echo x > x
44 44 $ echo z > z
45 45 $ hg commit -Aqm xz
46 46 $ hg up -q 1
47 47 $ echo b > b
48 48 $ hg commit -Aqm b
49 49 $ hg merge -q 2
50 50 $ hg commit -Aqm merge
51 51 $ echo c > c
52 52 $ hg commit -Aqm c
53 53 $ hg log -G -T '{rev} {shortest(node)} {desc}'
54 54 @ 5 ebb8 c
55 55 |
56 56 o 4 baf7 merge
57 57 |\
58 58 | o 3 a129 b
59 59 | |
60 60 o | 2 958c xz
61 61 | |
62 62 | o 1 f00c xy
63 63 |/
64 64 o 0 3903 a
65 65
66 66 $ cd ..
67 67 $ hg init client --config format.generaldelta=false --config format.usegeneraldelta=false
68 68 $ cd client
69 69 $ hg pull -q ../server -r 4
70 70 $ hg debugindex x
71 71 rev offset length base linkrev nodeid p1 p2
72 72 0 0 3 0 1 1406e7411862 000000000000 000000000000
73 73
74 74 $ cd ..
75 75
76 76 Test "usegeneraldelta" config
77 77 (repo are general delta, but incoming bundle are not re-deltafied)
78 78
79 79 delta coming from the server base delta server are not recompressed.
80 80 (also include the aggressive version for comparison)
81 81
82 82 $ hg clone repo --pull --config format.usegeneraldelta=1 usegd
83 83 requesting all changes
84 84 adding changesets
85 85 adding manifests
86 86 adding file changes
87 87 added 4 changesets with 6 changes to 3 files (+2 heads)
88 88 updating to branch default
89 89 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
90 90 $ hg clone repo --pull --config format.generaldelta=1 full
91 91 requesting all changes
92 92 adding changesets
93 93 adding manifests
94 94 adding file changes
95 95 added 4 changesets with 6 changes to 3 files (+2 heads)
96 96 updating to branch default
97 97 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
98 98 $ hg -R repo debugindex -m
99 99 rev offset length base linkrev nodeid p1 p2
100 100 0 0 104 0 0 cef96823c800 000000000000 000000000000
101 101 1 104 57 0 1 58ab9a8d541d cef96823c800 000000000000
102 102 2 161 57 0 2 134fdc6fd680 cef96823c800 000000000000
103 103 3 218 104 3 3 723508934dad cef96823c800 000000000000
104 104 $ hg -R usegd debugindex -m
105 105 rev offset length delta linkrev nodeid p1 p2
106 106 0 0 104 -1 0 cef96823c800 000000000000 000000000000
107 107 1 104 57 0 1 58ab9a8d541d cef96823c800 000000000000
108 108 2 161 57 1 2 134fdc6fd680 cef96823c800 000000000000
109 109 3 218 57 0 3 723508934dad cef96823c800 000000000000
110 110 $ hg -R full debugindex -m
111 111 rev offset length delta linkrev nodeid p1 p2
112 112 0 0 104 -1 0 cef96823c800 000000000000 000000000000
113 113 1 104 57 0 1 58ab9a8d541d cef96823c800 000000000000
114 114 2 161 57 0 2 134fdc6fd680 cef96823c800 000000000000
115 115 3 218 57 0 3 723508934dad cef96823c800 000000000000
116 116
117 117 Test format.aggressivemergedeltas
118 118
119 119 $ hg init --config format.generaldelta=1 aggressive
120 120 $ cd aggressive
121 121 $ cat << EOF >> .hg/hgrc
122 122 > [format]
123 123 > generaldelta = 1
124 124 > EOF
125 125 $ touch a b c d e
126 126 $ hg commit -Aqm side1
127 127 $ hg up -q null
128 128 $ touch x y
129 129 $ hg commit -Aqm side2
130 130
131 131 - Verify non-aggressive merge uses p1 (commit 1) as delta parent
132 132 $ hg merge -q 0
133 133 $ hg commit -q -m merge
134 134 $ hg debugindex -m
135 135 rev offset length delta linkrev nodeid p1 p2
136 136 0 0 59 -1 0 8dde941edb6e 000000000000 000000000000
137 137 1 59 61 0 1 315c023f341d 000000000000 000000000000
138 138 2 120 65 1 2 2ab389a983eb 315c023f341d 8dde941edb6e
139 139
140 140 $ hg strip -q -r . --config extensions.strip=
141 141
142 142 - Verify aggressive merge uses p2 (commit 0) as delta parent
143 143 $ hg up -q -C 1
144 144 $ hg merge -q 0
145 145 $ hg commit -q -m merge --config format.aggressivemergedeltas=True
146 146 $ hg debugindex -m
147 147 rev offset length delta linkrev nodeid p1 p2
148 148 0 0 59 -1 0 8dde941edb6e 000000000000 000000000000
149 149 1 59 61 0 1 315c023f341d 000000000000 000000000000
150 150 2 120 62 0 2 2ab389a983eb 315c023f341d 8dde941edb6e
151 151
152 152 Test that strip bundle use bundle2
153 153 $ hg --config extensions.strip= strip .
154 154 0 files updated, 0 files merged, 5 files removed, 0 files unresolved
155 155 saved backup bundle to $TESTTMP/aggressive/.hg/strip-backup/1c5d4dc9a8b8-6c68e60c-backup.hg (glob)
156 156 $ hg debugbundle .hg/strip-backup/*
157 157 Stream params: sortdict([('Compression', 'BZ')])
158 158 changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
159 159 1c5d4dc9a8b8d6e1750966d343e94db665e7a1e9
160 160
161 161 $ cd ..
162
163 test maxdeltachainspan
164
165 $ hg init source-repo
166 $ cd source-repo
167 $ hg debugbuilddag --new-file '.+5:brancha$.+11:branchb$.+30:branchc<brancha+2<branchb+2'
168 $ cd ..
169 $ hg -R source-repo debugindex -m
170 rev offset length delta linkrev nodeid p1 p2
171 0 0 46 -1 0 19deeef41503 000000000000 000000000000
172 1 46 57 0 1 fffc37b38c40 19deeef41503 000000000000
173 2 103 57 1 2 5822d75c83d9 fffc37b38c40 000000000000
174 3 160 57 2 3 19cf2273e601 5822d75c83d9 000000000000
175 4 217 57 3 4 d45ead487afe 19cf2273e601 000000000000
176 5 274 57 4 5 96e0c2ce55ed d45ead487afe 000000000000
177 6 331 46 -1 6 0c2ea5222c74 000000000000 000000000000
178 7 377 57 6 7 4ca08a89134d 0c2ea5222c74 000000000000
179 8 434 57 7 8 c973dbfd30ac 4ca08a89134d 000000000000
180 9 491 57 8 9 d81d878ff2cd c973dbfd30ac 000000000000
181 10 548 58 9 10 dbee7f0dd760 d81d878ff2cd 000000000000
182 11 606 58 10 11 474be9f1fd4e dbee7f0dd760 000000000000
183 12 664 58 11 12 594a27502c85 474be9f1fd4e 000000000000
184 13 722 58 12 13 a7d25307d6a9 594a27502c85 000000000000
185 14 780 58 13 14 3eb53082272e a7d25307d6a9 000000000000
186 15 838 58 14 15 d1e94c85caf6 3eb53082272e 000000000000
187 16 896 58 15 16 8933d9629788 d1e94c85caf6 000000000000
188 17 954 58 16 17 a33416e52d91 8933d9629788 000000000000
189 18 1012 47 -1 18 4ccbf31021ed 000000000000 000000000000
190 19 1059 58 18 19 dcad7a25656c 4ccbf31021ed 000000000000
191 20 1117 58 19 20 617c4f8be75f dcad7a25656c 000000000000
192 21 1175 58 20 21 975b9c1d75bb 617c4f8be75f 000000000000
193 22 1233 58 21 22 74f09cd33b70 975b9c1d75bb 000000000000
194 23 1291 58 22 23 54e79bfa7ef1 74f09cd33b70 000000000000
195 24 1349 58 23 24 c556e7ff90af 54e79bfa7ef1 000000000000
196 25 1407 58 24 25 42daedfe9c6b c556e7ff90af 000000000000
197 26 1465 58 25 26 f302566947c7 42daedfe9c6b 000000000000
198 27 1523 58 26 27 2346959851cb f302566947c7 000000000000
199 28 1581 58 27 28 ca8d867106b4 2346959851cb 000000000000
200 29 1639 58 28 29 fd9152decab2 ca8d867106b4 000000000000
201 30 1697 58 29 30 3fe34080a79b fd9152decab2 000000000000
202 31 1755 58 30 31 bce61a95078e 3fe34080a79b 000000000000
203 32 1813 58 31 32 1dd9ba54ba15 bce61a95078e 000000000000
204 33 1871 58 32 33 3cd9b90a9972 1dd9ba54ba15 000000000000
205 34 1929 58 33 34 5db8c9754ef5 3cd9b90a9972 000000000000
206 35 1987 58 34 35 ee4a240cc16c 5db8c9754ef5 000000000000
207 36 2045 58 35 36 9e1d38725343 ee4a240cc16c 000000000000
208 37 2103 58 36 37 3463f73086a8 9e1d38725343 000000000000
209 38 2161 58 37 38 88af72fab449 3463f73086a8 000000000000
210 39 2219 58 38 39 472f5ce73785 88af72fab449 000000000000
211 40 2277 58 39 40 c91b8351e5b8 472f5ce73785 000000000000
212 41 2335 58 40 41 9c8289c5c5c0 c91b8351e5b8 000000000000
213 42 2393 58 41 42 a13fd4a09d76 9c8289c5c5c0 000000000000
214 43 2451 58 42 43 2ec2c81cafe0 a13fd4a09d76 000000000000
215 44 2509 58 43 44 f27fdd174392 2ec2c81cafe0 000000000000
216 45 2567 58 44 45 a539ec59fe41 f27fdd174392 000000000000
217 46 2625 58 45 46 5e98b9ecb738 a539ec59fe41 000000000000
218 47 2683 58 46 47 31e6b47899d0 5e98b9ecb738 000000000000
219 48 2741 58 47 48 2cf25d6636bd 31e6b47899d0 000000000000
220 49 2799 197 -1 49 9fff62ea0624 96e0c2ce55ed 000000000000
221 50 2996 58 49 50 467f8e30a066 9fff62ea0624 000000000000
222 51 3054 356 50 51 346db97283df a33416e52d91 000000000000
223 52 3410 58 51 52 4e003fd4d5cd 346db97283df 000000000000
224 $ hg clone --pull source-repo --config experimental.maxdeltachainspan=2800 relax-chain --config format.generaldelta=yes
225 requesting all changes
226 adding changesets
227 adding manifests
228 adding file changes
229 added 53 changesets with 53 changes to 53 files (+2 heads)
230 updating to branch default
231 14 files updated, 0 files merged, 0 files removed, 0 files unresolved
232 $ hg -R relax-chain debugindex -m
233 rev offset length delta linkrev nodeid p1 p2
234 0 0 46 -1 0 19deeef41503 000000000000 000000000000
235 1 46 57 0 1 fffc37b38c40 19deeef41503 000000000000
236 2 103 57 1 2 5822d75c83d9 fffc37b38c40 000000000000
237 3 160 57 2 3 19cf2273e601 5822d75c83d9 000000000000
238 4 217 57 3 4 d45ead487afe 19cf2273e601 000000000000
239 5 274 57 4 5 96e0c2ce55ed d45ead487afe 000000000000
240 6 331 46 -1 6 0c2ea5222c74 000000000000 000000000000
241 7 377 57 6 7 4ca08a89134d 0c2ea5222c74 000000000000
242 8 434 57 7 8 c973dbfd30ac 4ca08a89134d 000000000000
243 9 491 57 8 9 d81d878ff2cd c973dbfd30ac 000000000000
244 10 548 58 9 10 dbee7f0dd760 d81d878ff2cd 000000000000
245 11 606 58 10 11 474be9f1fd4e dbee7f0dd760 000000000000
246 12 664 58 11 12 594a27502c85 474be9f1fd4e 000000000000
247 13 722 58 12 13 a7d25307d6a9 594a27502c85 000000000000
248 14 780 58 13 14 3eb53082272e a7d25307d6a9 000000000000
249 15 838 58 14 15 d1e94c85caf6 3eb53082272e 000000000000
250 16 896 58 15 16 8933d9629788 d1e94c85caf6 000000000000
251 17 954 58 16 17 a33416e52d91 8933d9629788 000000000000
252 18 1012 47 -1 18 4ccbf31021ed 000000000000 000000000000
253 19 1059 58 18 19 dcad7a25656c 4ccbf31021ed 000000000000
254 20 1117 58 19 20 617c4f8be75f dcad7a25656c 000000000000
255 21 1175 58 20 21 975b9c1d75bb 617c4f8be75f 000000000000
256 22 1233 58 21 22 74f09cd33b70 975b9c1d75bb 000000000000
257 23 1291 58 22 23 54e79bfa7ef1 74f09cd33b70 000000000000
258 24 1349 58 23 24 c556e7ff90af 54e79bfa7ef1 000000000000
259 25 1407 58 24 25 42daedfe9c6b c556e7ff90af 000000000000
260 26 1465 58 25 26 f302566947c7 42daedfe9c6b 000000000000
261 27 1523 58 26 27 2346959851cb f302566947c7 000000000000
262 28 1581 58 27 28 ca8d867106b4 2346959851cb 000000000000
263 29 1639 58 28 29 fd9152decab2 ca8d867106b4 000000000000
264 30 1697 58 29 30 3fe34080a79b fd9152decab2 000000000000
265 31 1755 58 30 31 bce61a95078e 3fe34080a79b 000000000000
266 32 1813 58 31 32 1dd9ba54ba15 bce61a95078e 000000000000
267 33 1871 58 32 33 3cd9b90a9972 1dd9ba54ba15 000000000000
268 34 1929 58 33 34 5db8c9754ef5 3cd9b90a9972 000000000000
269 35 1987 58 34 35 ee4a240cc16c 5db8c9754ef5 000000000000
270 36 2045 58 35 36 9e1d38725343 ee4a240cc16c 000000000000
271 37 2103 58 36 37 3463f73086a8 9e1d38725343 000000000000
272 38 2161 58 37 38 88af72fab449 3463f73086a8 000000000000
273 39 2219 58 38 39 472f5ce73785 88af72fab449 000000000000
274 40 2277 58 39 40 c91b8351e5b8 472f5ce73785 000000000000
275 41 2335 58 40 41 9c8289c5c5c0 c91b8351e5b8 000000000000
276 42 2393 58 41 42 a13fd4a09d76 9c8289c5c5c0 000000000000
277 43 2451 58 42 43 2ec2c81cafe0 a13fd4a09d76 000000000000
278 44 2509 58 43 44 f27fdd174392 2ec2c81cafe0 000000000000
279 45 2567 58 44 45 a539ec59fe41 f27fdd174392 000000000000
280 46 2625 58 45 46 5e98b9ecb738 a539ec59fe41 000000000000
281 47 2683 58 46 47 31e6b47899d0 5e98b9ecb738 000000000000
282 48 2741 58 47 48 2cf25d6636bd 31e6b47899d0 000000000000
283 49 2799 197 -1 49 9fff62ea0624 96e0c2ce55ed 000000000000
284 50 2996 58 49 50 467f8e30a066 9fff62ea0624 000000000000
285 51 3054 58 17 51 346db97283df a33416e52d91 000000000000
286 52 3112 369 -1 52 4e003fd4d5cd 346db97283df 000000000000
287 $ hg clone --pull source-repo --config experimental.maxdeltachainspan=0 noconst-chain --config format.generaldelta=yes
288 requesting all changes
289 adding changesets
290 adding manifests
291 adding file changes
292 added 53 changesets with 53 changes to 53 files (+2 heads)
293 updating to branch default
294 14 files updated, 0 files merged, 0 files removed, 0 files unresolved
295 $ hg -R noconst-chain debugindex -m
296 rev offset length delta linkrev nodeid p1 p2
297 0 0 46 -1 0 19deeef41503 000000000000 000000000000
298 1 46 57 0 1 fffc37b38c40 19deeef41503 000000000000
299 2 103 57 1 2 5822d75c83d9 fffc37b38c40 000000000000
300 3 160 57 2 3 19cf2273e601 5822d75c83d9 000000000000
301 4 217 57 3 4 d45ead487afe 19cf2273e601 000000000000
302 5 274 57 4 5 96e0c2ce55ed d45ead487afe 000000000000
303 6 331 46 -1 6 0c2ea5222c74 000000000000 000000000000
304 7 377 57 6 7 4ca08a89134d 0c2ea5222c74 000000000000
305 8 434 57 7 8 c973dbfd30ac 4ca08a89134d 000000000000
306 9 491 57 8 9 d81d878ff2cd c973dbfd30ac 000000000000
307 10 548 58 9 10 dbee7f0dd760 d81d878ff2cd 000000000000
308 11 606 58 10 11 474be9f1fd4e dbee7f0dd760 000000000000
309 12 664 58 11 12 594a27502c85 474be9f1fd4e 000000000000
310 13 722 58 12 13 a7d25307d6a9 594a27502c85 000000000000
311 14 780 58 13 14 3eb53082272e a7d25307d6a9 000000000000
312 15 838 58 14 15 d1e94c85caf6 3eb53082272e 000000000000
313 16 896 58 15 16 8933d9629788 d1e94c85caf6 000000000000
314 17 954 58 16 17 a33416e52d91 8933d9629788 000000000000
315 18 1012 47 -1 18 4ccbf31021ed 000000000000 000000000000
316 19 1059 58 18 19 dcad7a25656c 4ccbf31021ed 000000000000
317 20 1117 58 19 20 617c4f8be75f dcad7a25656c 000000000000
318 21 1175 58 20 21 975b9c1d75bb 617c4f8be75f 000000000000
319 22 1233 58 21 22 74f09cd33b70 975b9c1d75bb 000000000000
320 23 1291 58 22 23 54e79bfa7ef1 74f09cd33b70 000000000000
321 24 1349 58 23 24 c556e7ff90af 54e79bfa7ef1 000000000000
322 25 1407 58 24 25 42daedfe9c6b c556e7ff90af 000000000000
323 26 1465 58 25 26 f302566947c7 42daedfe9c6b 000000000000
324 27 1523 58 26 27 2346959851cb f302566947c7 000000000000
325 28 1581 58 27 28 ca8d867106b4 2346959851cb 000000000000
326 29 1639 58 28 29 fd9152decab2 ca8d867106b4 000000000000
327 30 1697 58 29 30 3fe34080a79b fd9152decab2 000000000000
328 31 1755 58 30 31 bce61a95078e 3fe34080a79b 000000000000
329 32 1813 58 31 32 1dd9ba54ba15 bce61a95078e 000000000000
330 33 1871 58 32 33 3cd9b90a9972 1dd9ba54ba15 000000000000
331 34 1929 58 33 34 5db8c9754ef5 3cd9b90a9972 000000000000
332 35 1987 58 34 35 ee4a240cc16c 5db8c9754ef5 000000000000
333 36 2045 58 35 36 9e1d38725343 ee4a240cc16c 000000000000
334 37 2103 58 36 37 3463f73086a8 9e1d38725343 000000000000
335 38 2161 58 37 38 88af72fab449 3463f73086a8 000000000000
336 39 2219 58 38 39 472f5ce73785 88af72fab449 000000000000
337 40 2277 58 39 40 c91b8351e5b8 472f5ce73785 000000000000
338 41 2335 58 40 41 9c8289c5c5c0 c91b8351e5b8 000000000000
339 42 2393 58 41 42 a13fd4a09d76 9c8289c5c5c0 000000000000
340 43 2451 58 42 43 2ec2c81cafe0 a13fd4a09d76 000000000000
341 44 2509 58 43 44 f27fdd174392 2ec2c81cafe0 000000000000
342 45 2567 58 44 45 a539ec59fe41 f27fdd174392 000000000000
343 46 2625 58 45 46 5e98b9ecb738 a539ec59fe41 000000000000
344 47 2683 58 46 47 31e6b47899d0 5e98b9ecb738 000000000000
345 48 2741 58 47 48 2cf25d6636bd 31e6b47899d0 000000000000
346 49 2799 58 5 49 9fff62ea0624 96e0c2ce55ed 000000000000
347 50 2857 58 49 50 467f8e30a066 9fff62ea0624 000000000000
348 51 2915 58 17 51 346db97283df a33416e52d91 000000000000
349 52 2973 58 51 52 4e003fd4d5cd 346db97283df 000000000000
General Comments 0
You need to be logged in to leave comments. Login now