##// END OF EJS Templates
revlog: allow flag processors to be applied via store options...
Matt Harbison -
r40303:9d5ddf55 default
parent child Browse files
Show More
@@ -1,3031 +1,3032 b''
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 os
13 13 import random
14 14 import sys
15 15 import time
16 16 import weakref
17 17
18 18 from .i18n import _
19 19 from .node import (
20 20 bin,
21 21 hex,
22 22 nullid,
23 23 nullrev,
24 24 short,
25 25 )
26 26 from . import (
27 27 bookmarks,
28 28 branchmap,
29 29 bundle2,
30 30 changegroup,
31 31 changelog,
32 32 color,
33 33 context,
34 34 dirstate,
35 35 dirstateguard,
36 36 discovery,
37 37 encoding,
38 38 error,
39 39 exchange,
40 40 extensions,
41 41 filelog,
42 42 hook,
43 43 lock as lockmod,
44 44 manifest,
45 45 match as matchmod,
46 46 merge as mergemod,
47 47 mergeutil,
48 48 namespaces,
49 49 narrowspec,
50 50 obsolete,
51 51 pathutil,
52 52 phases,
53 53 pushkey,
54 54 pycompat,
55 55 repository,
56 56 repoview,
57 57 revset,
58 58 revsetlang,
59 59 scmutil,
60 60 sparse,
61 61 store as storemod,
62 62 subrepoutil,
63 63 tags as tagsmod,
64 64 transaction,
65 65 txnutil,
66 66 util,
67 67 vfs as vfsmod,
68 68 )
69 69 from .utils import (
70 70 interfaceutil,
71 71 procutil,
72 72 stringutil,
73 73 )
74 74
75 75 from .revlogutils import (
76 76 constants as revlogconst,
77 77 )
78 78
79 79 release = lockmod.release
80 80 urlerr = util.urlerr
81 81 urlreq = util.urlreq
82 82
83 83 # set of (path, vfs-location) tuples. vfs-location is:
84 84 # - 'plain for vfs relative paths
85 85 # - '' for svfs relative paths
86 86 _cachedfiles = set()
87 87
88 88 class _basefilecache(scmutil.filecache):
89 89 """All filecache usage on repo are done for logic that should be unfiltered
90 90 """
91 91 def __get__(self, repo, type=None):
92 92 if repo is None:
93 93 return self
94 94 return super(_basefilecache, self).__get__(repo.unfiltered(), type)
95 95 def __set__(self, repo, value):
96 96 return super(_basefilecache, self).__set__(repo.unfiltered(), value)
97 97 def __delete__(self, repo):
98 98 return super(_basefilecache, self).__delete__(repo.unfiltered())
99 99
100 100 class repofilecache(_basefilecache):
101 101 """filecache for files in .hg but outside of .hg/store"""
102 102 def __init__(self, *paths):
103 103 super(repofilecache, self).__init__(*paths)
104 104 for path in paths:
105 105 _cachedfiles.add((path, 'plain'))
106 106
107 107 def join(self, obj, fname):
108 108 return obj.vfs.join(fname)
109 109
110 110 class storecache(_basefilecache):
111 111 """filecache for files in the store"""
112 112 def __init__(self, *paths):
113 113 super(storecache, self).__init__(*paths)
114 114 for path in paths:
115 115 _cachedfiles.add((path, ''))
116 116
117 117 def join(self, obj, fname):
118 118 return obj.sjoin(fname)
119 119
120 120 def isfilecached(repo, name):
121 121 """check if a repo has already cached "name" filecache-ed property
122 122
123 123 This returns (cachedobj-or-None, iscached) tuple.
124 124 """
125 125 cacheentry = repo.unfiltered()._filecache.get(name, None)
126 126 if not cacheentry:
127 127 return None, False
128 128 return cacheentry.obj, True
129 129
130 130 class unfilteredpropertycache(util.propertycache):
131 131 """propertycache that apply to unfiltered repo only"""
132 132
133 133 def __get__(self, repo, type=None):
134 134 unfi = repo.unfiltered()
135 135 if unfi is repo:
136 136 return super(unfilteredpropertycache, self).__get__(unfi)
137 137 return getattr(unfi, self.name)
138 138
139 139 class filteredpropertycache(util.propertycache):
140 140 """propertycache that must take filtering in account"""
141 141
142 142 def cachevalue(self, obj, value):
143 143 object.__setattr__(obj, self.name, value)
144 144
145 145
146 146 def hasunfilteredcache(repo, name):
147 147 """check if a repo has an unfilteredpropertycache value for <name>"""
148 148 return name in vars(repo.unfiltered())
149 149
150 150 def unfilteredmethod(orig):
151 151 """decorate method that always need to be run on unfiltered version"""
152 152 def wrapper(repo, *args, **kwargs):
153 153 return orig(repo.unfiltered(), *args, **kwargs)
154 154 return wrapper
155 155
156 156 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
157 157 'unbundle'}
158 158 legacycaps = moderncaps.union({'changegroupsubset'})
159 159
160 160 @interfaceutil.implementer(repository.ipeercommandexecutor)
161 161 class localcommandexecutor(object):
162 162 def __init__(self, peer):
163 163 self._peer = peer
164 164 self._sent = False
165 165 self._closed = False
166 166
167 167 def __enter__(self):
168 168 return self
169 169
170 170 def __exit__(self, exctype, excvalue, exctb):
171 171 self.close()
172 172
173 173 def callcommand(self, command, args):
174 174 if self._sent:
175 175 raise error.ProgrammingError('callcommand() cannot be used after '
176 176 'sendcommands()')
177 177
178 178 if self._closed:
179 179 raise error.ProgrammingError('callcommand() cannot be used after '
180 180 'close()')
181 181
182 182 # We don't need to support anything fancy. Just call the named
183 183 # method on the peer and return a resolved future.
184 184 fn = getattr(self._peer, pycompat.sysstr(command))
185 185
186 186 f = pycompat.futures.Future()
187 187
188 188 try:
189 189 result = fn(**pycompat.strkwargs(args))
190 190 except Exception:
191 191 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
192 192 else:
193 193 f.set_result(result)
194 194
195 195 return f
196 196
197 197 def sendcommands(self):
198 198 self._sent = True
199 199
200 200 def close(self):
201 201 self._closed = True
202 202
203 203 @interfaceutil.implementer(repository.ipeercommands)
204 204 class localpeer(repository.peer):
205 205 '''peer for a local repo; reflects only the most recent API'''
206 206
207 207 def __init__(self, repo, caps=None):
208 208 super(localpeer, self).__init__()
209 209
210 210 if caps is None:
211 211 caps = moderncaps.copy()
212 212 self._repo = repo.filtered('served')
213 213 self.ui = repo.ui
214 214 self._caps = repo._restrictcapabilities(caps)
215 215
216 216 # Begin of _basepeer interface.
217 217
218 218 def url(self):
219 219 return self._repo.url()
220 220
221 221 def local(self):
222 222 return self._repo
223 223
224 224 def peer(self):
225 225 return self
226 226
227 227 def canpush(self):
228 228 return True
229 229
230 230 def close(self):
231 231 self._repo.close()
232 232
233 233 # End of _basepeer interface.
234 234
235 235 # Begin of _basewirecommands interface.
236 236
237 237 def branchmap(self):
238 238 return self._repo.branchmap()
239 239
240 240 def capabilities(self):
241 241 return self._caps
242 242
243 243 def clonebundles(self):
244 244 return self._repo.tryread('clonebundles.manifest')
245 245
246 246 def debugwireargs(self, one, two, three=None, four=None, five=None):
247 247 """Used to test argument passing over the wire"""
248 248 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
249 249 pycompat.bytestr(four),
250 250 pycompat.bytestr(five))
251 251
252 252 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
253 253 **kwargs):
254 254 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
255 255 common=common, bundlecaps=bundlecaps,
256 256 **kwargs)[1]
257 257 cb = util.chunkbuffer(chunks)
258 258
259 259 if exchange.bundle2requested(bundlecaps):
260 260 # When requesting a bundle2, getbundle returns a stream to make the
261 261 # wire level function happier. We need to build a proper object
262 262 # from it in local peer.
263 263 return bundle2.getunbundler(self.ui, cb)
264 264 else:
265 265 return changegroup.getunbundler('01', cb, None)
266 266
267 267 def heads(self):
268 268 return self._repo.heads()
269 269
270 270 def known(self, nodes):
271 271 return self._repo.known(nodes)
272 272
273 273 def listkeys(self, namespace):
274 274 return self._repo.listkeys(namespace)
275 275
276 276 def lookup(self, key):
277 277 return self._repo.lookup(key)
278 278
279 279 def pushkey(self, namespace, key, old, new):
280 280 return self._repo.pushkey(namespace, key, old, new)
281 281
282 282 def stream_out(self):
283 283 raise error.Abort(_('cannot perform stream clone against local '
284 284 'peer'))
285 285
286 286 def unbundle(self, bundle, heads, url):
287 287 """apply a bundle on a repo
288 288
289 289 This function handles the repo locking itself."""
290 290 try:
291 291 try:
292 292 bundle = exchange.readbundle(self.ui, bundle, None)
293 293 ret = exchange.unbundle(self._repo, bundle, heads, 'push', url)
294 294 if util.safehasattr(ret, 'getchunks'):
295 295 # This is a bundle20 object, turn it into an unbundler.
296 296 # This little dance should be dropped eventually when the
297 297 # API is finally improved.
298 298 stream = util.chunkbuffer(ret.getchunks())
299 299 ret = bundle2.getunbundler(self.ui, stream)
300 300 return ret
301 301 except Exception as exc:
302 302 # If the exception contains output salvaged from a bundle2
303 303 # reply, we need to make sure it is printed before continuing
304 304 # to fail. So we build a bundle2 with such output and consume
305 305 # it directly.
306 306 #
307 307 # This is not very elegant but allows a "simple" solution for
308 308 # issue4594
309 309 output = getattr(exc, '_bundle2salvagedoutput', ())
310 310 if output:
311 311 bundler = bundle2.bundle20(self._repo.ui)
312 312 for out in output:
313 313 bundler.addpart(out)
314 314 stream = util.chunkbuffer(bundler.getchunks())
315 315 b = bundle2.getunbundler(self.ui, stream)
316 316 bundle2.processbundle(self._repo, b)
317 317 raise
318 318 except error.PushRaced as exc:
319 319 raise error.ResponseError(_('push failed:'),
320 320 stringutil.forcebytestr(exc))
321 321
322 322 # End of _basewirecommands interface.
323 323
324 324 # Begin of peer interface.
325 325
326 326 def commandexecutor(self):
327 327 return localcommandexecutor(self)
328 328
329 329 # End of peer interface.
330 330
331 331 @interfaceutil.implementer(repository.ipeerlegacycommands)
332 332 class locallegacypeer(localpeer):
333 333 '''peer extension which implements legacy methods too; used for tests with
334 334 restricted capabilities'''
335 335
336 336 def __init__(self, repo):
337 337 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
338 338
339 339 # Begin of baselegacywirecommands interface.
340 340
341 341 def between(self, pairs):
342 342 return self._repo.between(pairs)
343 343
344 344 def branches(self, nodes):
345 345 return self._repo.branches(nodes)
346 346
347 347 def changegroup(self, nodes, source):
348 348 outgoing = discovery.outgoing(self._repo, missingroots=nodes,
349 349 missingheads=self._repo.heads())
350 350 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
351 351
352 352 def changegroupsubset(self, bases, heads, source):
353 353 outgoing = discovery.outgoing(self._repo, missingroots=bases,
354 354 missingheads=heads)
355 355 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
356 356
357 357 # End of baselegacywirecommands interface.
358 358
359 359 # Increment the sub-version when the revlog v2 format changes to lock out old
360 360 # clients.
361 361 REVLOGV2_REQUIREMENT = 'exp-revlogv2.0'
362 362
363 363 # A repository with the sparserevlog feature will have delta chains that
364 364 # can spread over a larger span. Sparse reading cuts these large spans into
365 365 # pieces, so that each piece isn't too big.
366 366 # Without the sparserevlog capability, reading from the repository could use
367 367 # huge amounts of memory, because the whole span would be read at once,
368 368 # including all the intermediate revisions that aren't pertinent for the chain.
369 369 # This is why once a repository has enabled sparse-read, it becomes required.
370 370 SPARSEREVLOG_REQUIREMENT = 'sparserevlog'
371 371
372 372 # Functions receiving (ui, features) that extensions can register to impact
373 373 # the ability to load repositories with custom requirements. Only
374 374 # functions defined in loaded extensions are called.
375 375 #
376 376 # The function receives a set of requirement strings that the repository
377 377 # is capable of opening. Functions will typically add elements to the
378 378 # set to reflect that the extension knows how to handle that requirements.
379 379 featuresetupfuncs = set()
380 380
381 381 def makelocalrepository(baseui, path, intents=None):
382 382 """Create a local repository object.
383 383
384 384 Given arguments needed to construct a local repository, this function
385 385 performs various early repository loading functionality (such as
386 386 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
387 387 the repository can be opened, derives a type suitable for representing
388 388 that repository, and returns an instance of it.
389 389
390 390 The returned object conforms to the ``repository.completelocalrepository``
391 391 interface.
392 392
393 393 The repository type is derived by calling a series of factory functions
394 394 for each aspect/interface of the final repository. These are defined by
395 395 ``REPO_INTERFACES``.
396 396
397 397 Each factory function is called to produce a type implementing a specific
398 398 interface. The cumulative list of returned types will be combined into a
399 399 new type and that type will be instantiated to represent the local
400 400 repository.
401 401
402 402 The factory functions each receive various state that may be consulted
403 403 as part of deriving a type.
404 404
405 405 Extensions should wrap these factory functions to customize repository type
406 406 creation. Note that an extension's wrapped function may be called even if
407 407 that extension is not loaded for the repo being constructed. Extensions
408 408 should check if their ``__name__`` appears in the
409 409 ``extensionmodulenames`` set passed to the factory function and no-op if
410 410 not.
411 411 """
412 412 ui = baseui.copy()
413 413 # Prevent copying repo configuration.
414 414 ui.copy = baseui.copy
415 415
416 416 # Working directory VFS rooted at repository root.
417 417 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
418 418
419 419 # Main VFS for .hg/ directory.
420 420 hgpath = wdirvfs.join(b'.hg')
421 421 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
422 422
423 423 # The .hg/ path should exist and should be a directory. All other
424 424 # cases are errors.
425 425 if not hgvfs.isdir():
426 426 try:
427 427 hgvfs.stat()
428 428 except OSError as e:
429 429 if e.errno != errno.ENOENT:
430 430 raise
431 431
432 432 raise error.RepoError(_(b'repository %s not found') % path)
433 433
434 434 # .hg/requires file contains a newline-delimited list of
435 435 # features/capabilities the opener (us) must have in order to use
436 436 # the repository. This file was introduced in Mercurial 0.9.2,
437 437 # which means very old repositories may not have one. We assume
438 438 # a missing file translates to no requirements.
439 439 try:
440 440 requirements = set(hgvfs.read(b'requires').splitlines())
441 441 except IOError as e:
442 442 if e.errno != errno.ENOENT:
443 443 raise
444 444 requirements = set()
445 445
446 446 # The .hg/hgrc file may load extensions or contain config options
447 447 # that influence repository construction. Attempt to load it and
448 448 # process any new extensions that it may have pulled in.
449 449 try:
450 450 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
451 451 # Run this before extensions.loadall() so extensions can be
452 452 # automatically enabled.
453 453 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
454 454 except IOError:
455 455 pass
456 456 else:
457 457 extensions.loadall(ui)
458 458
459 459 # Set of module names of extensions loaded for this repository.
460 460 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
461 461
462 462 supportedrequirements = gathersupportedrequirements(ui)
463 463
464 464 # We first validate the requirements are known.
465 465 ensurerequirementsrecognized(requirements, supportedrequirements)
466 466
467 467 # Then we validate that the known set is reasonable to use together.
468 468 ensurerequirementscompatible(ui, requirements)
469 469
470 470 # TODO there are unhandled edge cases related to opening repositories with
471 471 # shared storage. If storage is shared, we should also test for requirements
472 472 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
473 473 # that repo, as that repo may load extensions needed to open it. This is a
474 474 # bit complicated because we don't want the other hgrc to overwrite settings
475 475 # in this hgrc.
476 476 #
477 477 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
478 478 # file when sharing repos. But if a requirement is added after the share is
479 479 # performed, thereby introducing a new requirement for the opener, we may
480 480 # will not see that and could encounter a run-time error interacting with
481 481 # that shared store since it has an unknown-to-us requirement.
482 482
483 483 # At this point, we know we should be capable of opening the repository.
484 484 # Now get on with doing that.
485 485
486 486 features = set()
487 487
488 488 # The "store" part of the repository holds versioned data. How it is
489 489 # accessed is determined by various requirements. The ``shared`` or
490 490 # ``relshared`` requirements indicate the store lives in the path contained
491 491 # in the ``.hg/sharedpath`` file. This is an absolute path for
492 492 # ``shared`` and relative to ``.hg/`` for ``relshared``.
493 493 if b'shared' in requirements or b'relshared' in requirements:
494 494 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
495 495 if b'relshared' in requirements:
496 496 sharedpath = hgvfs.join(sharedpath)
497 497
498 498 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
499 499
500 500 if not sharedvfs.exists():
501 501 raise error.RepoError(_(b'.hg/sharedpath points to nonexistent '
502 502 b'directory %s') % sharedvfs.base)
503 503
504 504 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
505 505
506 506 storebasepath = sharedvfs.base
507 507 cachepath = sharedvfs.join(b'cache')
508 508 else:
509 509 storebasepath = hgvfs.base
510 510 cachepath = hgvfs.join(b'cache')
511 511
512 512 # The store has changed over time and the exact layout is dictated by
513 513 # requirements. The store interface abstracts differences across all
514 514 # of them.
515 515 store = makestore(requirements, storebasepath,
516 516 lambda base: vfsmod.vfs(base, cacheaudited=True))
517 517 hgvfs.createmode = store.createmode
518 518
519 519 storevfs = store.vfs
520 520 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
521 521
522 522 # The cache vfs is used to manage cache files.
523 523 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
524 524 cachevfs.createmode = store.createmode
525 525
526 526 # Now resolve the type for the repository object. We do this by repeatedly
527 527 # calling a factory function to produces types for specific aspects of the
528 528 # repo's operation. The aggregate returned types are used as base classes
529 529 # for a dynamically-derived type, which will represent our new repository.
530 530
531 531 bases = []
532 532 extrastate = {}
533 533
534 534 for iface, fn in REPO_INTERFACES:
535 535 # We pass all potentially useful state to give extensions tons of
536 536 # flexibility.
537 537 typ = fn()(ui=ui,
538 538 intents=intents,
539 539 requirements=requirements,
540 540 features=features,
541 541 wdirvfs=wdirvfs,
542 542 hgvfs=hgvfs,
543 543 store=store,
544 544 storevfs=storevfs,
545 545 storeoptions=storevfs.options,
546 546 cachevfs=cachevfs,
547 547 extensionmodulenames=extensionmodulenames,
548 548 extrastate=extrastate,
549 549 baseclasses=bases)
550 550
551 551 if not isinstance(typ, type):
552 552 raise error.ProgrammingError('unable to construct type for %s' %
553 553 iface)
554 554
555 555 bases.append(typ)
556 556
557 557 # type() allows you to use characters in type names that wouldn't be
558 558 # recognized as Python symbols in source code. We abuse that to add
559 559 # rich information about our constructed repo.
560 560 name = pycompat.sysstr(b'derivedrepo:%s<%s>' % (
561 561 wdirvfs.base,
562 562 b','.join(sorted(requirements))))
563 563
564 564 cls = type(name, tuple(bases), {})
565 565
566 566 return cls(
567 567 baseui=baseui,
568 568 ui=ui,
569 569 origroot=path,
570 570 wdirvfs=wdirvfs,
571 571 hgvfs=hgvfs,
572 572 requirements=requirements,
573 573 supportedrequirements=supportedrequirements,
574 574 sharedpath=storebasepath,
575 575 store=store,
576 576 cachevfs=cachevfs,
577 577 features=features,
578 578 intents=intents)
579 579
580 580 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
581 581 """Perform additional actions after .hg/hgrc is loaded.
582 582
583 583 This function is called during repository loading immediately after
584 584 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
585 585
586 586 The function can be used to validate configs, automatically add
587 587 options (including extensions) based on requirements, etc.
588 588 """
589 589
590 590 # Map of requirements to list of extensions to load automatically when
591 591 # requirement is present.
592 592 autoextensions = {
593 593 b'largefiles': [b'largefiles'],
594 594 b'lfs': [b'lfs'],
595 595 }
596 596
597 597 for requirement, names in sorted(autoextensions.items()):
598 598 if requirement not in requirements:
599 599 continue
600 600
601 601 for name in names:
602 602 if not ui.hasconfig(b'extensions', name):
603 603 ui.setconfig(b'extensions', name, b'', source='autoload')
604 604
605 605 def gathersupportedrequirements(ui):
606 606 """Determine the complete set of recognized requirements."""
607 607 # Start with all requirements supported by this file.
608 608 supported = set(localrepository._basesupported)
609 609
610 610 # Execute ``featuresetupfuncs`` entries if they belong to an extension
611 611 # relevant to this ui instance.
612 612 modules = {m.__name__ for n, m in extensions.extensions(ui)}
613 613
614 614 for fn in featuresetupfuncs:
615 615 if fn.__module__ in modules:
616 616 fn(ui, supported)
617 617
618 618 # Add derived requirements from registered compression engines.
619 619 for name in util.compengines:
620 620 engine = util.compengines[name]
621 621 if engine.revlogheader():
622 622 supported.add(b'exp-compression-%s' % name)
623 623
624 624 return supported
625 625
626 626 def ensurerequirementsrecognized(requirements, supported):
627 627 """Validate that a set of local requirements is recognized.
628 628
629 629 Receives a set of requirements. Raises an ``error.RepoError`` if there
630 630 exists any requirement in that set that currently loaded code doesn't
631 631 recognize.
632 632
633 633 Returns a set of supported requirements.
634 634 """
635 635 missing = set()
636 636
637 637 for requirement in requirements:
638 638 if requirement in supported:
639 639 continue
640 640
641 641 if not requirement or not requirement[0:1].isalnum():
642 642 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
643 643
644 644 missing.add(requirement)
645 645
646 646 if missing:
647 647 raise error.RequirementError(
648 648 _(b'repository requires features unknown to this Mercurial: %s') %
649 649 b' '.join(sorted(missing)),
650 650 hint=_(b'see https://mercurial-scm.org/wiki/MissingRequirement '
651 651 b'for more information'))
652 652
653 653 def ensurerequirementscompatible(ui, requirements):
654 654 """Validates that a set of recognized requirements is mutually compatible.
655 655
656 656 Some requirements may not be compatible with others or require
657 657 config options that aren't enabled. This function is called during
658 658 repository opening to ensure that the set of requirements needed
659 659 to open a repository is sane and compatible with config options.
660 660
661 661 Extensions can monkeypatch this function to perform additional
662 662 checking.
663 663
664 664 ``error.RepoError`` should be raised on failure.
665 665 """
666 666 if b'exp-sparse' in requirements and not sparse.enabled:
667 667 raise error.RepoError(_(b'repository is using sparse feature but '
668 668 b'sparse is not enabled; enable the '
669 669 b'"sparse" extensions to access'))
670 670
671 671 def makestore(requirements, path, vfstype):
672 672 """Construct a storage object for a repository."""
673 673 if b'store' in requirements:
674 674 if b'fncache' in requirements:
675 675 return storemod.fncachestore(path, vfstype,
676 676 b'dotencode' in requirements)
677 677
678 678 return storemod.encodedstore(path, vfstype)
679 679
680 680 return storemod.basicstore(path, vfstype)
681 681
682 682 def resolvestorevfsoptions(ui, requirements, features):
683 683 """Resolve the options to pass to the store vfs opener.
684 684
685 685 The returned dict is used to influence behavior of the storage layer.
686 686 """
687 687 options = {}
688 688
689 689 if b'treemanifest' in requirements:
690 690 options[b'treemanifest'] = True
691 691
692 692 # experimental config: format.manifestcachesize
693 693 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
694 694 if manifestcachesize is not None:
695 695 options[b'manifestcachesize'] = manifestcachesize
696 696
697 697 # In the absence of another requirement superseding a revlog-related
698 698 # requirement, we have to assume the repo is using revlog version 0.
699 699 # This revlog format is super old and we don't bother trying to parse
700 700 # opener options for it because those options wouldn't do anything
701 701 # meaningful on such old repos.
702 702 if b'revlogv1' in requirements or REVLOGV2_REQUIREMENT in requirements:
703 703 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
704 704
705 705 return options
706 706
707 707 def resolverevlogstorevfsoptions(ui, requirements, features):
708 708 """Resolve opener options specific to revlogs."""
709 709
710 710 options = {}
711 options[b'flagprocessors'] = {}
711 712
712 713 if b'revlogv1' in requirements:
713 714 options[b'revlogv1'] = True
714 715 if REVLOGV2_REQUIREMENT in requirements:
715 716 options[b'revlogv2'] = True
716 717
717 718 if b'generaldelta' in requirements:
718 719 options[b'generaldelta'] = True
719 720
720 721 # experimental config: format.chunkcachesize
721 722 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
722 723 if chunkcachesize is not None:
723 724 options[b'chunkcachesize'] = chunkcachesize
724 725
725 726 deltabothparents = ui.configbool(b'storage',
726 727 b'revlog.optimize-delta-parent-choice')
727 728 options[b'deltabothparents'] = deltabothparents
728 729
729 730 options[b'lazydeltabase'] = not scmutil.gddeltaconfig(ui)
730 731
731 732 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
732 733 if 0 <= chainspan:
733 734 options[b'maxdeltachainspan'] = chainspan
734 735
735 736 mmapindexthreshold = ui.configbytes(b'experimental',
736 737 b'mmapindexthreshold')
737 738 if mmapindexthreshold is not None:
738 739 options[b'mmapindexthreshold'] = mmapindexthreshold
739 740
740 741 withsparseread = ui.configbool(b'experimental', b'sparse-read')
741 742 srdensitythres = float(ui.config(b'experimental',
742 743 b'sparse-read.density-threshold'))
743 744 srmingapsize = ui.configbytes(b'experimental',
744 745 b'sparse-read.min-gap-size')
745 746 options[b'with-sparse-read'] = withsparseread
746 747 options[b'sparse-read-density-threshold'] = srdensitythres
747 748 options[b'sparse-read-min-gap-size'] = srmingapsize
748 749
749 750 sparserevlog = SPARSEREVLOG_REQUIREMENT in requirements
750 751 options[b'sparse-revlog'] = sparserevlog
751 752 if sparserevlog:
752 753 options[b'generaldelta'] = True
753 754
754 755 maxchainlen = None
755 756 if sparserevlog:
756 757 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
757 758 # experimental config: format.maxchainlen
758 759 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
759 760 if maxchainlen is not None:
760 761 options[b'maxchainlen'] = maxchainlen
761 762
762 763 for r in requirements:
763 764 if r.startswith(b'exp-compression-'):
764 765 options[b'compengine'] = r[len(b'exp-compression-'):]
765 766
766 767 if repository.NARROW_REQUIREMENT in requirements:
767 768 options[b'enableellipsis'] = True
768 769
769 770 return options
770 771
771 772 def makemain(**kwargs):
772 773 """Produce a type conforming to ``ilocalrepositorymain``."""
773 774 return localrepository
774 775
775 776 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
776 777 class revlogfilestorage(object):
777 778 """File storage when using revlogs."""
778 779
779 780 def file(self, path):
780 781 if path[0] == b'/':
781 782 path = path[1:]
782 783
783 784 return filelog.filelog(self.svfs, path)
784 785
785 786 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
786 787 class revlognarrowfilestorage(object):
787 788 """File storage when using revlogs and narrow files."""
788 789
789 790 def file(self, path):
790 791 if path[0] == b'/':
791 792 path = path[1:]
792 793
793 794 return filelog.narrowfilelog(self.svfs, path, self.narrowmatch())
794 795
795 796 def makefilestorage(requirements, features, **kwargs):
796 797 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
797 798 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
798 799 features.add(repository.REPO_FEATURE_STREAM_CLONE)
799 800
800 801 if repository.NARROW_REQUIREMENT in requirements:
801 802 return revlognarrowfilestorage
802 803 else:
803 804 return revlogfilestorage
804 805
805 806 # List of repository interfaces and factory functions for them. Each
806 807 # will be called in order during ``makelocalrepository()`` to iteratively
807 808 # derive the final type for a local repository instance. We capture the
808 809 # function as a lambda so we don't hold a reference and the module-level
809 810 # functions can be wrapped.
810 811 REPO_INTERFACES = [
811 812 (repository.ilocalrepositorymain, lambda: makemain),
812 813 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
813 814 ]
814 815
815 816 @interfaceutil.implementer(repository.ilocalrepositorymain)
816 817 class localrepository(object):
817 818 """Main class for representing local repositories.
818 819
819 820 All local repositories are instances of this class.
820 821
821 822 Constructed on its own, instances of this class are not usable as
822 823 repository objects. To obtain a usable repository object, call
823 824 ``hg.repository()``, ``localrepo.instance()``, or
824 825 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
825 826 ``instance()`` adds support for creating new repositories.
826 827 ``hg.repository()`` adds more extension integration, including calling
827 828 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
828 829 used.
829 830 """
830 831
831 832 # obsolete experimental requirements:
832 833 # - manifestv2: An experimental new manifest format that allowed
833 834 # for stem compression of long paths. Experiment ended up not
834 835 # being successful (repository sizes went up due to worse delta
835 836 # chains), and the code was deleted in 4.6.
836 837 supportedformats = {
837 838 'revlogv1',
838 839 'generaldelta',
839 840 'treemanifest',
840 841 REVLOGV2_REQUIREMENT,
841 842 SPARSEREVLOG_REQUIREMENT,
842 843 }
843 844 _basesupported = supportedformats | {
844 845 'store',
845 846 'fncache',
846 847 'shared',
847 848 'relshared',
848 849 'dotencode',
849 850 'exp-sparse',
850 851 'internal-phase'
851 852 }
852 853
853 854 # list of prefix for file which can be written without 'wlock'
854 855 # Extensions should extend this list when needed
855 856 _wlockfreeprefix = {
856 857 # We migh consider requiring 'wlock' for the next
857 858 # two, but pretty much all the existing code assume
858 859 # wlock is not needed so we keep them excluded for
859 860 # now.
860 861 'hgrc',
861 862 'requires',
862 863 # XXX cache is a complicatged business someone
863 864 # should investigate this in depth at some point
864 865 'cache/',
865 866 # XXX shouldn't be dirstate covered by the wlock?
866 867 'dirstate',
867 868 # XXX bisect was still a bit too messy at the time
868 869 # this changeset was introduced. Someone should fix
869 870 # the remainig bit and drop this line
870 871 'bisect.state',
871 872 }
872 873
873 874 def __init__(self, baseui, ui, origroot, wdirvfs, hgvfs, requirements,
874 875 supportedrequirements, sharedpath, store, cachevfs,
875 876 features, intents=None):
876 877 """Create a new local repository instance.
877 878
878 879 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
879 880 or ``localrepo.makelocalrepository()`` for obtaining a new repository
880 881 object.
881 882
882 883 Arguments:
883 884
884 885 baseui
885 886 ``ui.ui`` instance that ``ui`` argument was based off of.
886 887
887 888 ui
888 889 ``ui.ui`` instance for use by the repository.
889 890
890 891 origroot
891 892 ``bytes`` path to working directory root of this repository.
892 893
893 894 wdirvfs
894 895 ``vfs.vfs`` rooted at the working directory.
895 896
896 897 hgvfs
897 898 ``vfs.vfs`` rooted at .hg/
898 899
899 900 requirements
900 901 ``set`` of bytestrings representing repository opening requirements.
901 902
902 903 supportedrequirements
903 904 ``set`` of bytestrings representing repository requirements that we
904 905 know how to open. May be a supetset of ``requirements``.
905 906
906 907 sharedpath
907 908 ``bytes`` Defining path to storage base directory. Points to a
908 909 ``.hg/`` directory somewhere.
909 910
910 911 store
911 912 ``store.basicstore`` (or derived) instance providing access to
912 913 versioned storage.
913 914
914 915 cachevfs
915 916 ``vfs.vfs`` used for cache files.
916 917
917 918 features
918 919 ``set`` of bytestrings defining features/capabilities of this
919 920 instance.
920 921
921 922 intents
922 923 ``set`` of system strings indicating what this repo will be used
923 924 for.
924 925 """
925 926 self.baseui = baseui
926 927 self.ui = ui
927 928 self.origroot = origroot
928 929 # vfs rooted at working directory.
929 930 self.wvfs = wdirvfs
930 931 self.root = wdirvfs.base
931 932 # vfs rooted at .hg/. Used to access most non-store paths.
932 933 self.vfs = hgvfs
933 934 self.path = hgvfs.base
934 935 self.requirements = requirements
935 936 self.supported = supportedrequirements
936 937 self.sharedpath = sharedpath
937 938 self.store = store
938 939 self.cachevfs = cachevfs
939 940 self.features = features
940 941
941 942 self.filtername = None
942 943
943 944 if (self.ui.configbool('devel', 'all-warnings') or
944 945 self.ui.configbool('devel', 'check-locks')):
945 946 self.vfs.audit = self._getvfsward(self.vfs.audit)
946 947 # A list of callback to shape the phase if no data were found.
947 948 # Callback are in the form: func(repo, roots) --> processed root.
948 949 # This list it to be filled by extension during repo setup
949 950 self._phasedefaults = []
950 951
951 952 color.setup(self.ui)
952 953
953 954 self.spath = self.store.path
954 955 self.svfs = self.store.vfs
955 956 self.sjoin = self.store.join
956 957 if (self.ui.configbool('devel', 'all-warnings') or
957 958 self.ui.configbool('devel', 'check-locks')):
958 959 if util.safehasattr(self.svfs, 'vfs'): # this is filtervfs
959 960 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
960 961 else: # standard vfs
961 962 self.svfs.audit = self._getsvfsward(self.svfs.audit)
962 963
963 964 self._dirstatevalidatewarned = False
964 965
965 966 self._branchcaches = {}
966 967 self._revbranchcache = None
967 968 self._filterpats = {}
968 969 self._datafilters = {}
969 970 self._transref = self._lockref = self._wlockref = None
970 971
971 972 # A cache for various files under .hg/ that tracks file changes,
972 973 # (used by the filecache decorator)
973 974 #
974 975 # Maps a property name to its util.filecacheentry
975 976 self._filecache = {}
976 977
977 978 # hold sets of revision to be filtered
978 979 # should be cleared when something might have changed the filter value:
979 980 # - new changesets,
980 981 # - phase change,
981 982 # - new obsolescence marker,
982 983 # - working directory parent change,
983 984 # - bookmark changes
984 985 self.filteredrevcache = {}
985 986
986 987 # post-dirstate-status hooks
987 988 self._postdsstatus = []
988 989
989 990 # generic mapping between names and nodes
990 991 self.names = namespaces.namespaces()
991 992
992 993 # Key to signature value.
993 994 self._sparsesignaturecache = {}
994 995 # Signature to cached matcher instance.
995 996 self._sparsematchercache = {}
996 997
997 998 def _getvfsward(self, origfunc):
998 999 """build a ward for self.vfs"""
999 1000 rref = weakref.ref(self)
1000 1001 def checkvfs(path, mode=None):
1001 1002 ret = origfunc(path, mode=mode)
1002 1003 repo = rref()
1003 1004 if (repo is None
1004 1005 or not util.safehasattr(repo, '_wlockref')
1005 1006 or not util.safehasattr(repo, '_lockref')):
1006 1007 return
1007 1008 if mode in (None, 'r', 'rb'):
1008 1009 return
1009 1010 if path.startswith(repo.path):
1010 1011 # truncate name relative to the repository (.hg)
1011 1012 path = path[len(repo.path) + 1:]
1012 1013 if path.startswith('cache/'):
1013 1014 msg = 'accessing cache with vfs instead of cachevfs: "%s"'
1014 1015 repo.ui.develwarn(msg % path, stacklevel=2, config="cache-vfs")
1015 1016 if path.startswith('journal.'):
1016 1017 # journal is covered by 'lock'
1017 1018 if repo._currentlock(repo._lockref) is None:
1018 1019 repo.ui.develwarn('write with no lock: "%s"' % path,
1019 1020 stacklevel=2, config='check-locks')
1020 1021 elif repo._currentlock(repo._wlockref) is None:
1021 1022 # rest of vfs files are covered by 'wlock'
1022 1023 #
1023 1024 # exclude special files
1024 1025 for prefix in self._wlockfreeprefix:
1025 1026 if path.startswith(prefix):
1026 1027 return
1027 1028 repo.ui.develwarn('write with no wlock: "%s"' % path,
1028 1029 stacklevel=2, config='check-locks')
1029 1030 return ret
1030 1031 return checkvfs
1031 1032
1032 1033 def _getsvfsward(self, origfunc):
1033 1034 """build a ward for self.svfs"""
1034 1035 rref = weakref.ref(self)
1035 1036 def checksvfs(path, mode=None):
1036 1037 ret = origfunc(path, mode=mode)
1037 1038 repo = rref()
1038 1039 if repo is None or not util.safehasattr(repo, '_lockref'):
1039 1040 return
1040 1041 if mode in (None, 'r', 'rb'):
1041 1042 return
1042 1043 if path.startswith(repo.sharedpath):
1043 1044 # truncate name relative to the repository (.hg)
1044 1045 path = path[len(repo.sharedpath) + 1:]
1045 1046 if repo._currentlock(repo._lockref) is None:
1046 1047 repo.ui.develwarn('write with no lock: "%s"' % path,
1047 1048 stacklevel=3)
1048 1049 return ret
1049 1050 return checksvfs
1050 1051
1051 1052 def close(self):
1052 1053 self._writecaches()
1053 1054
1054 1055 def _writecaches(self):
1055 1056 if self._revbranchcache:
1056 1057 self._revbranchcache.write()
1057 1058
1058 1059 def _restrictcapabilities(self, caps):
1059 1060 if self.ui.configbool('experimental', 'bundle2-advertise'):
1060 1061 caps = set(caps)
1061 1062 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self,
1062 1063 role='client'))
1063 1064 caps.add('bundle2=' + urlreq.quote(capsblob))
1064 1065 return caps
1065 1066
1066 1067 def _writerequirements(self):
1067 1068 scmutil.writerequires(self.vfs, self.requirements)
1068 1069
1069 1070 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1070 1071 # self -> auditor -> self._checknested -> self
1071 1072
1072 1073 @property
1073 1074 def auditor(self):
1074 1075 # This is only used by context.workingctx.match in order to
1075 1076 # detect files in subrepos.
1076 1077 return pathutil.pathauditor(self.root, callback=self._checknested)
1077 1078
1078 1079 @property
1079 1080 def nofsauditor(self):
1080 1081 # This is only used by context.basectx.match in order to detect
1081 1082 # files in subrepos.
1082 1083 return pathutil.pathauditor(self.root, callback=self._checknested,
1083 1084 realfs=False, cached=True)
1084 1085
1085 1086 def _checknested(self, path):
1086 1087 """Determine if path is a legal nested repository."""
1087 1088 if not path.startswith(self.root):
1088 1089 return False
1089 1090 subpath = path[len(self.root) + 1:]
1090 1091 normsubpath = util.pconvert(subpath)
1091 1092
1092 1093 # XXX: Checking against the current working copy is wrong in
1093 1094 # the sense that it can reject things like
1094 1095 #
1095 1096 # $ hg cat -r 10 sub/x.txt
1096 1097 #
1097 1098 # if sub/ is no longer a subrepository in the working copy
1098 1099 # parent revision.
1099 1100 #
1100 1101 # However, it can of course also allow things that would have
1101 1102 # been rejected before, such as the above cat command if sub/
1102 1103 # is a subrepository now, but was a normal directory before.
1103 1104 # The old path auditor would have rejected by mistake since it
1104 1105 # panics when it sees sub/.hg/.
1105 1106 #
1106 1107 # All in all, checking against the working copy seems sensible
1107 1108 # since we want to prevent access to nested repositories on
1108 1109 # the filesystem *now*.
1109 1110 ctx = self[None]
1110 1111 parts = util.splitpath(subpath)
1111 1112 while parts:
1112 1113 prefix = '/'.join(parts)
1113 1114 if prefix in ctx.substate:
1114 1115 if prefix == normsubpath:
1115 1116 return True
1116 1117 else:
1117 1118 sub = ctx.sub(prefix)
1118 1119 return sub.checknested(subpath[len(prefix) + 1:])
1119 1120 else:
1120 1121 parts.pop()
1121 1122 return False
1122 1123
1123 1124 def peer(self):
1124 1125 return localpeer(self) # not cached to avoid reference cycle
1125 1126
1126 1127 def unfiltered(self):
1127 1128 """Return unfiltered version of the repository
1128 1129
1129 1130 Intended to be overwritten by filtered repo."""
1130 1131 return self
1131 1132
1132 1133 def filtered(self, name, visibilityexceptions=None):
1133 1134 """Return a filtered version of a repository"""
1134 1135 cls = repoview.newtype(self.unfiltered().__class__)
1135 1136 return cls(self, name, visibilityexceptions)
1136 1137
1137 1138 @repofilecache('bookmarks', 'bookmarks.current')
1138 1139 def _bookmarks(self):
1139 1140 return bookmarks.bmstore(self)
1140 1141
1141 1142 @property
1142 1143 def _activebookmark(self):
1143 1144 return self._bookmarks.active
1144 1145
1145 1146 # _phasesets depend on changelog. what we need is to call
1146 1147 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1147 1148 # can't be easily expressed in filecache mechanism.
1148 1149 @storecache('phaseroots', '00changelog.i')
1149 1150 def _phasecache(self):
1150 1151 return phases.phasecache(self, self._phasedefaults)
1151 1152
1152 1153 @storecache('obsstore')
1153 1154 def obsstore(self):
1154 1155 return obsolete.makestore(self.ui, self)
1155 1156
1156 1157 @storecache('00changelog.i')
1157 1158 def changelog(self):
1158 1159 return changelog.changelog(self.svfs,
1159 1160 trypending=txnutil.mayhavepending(self.root))
1160 1161
1161 1162 @storecache('00manifest.i')
1162 1163 def manifestlog(self):
1163 1164 rootstore = manifest.manifestrevlog(self.svfs)
1164 1165 return manifest.manifestlog(self.svfs, self, rootstore)
1165 1166
1166 1167 @repofilecache('dirstate')
1167 1168 def dirstate(self):
1168 1169 return self._makedirstate()
1169 1170
1170 1171 def _makedirstate(self):
1171 1172 """Extension point for wrapping the dirstate per-repo."""
1172 1173 sparsematchfn = lambda: sparse.matcher(self)
1173 1174
1174 1175 return dirstate.dirstate(self.vfs, self.ui, self.root,
1175 1176 self._dirstatevalidate, sparsematchfn)
1176 1177
1177 1178 def _dirstatevalidate(self, node):
1178 1179 try:
1179 1180 self.changelog.rev(node)
1180 1181 return node
1181 1182 except error.LookupError:
1182 1183 if not self._dirstatevalidatewarned:
1183 1184 self._dirstatevalidatewarned = True
1184 1185 self.ui.warn(_("warning: ignoring unknown"
1185 1186 " working parent %s!\n") % short(node))
1186 1187 return nullid
1187 1188
1188 1189 @storecache(narrowspec.FILENAME)
1189 1190 def narrowpats(self):
1190 1191 """matcher patterns for this repository's narrowspec
1191 1192
1192 1193 A tuple of (includes, excludes).
1193 1194 """
1194 1195 return narrowspec.load(self)
1195 1196
1196 1197 @storecache(narrowspec.FILENAME)
1197 1198 def _narrowmatch(self):
1198 1199 if repository.NARROW_REQUIREMENT not in self.requirements:
1199 1200 return matchmod.always(self.root, '')
1200 1201 include, exclude = self.narrowpats
1201 1202 return narrowspec.match(self.root, include=include, exclude=exclude)
1202 1203
1203 1204 def narrowmatch(self, match=None, includeexact=False):
1204 1205 """matcher corresponding the the repo's narrowspec
1205 1206
1206 1207 If `match` is given, then that will be intersected with the narrow
1207 1208 matcher.
1208 1209
1209 1210 If `includeexact` is True, then any exact matches from `match` will
1210 1211 be included even if they're outside the narrowspec.
1211 1212 """
1212 1213 if match:
1213 1214 if includeexact and not self._narrowmatch.always():
1214 1215 # do not exclude explicitly-specified paths so that they can
1215 1216 # be warned later on
1216 1217 em = matchmod.exact(match._root, match._cwd, match.files())
1217 1218 nm = matchmod.unionmatcher([self._narrowmatch, em])
1218 1219 return matchmod.intersectmatchers(match, nm)
1219 1220 return matchmod.intersectmatchers(match, self._narrowmatch)
1220 1221 return self._narrowmatch
1221 1222
1222 1223 def setnarrowpats(self, newincludes, newexcludes):
1223 1224 narrowspec.save(self, newincludes, newexcludes)
1224 1225 self.invalidate(clearfilecache=True)
1225 1226
1226 1227 def __getitem__(self, changeid):
1227 1228 if changeid is None:
1228 1229 return context.workingctx(self)
1229 1230 if isinstance(changeid, context.basectx):
1230 1231 return changeid
1231 1232 if isinstance(changeid, slice):
1232 1233 # wdirrev isn't contiguous so the slice shouldn't include it
1233 1234 return [self[i]
1234 1235 for i in pycompat.xrange(*changeid.indices(len(self)))
1235 1236 if i not in self.changelog.filteredrevs]
1236 1237 try:
1237 1238 if isinstance(changeid, int):
1238 1239 node = self.changelog.node(changeid)
1239 1240 rev = changeid
1240 1241 elif changeid == 'null':
1241 1242 node = nullid
1242 1243 rev = nullrev
1243 1244 elif changeid == 'tip':
1244 1245 node = self.changelog.tip()
1245 1246 rev = self.changelog.rev(node)
1246 1247 elif changeid == '.':
1247 1248 # this is a hack to delay/avoid loading obsmarkers
1248 1249 # when we know that '.' won't be hidden
1249 1250 node = self.dirstate.p1()
1250 1251 rev = self.unfiltered().changelog.rev(node)
1251 1252 elif len(changeid) == 20:
1252 1253 try:
1253 1254 node = changeid
1254 1255 rev = self.changelog.rev(changeid)
1255 1256 except error.FilteredLookupError:
1256 1257 changeid = hex(changeid) # for the error message
1257 1258 raise
1258 1259 except LookupError:
1259 1260 # check if it might have come from damaged dirstate
1260 1261 #
1261 1262 # XXX we could avoid the unfiltered if we had a recognizable
1262 1263 # exception for filtered changeset access
1263 1264 if (self.local()
1264 1265 and changeid in self.unfiltered().dirstate.parents()):
1265 1266 msg = _("working directory has unknown parent '%s'!")
1266 1267 raise error.Abort(msg % short(changeid))
1267 1268 changeid = hex(changeid) # for the error message
1268 1269 raise
1269 1270
1270 1271 elif len(changeid) == 40:
1271 1272 node = bin(changeid)
1272 1273 rev = self.changelog.rev(node)
1273 1274 else:
1274 1275 raise error.ProgrammingError(
1275 1276 "unsupported changeid '%s' of type %s" %
1276 1277 (changeid, type(changeid)))
1277 1278
1278 1279 return context.changectx(self, rev, node)
1279 1280
1280 1281 except (error.FilteredIndexError, error.FilteredLookupError):
1281 1282 raise error.FilteredRepoLookupError(_("filtered revision '%s'")
1282 1283 % pycompat.bytestr(changeid))
1283 1284 except (IndexError, LookupError):
1284 1285 raise error.RepoLookupError(_("unknown revision '%s'") % changeid)
1285 1286 except error.WdirUnsupported:
1286 1287 return context.workingctx(self)
1287 1288
1288 1289 def __contains__(self, changeid):
1289 1290 """True if the given changeid exists
1290 1291
1291 1292 error.AmbiguousPrefixLookupError is raised if an ambiguous node
1292 1293 specified.
1293 1294 """
1294 1295 try:
1295 1296 self[changeid]
1296 1297 return True
1297 1298 except error.RepoLookupError:
1298 1299 return False
1299 1300
1300 1301 def __nonzero__(self):
1301 1302 return True
1302 1303
1303 1304 __bool__ = __nonzero__
1304 1305
1305 1306 def __len__(self):
1306 1307 # no need to pay the cost of repoview.changelog
1307 1308 unfi = self.unfiltered()
1308 1309 return len(unfi.changelog)
1309 1310
1310 1311 def __iter__(self):
1311 1312 return iter(self.changelog)
1312 1313
1313 1314 def revs(self, expr, *args):
1314 1315 '''Find revisions matching a revset.
1315 1316
1316 1317 The revset is specified as a string ``expr`` that may contain
1317 1318 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1318 1319
1319 1320 Revset aliases from the configuration are not expanded. To expand
1320 1321 user aliases, consider calling ``scmutil.revrange()`` or
1321 1322 ``repo.anyrevs([expr], user=True)``.
1322 1323
1323 1324 Returns a revset.abstractsmartset, which is a list-like interface
1324 1325 that contains integer revisions.
1325 1326 '''
1326 1327 expr = revsetlang.formatspec(expr, *args)
1327 1328 m = revset.match(None, expr)
1328 1329 return m(self)
1329 1330
1330 1331 def set(self, expr, *args):
1331 1332 '''Find revisions matching a revset and emit changectx instances.
1332 1333
1333 1334 This is a convenience wrapper around ``revs()`` that iterates the
1334 1335 result and is a generator of changectx instances.
1335 1336
1336 1337 Revset aliases from the configuration are not expanded. To expand
1337 1338 user aliases, consider calling ``scmutil.revrange()``.
1338 1339 '''
1339 1340 for r in self.revs(expr, *args):
1340 1341 yield self[r]
1341 1342
1342 1343 def anyrevs(self, specs, user=False, localalias=None):
1343 1344 '''Find revisions matching one of the given revsets.
1344 1345
1345 1346 Revset aliases from the configuration are not expanded by default. To
1346 1347 expand user aliases, specify ``user=True``. To provide some local
1347 1348 definitions overriding user aliases, set ``localalias`` to
1348 1349 ``{name: definitionstring}``.
1349 1350 '''
1350 1351 if user:
1351 1352 m = revset.matchany(self.ui, specs,
1352 1353 lookup=revset.lookupfn(self),
1353 1354 localalias=localalias)
1354 1355 else:
1355 1356 m = revset.matchany(None, specs, localalias=localalias)
1356 1357 return m(self)
1357 1358
1358 1359 def url(self):
1359 1360 return 'file:' + self.root
1360 1361
1361 1362 def hook(self, name, throw=False, **args):
1362 1363 """Call a hook, passing this repo instance.
1363 1364
1364 1365 This a convenience method to aid invoking hooks. Extensions likely
1365 1366 won't call this unless they have registered a custom hook or are
1366 1367 replacing code that is expected to call a hook.
1367 1368 """
1368 1369 return hook.hook(self.ui, self, name, throw, **args)
1369 1370
1370 1371 @filteredpropertycache
1371 1372 def _tagscache(self):
1372 1373 '''Returns a tagscache object that contains various tags related
1373 1374 caches.'''
1374 1375
1375 1376 # This simplifies its cache management by having one decorated
1376 1377 # function (this one) and the rest simply fetch things from it.
1377 1378 class tagscache(object):
1378 1379 def __init__(self):
1379 1380 # These two define the set of tags for this repository. tags
1380 1381 # maps tag name to node; tagtypes maps tag name to 'global' or
1381 1382 # 'local'. (Global tags are defined by .hgtags across all
1382 1383 # heads, and local tags are defined in .hg/localtags.)
1383 1384 # They constitute the in-memory cache of tags.
1384 1385 self.tags = self.tagtypes = None
1385 1386
1386 1387 self.nodetagscache = self.tagslist = None
1387 1388
1388 1389 cache = tagscache()
1389 1390 cache.tags, cache.tagtypes = self._findtags()
1390 1391
1391 1392 return cache
1392 1393
1393 1394 def tags(self):
1394 1395 '''return a mapping of tag to node'''
1395 1396 t = {}
1396 1397 if self.changelog.filteredrevs:
1397 1398 tags, tt = self._findtags()
1398 1399 else:
1399 1400 tags = self._tagscache.tags
1400 1401 for k, v in tags.iteritems():
1401 1402 try:
1402 1403 # ignore tags to unknown nodes
1403 1404 self.changelog.rev(v)
1404 1405 t[k] = v
1405 1406 except (error.LookupError, ValueError):
1406 1407 pass
1407 1408 return t
1408 1409
1409 1410 def _findtags(self):
1410 1411 '''Do the hard work of finding tags. Return a pair of dicts
1411 1412 (tags, tagtypes) where tags maps tag name to node, and tagtypes
1412 1413 maps tag name to a string like \'global\' or \'local\'.
1413 1414 Subclasses or extensions are free to add their own tags, but
1414 1415 should be aware that the returned dicts will be retained for the
1415 1416 duration of the localrepo object.'''
1416 1417
1417 1418 # XXX what tagtype should subclasses/extensions use? Currently
1418 1419 # mq and bookmarks add tags, but do not set the tagtype at all.
1419 1420 # Should each extension invent its own tag type? Should there
1420 1421 # be one tagtype for all such "virtual" tags? Or is the status
1421 1422 # quo fine?
1422 1423
1423 1424
1424 1425 # map tag name to (node, hist)
1425 1426 alltags = tagsmod.findglobaltags(self.ui, self)
1426 1427 # map tag name to tag type
1427 1428 tagtypes = dict((tag, 'global') for tag in alltags)
1428 1429
1429 1430 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1430 1431
1431 1432 # Build the return dicts. Have to re-encode tag names because
1432 1433 # the tags module always uses UTF-8 (in order not to lose info
1433 1434 # writing to the cache), but the rest of Mercurial wants them in
1434 1435 # local encoding.
1435 1436 tags = {}
1436 1437 for (name, (node, hist)) in alltags.iteritems():
1437 1438 if node != nullid:
1438 1439 tags[encoding.tolocal(name)] = node
1439 1440 tags['tip'] = self.changelog.tip()
1440 1441 tagtypes = dict([(encoding.tolocal(name), value)
1441 1442 for (name, value) in tagtypes.iteritems()])
1442 1443 return (tags, tagtypes)
1443 1444
1444 1445 def tagtype(self, tagname):
1445 1446 '''
1446 1447 return the type of the given tag. result can be:
1447 1448
1448 1449 'local' : a local tag
1449 1450 'global' : a global tag
1450 1451 None : tag does not exist
1451 1452 '''
1452 1453
1453 1454 return self._tagscache.tagtypes.get(tagname)
1454 1455
1455 1456 def tagslist(self):
1456 1457 '''return a list of tags ordered by revision'''
1457 1458 if not self._tagscache.tagslist:
1458 1459 l = []
1459 1460 for t, n in self.tags().iteritems():
1460 1461 l.append((self.changelog.rev(n), t, n))
1461 1462 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1462 1463
1463 1464 return self._tagscache.tagslist
1464 1465
1465 1466 def nodetags(self, node):
1466 1467 '''return the tags associated with a node'''
1467 1468 if not self._tagscache.nodetagscache:
1468 1469 nodetagscache = {}
1469 1470 for t, n in self._tagscache.tags.iteritems():
1470 1471 nodetagscache.setdefault(n, []).append(t)
1471 1472 for tags in nodetagscache.itervalues():
1472 1473 tags.sort()
1473 1474 self._tagscache.nodetagscache = nodetagscache
1474 1475 return self._tagscache.nodetagscache.get(node, [])
1475 1476
1476 1477 def nodebookmarks(self, node):
1477 1478 """return the list of bookmarks pointing to the specified node"""
1478 1479 return self._bookmarks.names(node)
1479 1480
1480 1481 def branchmap(self):
1481 1482 '''returns a dictionary {branch: [branchheads]} with branchheads
1482 1483 ordered by increasing revision number'''
1483 1484 branchmap.updatecache(self)
1484 1485 return self._branchcaches[self.filtername]
1485 1486
1486 1487 @unfilteredmethod
1487 1488 def revbranchcache(self):
1488 1489 if not self._revbranchcache:
1489 1490 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1490 1491 return self._revbranchcache
1491 1492
1492 1493 def branchtip(self, branch, ignoremissing=False):
1493 1494 '''return the tip node for a given branch
1494 1495
1495 1496 If ignoremissing is True, then this method will not raise an error.
1496 1497 This is helpful for callers that only expect None for a missing branch
1497 1498 (e.g. namespace).
1498 1499
1499 1500 '''
1500 1501 try:
1501 1502 return self.branchmap().branchtip(branch)
1502 1503 except KeyError:
1503 1504 if not ignoremissing:
1504 1505 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
1505 1506 else:
1506 1507 pass
1507 1508
1508 1509 def lookup(self, key):
1509 1510 return scmutil.revsymbol(self, key).node()
1510 1511
1511 1512 def lookupbranch(self, key):
1512 1513 if key in self.branchmap():
1513 1514 return key
1514 1515
1515 1516 return scmutil.revsymbol(self, key).branch()
1516 1517
1517 1518 def known(self, nodes):
1518 1519 cl = self.changelog
1519 1520 nm = cl.nodemap
1520 1521 filtered = cl.filteredrevs
1521 1522 result = []
1522 1523 for n in nodes:
1523 1524 r = nm.get(n)
1524 1525 resp = not (r is None or r in filtered)
1525 1526 result.append(resp)
1526 1527 return result
1527 1528
1528 1529 def local(self):
1529 1530 return self
1530 1531
1531 1532 def publishing(self):
1532 1533 # it's safe (and desirable) to trust the publish flag unconditionally
1533 1534 # so that we don't finalize changes shared between users via ssh or nfs
1534 1535 return self.ui.configbool('phases', 'publish', untrusted=True)
1535 1536
1536 1537 def cancopy(self):
1537 1538 # so statichttprepo's override of local() works
1538 1539 if not self.local():
1539 1540 return False
1540 1541 if not self.publishing():
1541 1542 return True
1542 1543 # if publishing we can't copy if there is filtered content
1543 1544 return not self.filtered('visible').changelog.filteredrevs
1544 1545
1545 1546 def shared(self):
1546 1547 '''the type of shared repository (None if not shared)'''
1547 1548 if self.sharedpath != self.path:
1548 1549 return 'store'
1549 1550 return None
1550 1551
1551 1552 def wjoin(self, f, *insidef):
1552 1553 return self.vfs.reljoin(self.root, f, *insidef)
1553 1554
1554 1555 def setparents(self, p1, p2=nullid):
1555 1556 with self.dirstate.parentchange():
1556 1557 copies = self.dirstate.setparents(p1, p2)
1557 1558 pctx = self[p1]
1558 1559 if copies:
1559 1560 # Adjust copy records, the dirstate cannot do it, it
1560 1561 # requires access to parents manifests. Preserve them
1561 1562 # only for entries added to first parent.
1562 1563 for f in copies:
1563 1564 if f not in pctx and copies[f] in pctx:
1564 1565 self.dirstate.copy(copies[f], f)
1565 1566 if p2 == nullid:
1566 1567 for f, s in sorted(self.dirstate.copies().items()):
1567 1568 if f not in pctx and s not in pctx:
1568 1569 self.dirstate.copy(None, f)
1569 1570
1570 1571 def filectx(self, path, changeid=None, fileid=None, changectx=None):
1571 1572 """changeid can be a changeset revision, node, or tag.
1572 1573 fileid can be a file revision or node."""
1573 1574 return context.filectx(self, path, changeid, fileid,
1574 1575 changectx=changectx)
1575 1576
1576 1577 def getcwd(self):
1577 1578 return self.dirstate.getcwd()
1578 1579
1579 1580 def pathto(self, f, cwd=None):
1580 1581 return self.dirstate.pathto(f, cwd)
1581 1582
1582 1583 def _loadfilter(self, filter):
1583 1584 if filter not in self._filterpats:
1584 1585 l = []
1585 1586 for pat, cmd in self.ui.configitems(filter):
1586 1587 if cmd == '!':
1587 1588 continue
1588 1589 mf = matchmod.match(self.root, '', [pat])
1589 1590 fn = None
1590 1591 params = cmd
1591 1592 for name, filterfn in self._datafilters.iteritems():
1592 1593 if cmd.startswith(name):
1593 1594 fn = filterfn
1594 1595 params = cmd[len(name):].lstrip()
1595 1596 break
1596 1597 if not fn:
1597 1598 fn = lambda s, c, **kwargs: procutil.filter(s, c)
1598 1599 # Wrap old filters not supporting keyword arguments
1599 1600 if not pycompat.getargspec(fn)[2]:
1600 1601 oldfn = fn
1601 1602 fn = lambda s, c, **kwargs: oldfn(s, c)
1602 1603 l.append((mf, fn, params))
1603 1604 self._filterpats[filter] = l
1604 1605 return self._filterpats[filter]
1605 1606
1606 1607 def _filter(self, filterpats, filename, data):
1607 1608 for mf, fn, cmd in filterpats:
1608 1609 if mf(filename):
1609 1610 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
1610 1611 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
1611 1612 break
1612 1613
1613 1614 return data
1614 1615
1615 1616 @unfilteredpropertycache
1616 1617 def _encodefilterpats(self):
1617 1618 return self._loadfilter('encode')
1618 1619
1619 1620 @unfilteredpropertycache
1620 1621 def _decodefilterpats(self):
1621 1622 return self._loadfilter('decode')
1622 1623
1623 1624 def adddatafilter(self, name, filter):
1624 1625 self._datafilters[name] = filter
1625 1626
1626 1627 def wread(self, filename):
1627 1628 if self.wvfs.islink(filename):
1628 1629 data = self.wvfs.readlink(filename)
1629 1630 else:
1630 1631 data = self.wvfs.read(filename)
1631 1632 return self._filter(self._encodefilterpats, filename, data)
1632 1633
1633 1634 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
1634 1635 """write ``data`` into ``filename`` in the working directory
1635 1636
1636 1637 This returns length of written (maybe decoded) data.
1637 1638 """
1638 1639 data = self._filter(self._decodefilterpats, filename, data)
1639 1640 if 'l' in flags:
1640 1641 self.wvfs.symlink(data, filename)
1641 1642 else:
1642 1643 self.wvfs.write(filename, data, backgroundclose=backgroundclose,
1643 1644 **kwargs)
1644 1645 if 'x' in flags:
1645 1646 self.wvfs.setflags(filename, False, True)
1646 1647 else:
1647 1648 self.wvfs.setflags(filename, False, False)
1648 1649 return len(data)
1649 1650
1650 1651 def wwritedata(self, filename, data):
1651 1652 return self._filter(self._decodefilterpats, filename, data)
1652 1653
1653 1654 def currenttransaction(self):
1654 1655 """return the current transaction or None if non exists"""
1655 1656 if self._transref:
1656 1657 tr = self._transref()
1657 1658 else:
1658 1659 tr = None
1659 1660
1660 1661 if tr and tr.running():
1661 1662 return tr
1662 1663 return None
1663 1664
1664 1665 def transaction(self, desc, report=None):
1665 1666 if (self.ui.configbool('devel', 'all-warnings')
1666 1667 or self.ui.configbool('devel', 'check-locks')):
1667 1668 if self._currentlock(self._lockref) is None:
1668 1669 raise error.ProgrammingError('transaction requires locking')
1669 1670 tr = self.currenttransaction()
1670 1671 if tr is not None:
1671 1672 return tr.nest(name=desc)
1672 1673
1673 1674 # abort here if the journal already exists
1674 1675 if self.svfs.exists("journal"):
1675 1676 raise error.RepoError(
1676 1677 _("abandoned transaction found"),
1677 1678 hint=_("run 'hg recover' to clean up transaction"))
1678 1679
1679 1680 idbase = "%.40f#%f" % (random.random(), time.time())
1680 1681 ha = hex(hashlib.sha1(idbase).digest())
1681 1682 txnid = 'TXN:' + ha
1682 1683 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1683 1684
1684 1685 self._writejournal(desc)
1685 1686 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1686 1687 if report:
1687 1688 rp = report
1688 1689 else:
1689 1690 rp = self.ui.warn
1690 1691 vfsmap = {'plain': self.vfs, 'store': self.svfs} # root of .hg/
1691 1692 # we must avoid cyclic reference between repo and transaction.
1692 1693 reporef = weakref.ref(self)
1693 1694 # Code to track tag movement
1694 1695 #
1695 1696 # Since tags are all handled as file content, it is actually quite hard
1696 1697 # to track these movement from a code perspective. So we fallback to a
1697 1698 # tracking at the repository level. One could envision to track changes
1698 1699 # to the '.hgtags' file through changegroup apply but that fails to
1699 1700 # cope with case where transaction expose new heads without changegroup
1700 1701 # being involved (eg: phase movement).
1701 1702 #
1702 1703 # For now, We gate the feature behind a flag since this likely comes
1703 1704 # with performance impacts. The current code run more often than needed
1704 1705 # and do not use caches as much as it could. The current focus is on
1705 1706 # the behavior of the feature so we disable it by default. The flag
1706 1707 # will be removed when we are happy with the performance impact.
1707 1708 #
1708 1709 # Once this feature is no longer experimental move the following
1709 1710 # documentation to the appropriate help section:
1710 1711 #
1711 1712 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1712 1713 # tags (new or changed or deleted tags). In addition the details of
1713 1714 # these changes are made available in a file at:
1714 1715 # ``REPOROOT/.hg/changes/tags.changes``.
1715 1716 # Make sure you check for HG_TAG_MOVED before reading that file as it
1716 1717 # might exist from a previous transaction even if no tag were touched
1717 1718 # in this one. Changes are recorded in a line base format::
1718 1719 #
1719 1720 # <action> <hex-node> <tag-name>\n
1720 1721 #
1721 1722 # Actions are defined as follow:
1722 1723 # "-R": tag is removed,
1723 1724 # "+A": tag is added,
1724 1725 # "-M": tag is moved (old value),
1725 1726 # "+M": tag is moved (new value),
1726 1727 tracktags = lambda x: None
1727 1728 # experimental config: experimental.hook-track-tags
1728 1729 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags')
1729 1730 if desc != 'strip' and shouldtracktags:
1730 1731 oldheads = self.changelog.headrevs()
1731 1732 def tracktags(tr2):
1732 1733 repo = reporef()
1733 1734 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1734 1735 newheads = repo.changelog.headrevs()
1735 1736 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1736 1737 # notes: we compare lists here.
1737 1738 # As we do it only once buiding set would not be cheaper
1738 1739 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1739 1740 if changes:
1740 1741 tr2.hookargs['tag_moved'] = '1'
1741 1742 with repo.vfs('changes/tags.changes', 'w',
1742 1743 atomictemp=True) as changesfile:
1743 1744 # note: we do not register the file to the transaction
1744 1745 # because we needs it to still exist on the transaction
1745 1746 # is close (for txnclose hooks)
1746 1747 tagsmod.writediff(changesfile, changes)
1747 1748 def validate(tr2):
1748 1749 """will run pre-closing hooks"""
1749 1750 # XXX the transaction API is a bit lacking here so we take a hacky
1750 1751 # path for now
1751 1752 #
1752 1753 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1753 1754 # dict is copied before these run. In addition we needs the data
1754 1755 # available to in memory hooks too.
1755 1756 #
1756 1757 # Moreover, we also need to make sure this runs before txnclose
1757 1758 # hooks and there is no "pending" mechanism that would execute
1758 1759 # logic only if hooks are about to run.
1759 1760 #
1760 1761 # Fixing this limitation of the transaction is also needed to track
1761 1762 # other families of changes (bookmarks, phases, obsolescence).
1762 1763 #
1763 1764 # This will have to be fixed before we remove the experimental
1764 1765 # gating.
1765 1766 tracktags(tr2)
1766 1767 repo = reporef()
1767 1768 if repo.ui.configbool('experimental', 'single-head-per-branch'):
1768 1769 scmutil.enforcesinglehead(repo, tr2, desc)
1769 1770 if hook.hashook(repo.ui, 'pretxnclose-bookmark'):
1770 1771 for name, (old, new) in sorted(tr.changes['bookmarks'].items()):
1771 1772 args = tr.hookargs.copy()
1772 1773 args.update(bookmarks.preparehookargs(name, old, new))
1773 1774 repo.hook('pretxnclose-bookmark', throw=True,
1774 1775 txnname=desc,
1775 1776 **pycompat.strkwargs(args))
1776 1777 if hook.hashook(repo.ui, 'pretxnclose-phase'):
1777 1778 cl = repo.unfiltered().changelog
1778 1779 for rev, (old, new) in tr.changes['phases'].items():
1779 1780 args = tr.hookargs.copy()
1780 1781 node = hex(cl.node(rev))
1781 1782 args.update(phases.preparehookargs(node, old, new))
1782 1783 repo.hook('pretxnclose-phase', throw=True, txnname=desc,
1783 1784 **pycompat.strkwargs(args))
1784 1785
1785 1786 repo.hook('pretxnclose', throw=True,
1786 1787 txnname=desc, **pycompat.strkwargs(tr.hookargs))
1787 1788 def releasefn(tr, success):
1788 1789 repo = reporef()
1789 1790 if success:
1790 1791 # this should be explicitly invoked here, because
1791 1792 # in-memory changes aren't written out at closing
1792 1793 # transaction, if tr.addfilegenerator (via
1793 1794 # dirstate.write or so) isn't invoked while
1794 1795 # transaction running
1795 1796 repo.dirstate.write(None)
1796 1797 else:
1797 1798 # discard all changes (including ones already written
1798 1799 # out) in this transaction
1799 1800 narrowspec.restorebackup(self, 'journal.narrowspec')
1800 1801 repo.dirstate.restorebackup(None, 'journal.dirstate')
1801 1802
1802 1803 repo.invalidate(clearfilecache=True)
1803 1804
1804 1805 tr = transaction.transaction(rp, self.svfs, vfsmap,
1805 1806 "journal",
1806 1807 "undo",
1807 1808 aftertrans(renames),
1808 1809 self.store.createmode,
1809 1810 validator=validate,
1810 1811 releasefn=releasefn,
1811 1812 checkambigfiles=_cachedfiles,
1812 1813 name=desc)
1813 1814 tr.changes['origrepolen'] = len(self)
1814 1815 tr.changes['obsmarkers'] = set()
1815 1816 tr.changes['phases'] = {}
1816 1817 tr.changes['bookmarks'] = {}
1817 1818
1818 1819 tr.hookargs['txnid'] = txnid
1819 1820 # note: writing the fncache only during finalize mean that the file is
1820 1821 # outdated when running hooks. As fncache is used for streaming clone,
1821 1822 # this is not expected to break anything that happen during the hooks.
1822 1823 tr.addfinalize('flush-fncache', self.store.write)
1823 1824 def txnclosehook(tr2):
1824 1825 """To be run if transaction is successful, will schedule a hook run
1825 1826 """
1826 1827 # Don't reference tr2 in hook() so we don't hold a reference.
1827 1828 # This reduces memory consumption when there are multiple
1828 1829 # transactions per lock. This can likely go away if issue5045
1829 1830 # fixes the function accumulation.
1830 1831 hookargs = tr2.hookargs
1831 1832
1832 1833 def hookfunc():
1833 1834 repo = reporef()
1834 1835 if hook.hashook(repo.ui, 'txnclose-bookmark'):
1835 1836 bmchanges = sorted(tr.changes['bookmarks'].items())
1836 1837 for name, (old, new) in bmchanges:
1837 1838 args = tr.hookargs.copy()
1838 1839 args.update(bookmarks.preparehookargs(name, old, new))
1839 1840 repo.hook('txnclose-bookmark', throw=False,
1840 1841 txnname=desc, **pycompat.strkwargs(args))
1841 1842
1842 1843 if hook.hashook(repo.ui, 'txnclose-phase'):
1843 1844 cl = repo.unfiltered().changelog
1844 1845 phasemv = sorted(tr.changes['phases'].items())
1845 1846 for rev, (old, new) in phasemv:
1846 1847 args = tr.hookargs.copy()
1847 1848 node = hex(cl.node(rev))
1848 1849 args.update(phases.preparehookargs(node, old, new))
1849 1850 repo.hook('txnclose-phase', throw=False, txnname=desc,
1850 1851 **pycompat.strkwargs(args))
1851 1852
1852 1853 repo.hook('txnclose', throw=False, txnname=desc,
1853 1854 **pycompat.strkwargs(hookargs))
1854 1855 reporef()._afterlock(hookfunc)
1855 1856 tr.addfinalize('txnclose-hook', txnclosehook)
1856 1857 # Include a leading "-" to make it happen before the transaction summary
1857 1858 # reports registered via scmutil.registersummarycallback() whose names
1858 1859 # are 00-txnreport etc. That way, the caches will be warm when the
1859 1860 # callbacks run.
1860 1861 tr.addpostclose('-warm-cache', self._buildcacheupdater(tr))
1861 1862 def txnaborthook(tr2):
1862 1863 """To be run if transaction is aborted
1863 1864 """
1864 1865 reporef().hook('txnabort', throw=False, txnname=desc,
1865 1866 **pycompat.strkwargs(tr2.hookargs))
1866 1867 tr.addabort('txnabort-hook', txnaborthook)
1867 1868 # avoid eager cache invalidation. in-memory data should be identical
1868 1869 # to stored data if transaction has no error.
1869 1870 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1870 1871 self._transref = weakref.ref(tr)
1871 1872 scmutil.registersummarycallback(self, tr, desc)
1872 1873 return tr
1873 1874
1874 1875 def _journalfiles(self):
1875 1876 return ((self.svfs, 'journal'),
1876 1877 (self.vfs, 'journal.dirstate'),
1877 1878 (self.vfs, 'journal.branch'),
1878 1879 (self.vfs, 'journal.desc'),
1879 1880 (self.vfs, 'journal.bookmarks'),
1880 1881 (self.svfs, 'journal.phaseroots'))
1881 1882
1882 1883 def undofiles(self):
1883 1884 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1884 1885
1885 1886 @unfilteredmethod
1886 1887 def _writejournal(self, desc):
1887 1888 self.dirstate.savebackup(None, 'journal.dirstate')
1888 1889 narrowspec.savebackup(self, 'journal.narrowspec')
1889 1890 self.vfs.write("journal.branch",
1890 1891 encoding.fromlocal(self.dirstate.branch()))
1891 1892 self.vfs.write("journal.desc",
1892 1893 "%d\n%s\n" % (len(self), desc))
1893 1894 self.vfs.write("journal.bookmarks",
1894 1895 self.vfs.tryread("bookmarks"))
1895 1896 self.svfs.write("journal.phaseroots",
1896 1897 self.svfs.tryread("phaseroots"))
1897 1898
1898 1899 def recover(self):
1899 1900 with self.lock():
1900 1901 if self.svfs.exists("journal"):
1901 1902 self.ui.status(_("rolling back interrupted transaction\n"))
1902 1903 vfsmap = {'': self.svfs,
1903 1904 'plain': self.vfs,}
1904 1905 transaction.rollback(self.svfs, vfsmap, "journal",
1905 1906 self.ui.warn,
1906 1907 checkambigfiles=_cachedfiles)
1907 1908 self.invalidate()
1908 1909 return True
1909 1910 else:
1910 1911 self.ui.warn(_("no interrupted transaction available\n"))
1911 1912 return False
1912 1913
1913 1914 def rollback(self, dryrun=False, force=False):
1914 1915 wlock = lock = dsguard = None
1915 1916 try:
1916 1917 wlock = self.wlock()
1917 1918 lock = self.lock()
1918 1919 if self.svfs.exists("undo"):
1919 1920 dsguard = dirstateguard.dirstateguard(self, 'rollback')
1920 1921
1921 1922 return self._rollback(dryrun, force, dsguard)
1922 1923 else:
1923 1924 self.ui.warn(_("no rollback information available\n"))
1924 1925 return 1
1925 1926 finally:
1926 1927 release(dsguard, lock, wlock)
1927 1928
1928 1929 @unfilteredmethod # Until we get smarter cache management
1929 1930 def _rollback(self, dryrun, force, dsguard):
1930 1931 ui = self.ui
1931 1932 try:
1932 1933 args = self.vfs.read('undo.desc').splitlines()
1933 1934 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1934 1935 if len(args) >= 3:
1935 1936 detail = args[2]
1936 1937 oldtip = oldlen - 1
1937 1938
1938 1939 if detail and ui.verbose:
1939 1940 msg = (_('repository tip rolled back to revision %d'
1940 1941 ' (undo %s: %s)\n')
1941 1942 % (oldtip, desc, detail))
1942 1943 else:
1943 1944 msg = (_('repository tip rolled back to revision %d'
1944 1945 ' (undo %s)\n')
1945 1946 % (oldtip, desc))
1946 1947 except IOError:
1947 1948 msg = _('rolling back unknown transaction\n')
1948 1949 desc = None
1949 1950
1950 1951 if not force and self['.'] != self['tip'] and desc == 'commit':
1951 1952 raise error.Abort(
1952 1953 _('rollback of last commit while not checked out '
1953 1954 'may lose data'), hint=_('use -f to force'))
1954 1955
1955 1956 ui.status(msg)
1956 1957 if dryrun:
1957 1958 return 0
1958 1959
1959 1960 parents = self.dirstate.parents()
1960 1961 self.destroying()
1961 1962 vfsmap = {'plain': self.vfs, '': self.svfs}
1962 1963 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn,
1963 1964 checkambigfiles=_cachedfiles)
1964 1965 if self.vfs.exists('undo.bookmarks'):
1965 1966 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1966 1967 if self.svfs.exists('undo.phaseroots'):
1967 1968 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1968 1969 self.invalidate()
1969 1970
1970 1971 parentgone = (parents[0] not in self.changelog.nodemap or
1971 1972 parents[1] not in self.changelog.nodemap)
1972 1973 if parentgone:
1973 1974 # prevent dirstateguard from overwriting already restored one
1974 1975 dsguard.close()
1975 1976
1976 1977 narrowspec.restorebackup(self, 'undo.narrowspec')
1977 1978 self.dirstate.restorebackup(None, 'undo.dirstate')
1978 1979 try:
1979 1980 branch = self.vfs.read('undo.branch')
1980 1981 self.dirstate.setbranch(encoding.tolocal(branch))
1981 1982 except IOError:
1982 1983 ui.warn(_('named branch could not be reset: '
1983 1984 'current branch is still \'%s\'\n')
1984 1985 % self.dirstate.branch())
1985 1986
1986 1987 parents = tuple([p.rev() for p in self[None].parents()])
1987 1988 if len(parents) > 1:
1988 1989 ui.status(_('working directory now based on '
1989 1990 'revisions %d and %d\n') % parents)
1990 1991 else:
1991 1992 ui.status(_('working directory now based on '
1992 1993 'revision %d\n') % parents)
1993 1994 mergemod.mergestate.clean(self, self['.'].node())
1994 1995
1995 1996 # TODO: if we know which new heads may result from this rollback, pass
1996 1997 # them to destroy(), which will prevent the branchhead cache from being
1997 1998 # invalidated.
1998 1999 self.destroyed()
1999 2000 return 0
2000 2001
2001 2002 def _buildcacheupdater(self, newtransaction):
2002 2003 """called during transaction to build the callback updating cache
2003 2004
2004 2005 Lives on the repository to help extension who might want to augment
2005 2006 this logic. For this purpose, the created transaction is passed to the
2006 2007 method.
2007 2008 """
2008 2009 # we must avoid cyclic reference between repo and transaction.
2009 2010 reporef = weakref.ref(self)
2010 2011 def updater(tr):
2011 2012 repo = reporef()
2012 2013 repo.updatecaches(tr)
2013 2014 return updater
2014 2015
2015 2016 @unfilteredmethod
2016 2017 def updatecaches(self, tr=None, full=False):
2017 2018 """warm appropriate caches
2018 2019
2019 2020 If this function is called after a transaction closed. The transaction
2020 2021 will be available in the 'tr' argument. This can be used to selectively
2021 2022 update caches relevant to the changes in that transaction.
2022 2023
2023 2024 If 'full' is set, make sure all caches the function knows about have
2024 2025 up-to-date data. Even the ones usually loaded more lazily.
2025 2026 """
2026 2027 if tr is not None and tr.hookargs.get('source') == 'strip':
2027 2028 # During strip, many caches are invalid but
2028 2029 # later call to `destroyed` will refresh them.
2029 2030 return
2030 2031
2031 2032 if tr is None or tr.changes['origrepolen'] < len(self):
2032 2033 # updating the unfiltered branchmap should refresh all the others,
2033 2034 self.ui.debug('updating the branch cache\n')
2034 2035 branchmap.updatecache(self.filtered('served'))
2035 2036
2036 2037 if full:
2037 2038 rbc = self.revbranchcache()
2038 2039 for r in self.changelog:
2039 2040 rbc.branchinfo(r)
2040 2041 rbc.write()
2041 2042
2042 2043 # ensure the working copy parents are in the manifestfulltextcache
2043 2044 for ctx in self['.'].parents():
2044 2045 ctx.manifest() # accessing the manifest is enough
2045 2046
2046 2047 def invalidatecaches(self):
2047 2048
2048 2049 if '_tagscache' in vars(self):
2049 2050 # can't use delattr on proxy
2050 2051 del self.__dict__['_tagscache']
2051 2052
2052 2053 self.unfiltered()._branchcaches.clear()
2053 2054 self.invalidatevolatilesets()
2054 2055 self._sparsesignaturecache.clear()
2055 2056
2056 2057 def invalidatevolatilesets(self):
2057 2058 self.filteredrevcache.clear()
2058 2059 obsolete.clearobscaches(self)
2059 2060
2060 2061 def invalidatedirstate(self):
2061 2062 '''Invalidates the dirstate, causing the next call to dirstate
2062 2063 to check if it was modified since the last time it was read,
2063 2064 rereading it if it has.
2064 2065
2065 2066 This is different to dirstate.invalidate() that it doesn't always
2066 2067 rereads the dirstate. Use dirstate.invalidate() if you want to
2067 2068 explicitly read the dirstate again (i.e. restoring it to a previous
2068 2069 known good state).'''
2069 2070 if hasunfilteredcache(self, 'dirstate'):
2070 2071 for k in self.dirstate._filecache:
2071 2072 try:
2072 2073 delattr(self.dirstate, k)
2073 2074 except AttributeError:
2074 2075 pass
2075 2076 delattr(self.unfiltered(), 'dirstate')
2076 2077
2077 2078 def invalidate(self, clearfilecache=False):
2078 2079 '''Invalidates both store and non-store parts other than dirstate
2079 2080
2080 2081 If a transaction is running, invalidation of store is omitted,
2081 2082 because discarding in-memory changes might cause inconsistency
2082 2083 (e.g. incomplete fncache causes unintentional failure, but
2083 2084 redundant one doesn't).
2084 2085 '''
2085 2086 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2086 2087 for k in list(self._filecache.keys()):
2087 2088 # dirstate is invalidated separately in invalidatedirstate()
2088 2089 if k == 'dirstate':
2089 2090 continue
2090 2091 if (k == 'changelog' and
2091 2092 self.currenttransaction() and
2092 2093 self.changelog._delayed):
2093 2094 # The changelog object may store unwritten revisions. We don't
2094 2095 # want to lose them.
2095 2096 # TODO: Solve the problem instead of working around it.
2096 2097 continue
2097 2098
2098 2099 if clearfilecache:
2099 2100 del self._filecache[k]
2100 2101 try:
2101 2102 delattr(unfiltered, k)
2102 2103 except AttributeError:
2103 2104 pass
2104 2105 self.invalidatecaches()
2105 2106 if not self.currenttransaction():
2106 2107 # TODO: Changing contents of store outside transaction
2107 2108 # causes inconsistency. We should make in-memory store
2108 2109 # changes detectable, and abort if changed.
2109 2110 self.store.invalidatecaches()
2110 2111
2111 2112 def invalidateall(self):
2112 2113 '''Fully invalidates both store and non-store parts, causing the
2113 2114 subsequent operation to reread any outside changes.'''
2114 2115 # extension should hook this to invalidate its caches
2115 2116 self.invalidate()
2116 2117 self.invalidatedirstate()
2117 2118
2118 2119 @unfilteredmethod
2119 2120 def _refreshfilecachestats(self, tr):
2120 2121 """Reload stats of cached files so that they are flagged as valid"""
2121 2122 for k, ce in self._filecache.items():
2122 2123 k = pycompat.sysstr(k)
2123 2124 if k == r'dirstate' or k not in self.__dict__:
2124 2125 continue
2125 2126 ce.refresh()
2126 2127
2127 2128 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
2128 2129 inheritchecker=None, parentenvvar=None):
2129 2130 parentlock = None
2130 2131 # the contents of parentenvvar are used by the underlying lock to
2131 2132 # determine whether it can be inherited
2132 2133 if parentenvvar is not None:
2133 2134 parentlock = encoding.environ.get(parentenvvar)
2134 2135
2135 2136 timeout = 0
2136 2137 warntimeout = 0
2137 2138 if wait:
2138 2139 timeout = self.ui.configint("ui", "timeout")
2139 2140 warntimeout = self.ui.configint("ui", "timeout.warn")
2140 2141 # internal config: ui.signal-safe-lock
2141 2142 signalsafe = self.ui.configbool('ui', 'signal-safe-lock')
2142 2143
2143 2144 l = lockmod.trylock(self.ui, vfs, lockname, timeout, warntimeout,
2144 2145 releasefn=releasefn,
2145 2146 acquirefn=acquirefn, desc=desc,
2146 2147 inheritchecker=inheritchecker,
2147 2148 parentlock=parentlock,
2148 2149 signalsafe=signalsafe)
2149 2150 return l
2150 2151
2151 2152 def _afterlock(self, callback):
2152 2153 """add a callback to be run when the repository is fully unlocked
2153 2154
2154 2155 The callback will be executed when the outermost lock is released
2155 2156 (with wlock being higher level than 'lock')."""
2156 2157 for ref in (self._wlockref, self._lockref):
2157 2158 l = ref and ref()
2158 2159 if l and l.held:
2159 2160 l.postrelease.append(callback)
2160 2161 break
2161 2162 else: # no lock have been found.
2162 2163 callback()
2163 2164
2164 2165 def lock(self, wait=True):
2165 2166 '''Lock the repository store (.hg/store) and return a weak reference
2166 2167 to the lock. Use this before modifying the store (e.g. committing or
2167 2168 stripping). If you are opening a transaction, get a lock as well.)
2168 2169
2169 2170 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2170 2171 'wlock' first to avoid a dead-lock hazard.'''
2171 2172 l = self._currentlock(self._lockref)
2172 2173 if l is not None:
2173 2174 l.lock()
2174 2175 return l
2175 2176
2176 2177 l = self._lock(self.svfs, "lock", wait, None,
2177 2178 self.invalidate, _('repository %s') % self.origroot)
2178 2179 self._lockref = weakref.ref(l)
2179 2180 return l
2180 2181
2181 2182 def _wlockchecktransaction(self):
2182 2183 if self.currenttransaction() is not None:
2183 2184 raise error.LockInheritanceContractViolation(
2184 2185 'wlock cannot be inherited in the middle of a transaction')
2185 2186
2186 2187 def wlock(self, wait=True):
2187 2188 '''Lock the non-store parts of the repository (everything under
2188 2189 .hg except .hg/store) and return a weak reference to the lock.
2189 2190
2190 2191 Use this before modifying files in .hg.
2191 2192
2192 2193 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2193 2194 'wlock' first to avoid a dead-lock hazard.'''
2194 2195 l = self._wlockref and self._wlockref()
2195 2196 if l is not None and l.held:
2196 2197 l.lock()
2197 2198 return l
2198 2199
2199 2200 # We do not need to check for non-waiting lock acquisition. Such
2200 2201 # acquisition would not cause dead-lock as they would just fail.
2201 2202 if wait and (self.ui.configbool('devel', 'all-warnings')
2202 2203 or self.ui.configbool('devel', 'check-locks')):
2203 2204 if self._currentlock(self._lockref) is not None:
2204 2205 self.ui.develwarn('"wlock" acquired after "lock"')
2205 2206
2206 2207 def unlock():
2207 2208 if self.dirstate.pendingparentchange():
2208 2209 self.dirstate.invalidate()
2209 2210 else:
2210 2211 self.dirstate.write(None)
2211 2212
2212 2213 self._filecache['dirstate'].refresh()
2213 2214
2214 2215 l = self._lock(self.vfs, "wlock", wait, unlock,
2215 2216 self.invalidatedirstate, _('working directory of %s') %
2216 2217 self.origroot,
2217 2218 inheritchecker=self._wlockchecktransaction,
2218 2219 parentenvvar='HG_WLOCK_LOCKER')
2219 2220 self._wlockref = weakref.ref(l)
2220 2221 return l
2221 2222
2222 2223 def _currentlock(self, lockref):
2223 2224 """Returns the lock if it's held, or None if it's not."""
2224 2225 if lockref is None:
2225 2226 return None
2226 2227 l = lockref()
2227 2228 if l is None or not l.held:
2228 2229 return None
2229 2230 return l
2230 2231
2231 2232 def currentwlock(self):
2232 2233 """Returns the wlock if it's held, or None if it's not."""
2233 2234 return self._currentlock(self._wlockref)
2234 2235
2235 2236 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
2236 2237 """
2237 2238 commit an individual file as part of a larger transaction
2238 2239 """
2239 2240
2240 2241 fname = fctx.path()
2241 2242 fparent1 = manifest1.get(fname, nullid)
2242 2243 fparent2 = manifest2.get(fname, nullid)
2243 2244 if isinstance(fctx, context.filectx):
2244 2245 node = fctx.filenode()
2245 2246 if node in [fparent1, fparent2]:
2246 2247 self.ui.debug('reusing %s filelog entry\n' % fname)
2247 2248 if manifest1.flags(fname) != fctx.flags():
2248 2249 changelist.append(fname)
2249 2250 return node
2250 2251
2251 2252 flog = self.file(fname)
2252 2253 meta = {}
2253 2254 copy = fctx.renamed()
2254 2255 if copy and copy[0] != fname:
2255 2256 # Mark the new revision of this file as a copy of another
2256 2257 # file. This copy data will effectively act as a parent
2257 2258 # of this new revision. If this is a merge, the first
2258 2259 # parent will be the nullid (meaning "look up the copy data")
2259 2260 # and the second one will be the other parent. For example:
2260 2261 #
2261 2262 # 0 --- 1 --- 3 rev1 changes file foo
2262 2263 # \ / rev2 renames foo to bar and changes it
2263 2264 # \- 2 -/ rev3 should have bar with all changes and
2264 2265 # should record that bar descends from
2265 2266 # bar in rev2 and foo in rev1
2266 2267 #
2267 2268 # this allows this merge to succeed:
2268 2269 #
2269 2270 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
2270 2271 # \ / merging rev3 and rev4 should use bar@rev2
2271 2272 # \- 2 --- 4 as the merge base
2272 2273 #
2273 2274
2274 2275 cfname = copy[0]
2275 2276 crev = manifest1.get(cfname)
2276 2277 newfparent = fparent2
2277 2278
2278 2279 if manifest2: # branch merge
2279 2280 if fparent2 == nullid or crev is None: # copied on remote side
2280 2281 if cfname in manifest2:
2281 2282 crev = manifest2[cfname]
2282 2283 newfparent = fparent1
2283 2284
2284 2285 # Here, we used to search backwards through history to try to find
2285 2286 # where the file copy came from if the source of a copy was not in
2286 2287 # the parent directory. However, this doesn't actually make sense to
2287 2288 # do (what does a copy from something not in your working copy even
2288 2289 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
2289 2290 # the user that copy information was dropped, so if they didn't
2290 2291 # expect this outcome it can be fixed, but this is the correct
2291 2292 # behavior in this circumstance.
2292 2293
2293 2294 if crev:
2294 2295 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
2295 2296 meta["copy"] = cfname
2296 2297 meta["copyrev"] = hex(crev)
2297 2298 fparent1, fparent2 = nullid, newfparent
2298 2299 else:
2299 2300 self.ui.warn(_("warning: can't find ancestor for '%s' "
2300 2301 "copied from '%s'!\n") % (fname, cfname))
2301 2302
2302 2303 elif fparent1 == nullid:
2303 2304 fparent1, fparent2 = fparent2, nullid
2304 2305 elif fparent2 != nullid:
2305 2306 # is one parent an ancestor of the other?
2306 2307 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
2307 2308 if fparent1 in fparentancestors:
2308 2309 fparent1, fparent2 = fparent2, nullid
2309 2310 elif fparent2 in fparentancestors:
2310 2311 fparent2 = nullid
2311 2312
2312 2313 # is the file changed?
2313 2314 text = fctx.data()
2314 2315 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
2315 2316 changelist.append(fname)
2316 2317 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
2317 2318 # are just the flags changed during merge?
2318 2319 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
2319 2320 changelist.append(fname)
2320 2321
2321 2322 return fparent1
2322 2323
2323 2324 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
2324 2325 """check for commit arguments that aren't committable"""
2325 2326 if match.isexact() or match.prefix():
2326 2327 matched = set(status.modified + status.added + status.removed)
2327 2328
2328 2329 for f in match.files():
2329 2330 f = self.dirstate.normalize(f)
2330 2331 if f == '.' or f in matched or f in wctx.substate:
2331 2332 continue
2332 2333 if f in status.deleted:
2333 2334 fail(f, _('file not found!'))
2334 2335 if f in vdirs: # visited directory
2335 2336 d = f + '/'
2336 2337 for mf in matched:
2337 2338 if mf.startswith(d):
2338 2339 break
2339 2340 else:
2340 2341 fail(f, _("no match under directory!"))
2341 2342 elif f not in self.dirstate:
2342 2343 fail(f, _("file not tracked!"))
2343 2344
2344 2345 @unfilteredmethod
2345 2346 def commit(self, text="", user=None, date=None, match=None, force=False,
2346 2347 editor=False, extra=None):
2347 2348 """Add a new revision to current repository.
2348 2349
2349 2350 Revision information is gathered from the working directory,
2350 2351 match can be used to filter the committed files. If editor is
2351 2352 supplied, it is called to get a commit message.
2352 2353 """
2353 2354 if extra is None:
2354 2355 extra = {}
2355 2356
2356 2357 def fail(f, msg):
2357 2358 raise error.Abort('%s: %s' % (f, msg))
2358 2359
2359 2360 if not match:
2360 2361 match = matchmod.always(self.root, '')
2361 2362
2362 2363 if not force:
2363 2364 vdirs = []
2364 2365 match.explicitdir = vdirs.append
2365 2366 match.bad = fail
2366 2367
2367 2368 wlock = lock = tr = None
2368 2369 try:
2369 2370 wlock = self.wlock()
2370 2371 lock = self.lock() # for recent changelog (see issue4368)
2371 2372
2372 2373 wctx = self[None]
2373 2374 merge = len(wctx.parents()) > 1
2374 2375
2375 2376 if not force and merge and not match.always():
2376 2377 raise error.Abort(_('cannot partially commit a merge '
2377 2378 '(do not specify files or patterns)'))
2378 2379
2379 2380 status = self.status(match=match, clean=force)
2380 2381 if force:
2381 2382 status.modified.extend(status.clean) # mq may commit clean files
2382 2383
2383 2384 # check subrepos
2384 2385 subs, commitsubs, newstate = subrepoutil.precommit(
2385 2386 self.ui, wctx, status, match, force=force)
2386 2387
2387 2388 # make sure all explicit patterns are matched
2388 2389 if not force:
2389 2390 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
2390 2391
2391 2392 cctx = context.workingcommitctx(self, status,
2392 2393 text, user, date, extra)
2393 2394
2394 2395 # internal config: ui.allowemptycommit
2395 2396 allowemptycommit = (wctx.branch() != wctx.p1().branch()
2396 2397 or extra.get('close') or merge or cctx.files()
2397 2398 or self.ui.configbool('ui', 'allowemptycommit'))
2398 2399 if not allowemptycommit:
2399 2400 return None
2400 2401
2401 2402 if merge and cctx.deleted():
2402 2403 raise error.Abort(_("cannot commit merge with missing files"))
2403 2404
2404 2405 ms = mergemod.mergestate.read(self)
2405 2406 mergeutil.checkunresolved(ms)
2406 2407
2407 2408 if editor:
2408 2409 cctx._text = editor(self, cctx, subs)
2409 2410 edited = (text != cctx._text)
2410 2411
2411 2412 # Save commit message in case this transaction gets rolled back
2412 2413 # (e.g. by a pretxncommit hook). Leave the content alone on
2413 2414 # the assumption that the user will use the same editor again.
2414 2415 msgfn = self.savecommitmessage(cctx._text)
2415 2416
2416 2417 # commit subs and write new state
2417 2418 if subs:
2418 2419 for s in sorted(commitsubs):
2419 2420 sub = wctx.sub(s)
2420 2421 self.ui.status(_('committing subrepository %s\n') %
2421 2422 subrepoutil.subrelpath(sub))
2422 2423 sr = sub.commit(cctx._text, user, date)
2423 2424 newstate[s] = (newstate[s][0], sr)
2424 2425 subrepoutil.writestate(self, newstate)
2425 2426
2426 2427 p1, p2 = self.dirstate.parents()
2427 2428 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
2428 2429 try:
2429 2430 self.hook("precommit", throw=True, parent1=hookp1,
2430 2431 parent2=hookp2)
2431 2432 tr = self.transaction('commit')
2432 2433 ret = self.commitctx(cctx, True)
2433 2434 except: # re-raises
2434 2435 if edited:
2435 2436 self.ui.write(
2436 2437 _('note: commit message saved in %s\n') % msgfn)
2437 2438 raise
2438 2439 # update bookmarks, dirstate and mergestate
2439 2440 bookmarks.update(self, [p1, p2], ret)
2440 2441 cctx.markcommitted(ret)
2441 2442 ms.reset()
2442 2443 tr.close()
2443 2444
2444 2445 finally:
2445 2446 lockmod.release(tr, lock, wlock)
2446 2447
2447 2448 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
2448 2449 # hack for command that use a temporary commit (eg: histedit)
2449 2450 # temporary commit got stripped before hook release
2450 2451 if self.changelog.hasnode(ret):
2451 2452 self.hook("commit", node=node, parent1=parent1,
2452 2453 parent2=parent2)
2453 2454 self._afterlock(commithook)
2454 2455 return ret
2455 2456
2456 2457 @unfilteredmethod
2457 2458 def commitctx(self, ctx, error=False):
2458 2459 """Add a new revision to current repository.
2459 2460 Revision information is passed via the context argument.
2460 2461
2461 2462 ctx.files() should list all files involved in this commit, i.e.
2462 2463 modified/added/removed files. On merge, it may be wider than the
2463 2464 ctx.files() to be committed, since any file nodes derived directly
2464 2465 from p1 or p2 are excluded from the committed ctx.files().
2465 2466 """
2466 2467
2467 2468 tr = None
2468 2469 p1, p2 = ctx.p1(), ctx.p2()
2469 2470 user = ctx.user()
2470 2471
2471 2472 lock = self.lock()
2472 2473 try:
2473 2474 tr = self.transaction("commit")
2474 2475 trp = weakref.proxy(tr)
2475 2476
2476 2477 if ctx.manifestnode():
2477 2478 # reuse an existing manifest revision
2478 2479 self.ui.debug('reusing known manifest\n')
2479 2480 mn = ctx.manifestnode()
2480 2481 files = ctx.files()
2481 2482 elif ctx.files():
2482 2483 m1ctx = p1.manifestctx()
2483 2484 m2ctx = p2.manifestctx()
2484 2485 mctx = m1ctx.copy()
2485 2486
2486 2487 m = mctx.read()
2487 2488 m1 = m1ctx.read()
2488 2489 m2 = m2ctx.read()
2489 2490
2490 2491 # check in files
2491 2492 added = []
2492 2493 changed = []
2493 2494 removed = list(ctx.removed())
2494 2495 linkrev = len(self)
2495 2496 self.ui.note(_("committing files:\n"))
2496 2497 for f in sorted(ctx.modified() + ctx.added()):
2497 2498 self.ui.note(f + "\n")
2498 2499 try:
2499 2500 fctx = ctx[f]
2500 2501 if fctx is None:
2501 2502 removed.append(f)
2502 2503 else:
2503 2504 added.append(f)
2504 2505 m[f] = self._filecommit(fctx, m1, m2, linkrev,
2505 2506 trp, changed)
2506 2507 m.setflag(f, fctx.flags())
2507 2508 except OSError as inst:
2508 2509 self.ui.warn(_("trouble committing %s!\n") % f)
2509 2510 raise
2510 2511 except IOError as inst:
2511 2512 errcode = getattr(inst, 'errno', errno.ENOENT)
2512 2513 if error or errcode and errcode != errno.ENOENT:
2513 2514 self.ui.warn(_("trouble committing %s!\n") % f)
2514 2515 raise
2515 2516
2516 2517 # update manifest
2517 2518 removed = [f for f in sorted(removed) if f in m1 or f in m2]
2518 2519 drop = [f for f in removed if f in m]
2519 2520 for f in drop:
2520 2521 del m[f]
2521 2522 files = changed + removed
2522 2523 md = None
2523 2524 if not files:
2524 2525 # if no "files" actually changed in terms of the changelog,
2525 2526 # try hard to detect unmodified manifest entry so that the
2526 2527 # exact same commit can be reproduced later on convert.
2527 2528 md = m1.diff(m, scmutil.matchfiles(self, ctx.files()))
2528 2529 if not files and md:
2529 2530 self.ui.debug('not reusing manifest (no file change in '
2530 2531 'changelog, but manifest differs)\n')
2531 2532 if files or md:
2532 2533 self.ui.note(_("committing manifest\n"))
2533 2534 # we're using narrowmatch here since it's already applied at
2534 2535 # other stages (such as dirstate.walk), so we're already
2535 2536 # ignoring things outside of narrowspec in most cases. The
2536 2537 # one case where we might have files outside the narrowspec
2537 2538 # at this point is merges, and we already error out in the
2538 2539 # case where the merge has files outside of the narrowspec,
2539 2540 # so this is safe.
2540 2541 mn = mctx.write(trp, linkrev,
2541 2542 p1.manifestnode(), p2.manifestnode(),
2542 2543 added, drop, match=self.narrowmatch())
2543 2544 else:
2544 2545 self.ui.debug('reusing manifest form p1 (listed files '
2545 2546 'actually unchanged)\n')
2546 2547 mn = p1.manifestnode()
2547 2548 else:
2548 2549 self.ui.debug('reusing manifest from p1 (no file change)\n')
2549 2550 mn = p1.manifestnode()
2550 2551 files = []
2551 2552
2552 2553 # update changelog
2553 2554 self.ui.note(_("committing changelog\n"))
2554 2555 self.changelog.delayupdate(tr)
2555 2556 n = self.changelog.add(mn, files, ctx.description(),
2556 2557 trp, p1.node(), p2.node(),
2557 2558 user, ctx.date(), ctx.extra().copy())
2558 2559 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
2559 2560 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
2560 2561 parent2=xp2)
2561 2562 # set the new commit is proper phase
2562 2563 targetphase = subrepoutil.newcommitphase(self.ui, ctx)
2563 2564 if targetphase:
2564 2565 # retract boundary do not alter parent changeset.
2565 2566 # if a parent have higher the resulting phase will
2566 2567 # be compliant anyway
2567 2568 #
2568 2569 # if minimal phase was 0 we don't need to retract anything
2569 2570 phases.registernew(self, tr, targetphase, [n])
2570 2571 tr.close()
2571 2572 return n
2572 2573 finally:
2573 2574 if tr:
2574 2575 tr.release()
2575 2576 lock.release()
2576 2577
2577 2578 @unfilteredmethod
2578 2579 def destroying(self):
2579 2580 '''Inform the repository that nodes are about to be destroyed.
2580 2581 Intended for use by strip and rollback, so there's a common
2581 2582 place for anything that has to be done before destroying history.
2582 2583
2583 2584 This is mostly useful for saving state that is in memory and waiting
2584 2585 to be flushed when the current lock is released. Because a call to
2585 2586 destroyed is imminent, the repo will be invalidated causing those
2586 2587 changes to stay in memory (waiting for the next unlock), or vanish
2587 2588 completely.
2588 2589 '''
2589 2590 # When using the same lock to commit and strip, the phasecache is left
2590 2591 # dirty after committing. Then when we strip, the repo is invalidated,
2591 2592 # causing those changes to disappear.
2592 2593 if '_phasecache' in vars(self):
2593 2594 self._phasecache.write()
2594 2595
2595 2596 @unfilteredmethod
2596 2597 def destroyed(self):
2597 2598 '''Inform the repository that nodes have been destroyed.
2598 2599 Intended for use by strip and rollback, so there's a common
2599 2600 place for anything that has to be done after destroying history.
2600 2601 '''
2601 2602 # When one tries to:
2602 2603 # 1) destroy nodes thus calling this method (e.g. strip)
2603 2604 # 2) use phasecache somewhere (e.g. commit)
2604 2605 #
2605 2606 # then 2) will fail because the phasecache contains nodes that were
2606 2607 # removed. We can either remove phasecache from the filecache,
2607 2608 # causing it to reload next time it is accessed, or simply filter
2608 2609 # the removed nodes now and write the updated cache.
2609 2610 self._phasecache.filterunknown(self)
2610 2611 self._phasecache.write()
2611 2612
2612 2613 # refresh all repository caches
2613 2614 self.updatecaches()
2614 2615
2615 2616 # Ensure the persistent tag cache is updated. Doing it now
2616 2617 # means that the tag cache only has to worry about destroyed
2617 2618 # heads immediately after a strip/rollback. That in turn
2618 2619 # guarantees that "cachetip == currenttip" (comparing both rev
2619 2620 # and node) always means no nodes have been added or destroyed.
2620 2621
2621 2622 # XXX this is suboptimal when qrefresh'ing: we strip the current
2622 2623 # head, refresh the tag cache, then immediately add a new head.
2623 2624 # But I think doing it this way is necessary for the "instant
2624 2625 # tag cache retrieval" case to work.
2625 2626 self.invalidate()
2626 2627
2627 2628 def status(self, node1='.', node2=None, match=None,
2628 2629 ignored=False, clean=False, unknown=False,
2629 2630 listsubrepos=False):
2630 2631 '''a convenience method that calls node1.status(node2)'''
2631 2632 return self[node1].status(node2, match, ignored, clean, unknown,
2632 2633 listsubrepos)
2633 2634
2634 2635 def addpostdsstatus(self, ps):
2635 2636 """Add a callback to run within the wlock, at the point at which status
2636 2637 fixups happen.
2637 2638
2638 2639 On status completion, callback(wctx, status) will be called with the
2639 2640 wlock held, unless the dirstate has changed from underneath or the wlock
2640 2641 couldn't be grabbed.
2641 2642
2642 2643 Callbacks should not capture and use a cached copy of the dirstate --
2643 2644 it might change in the meanwhile. Instead, they should access the
2644 2645 dirstate via wctx.repo().dirstate.
2645 2646
2646 2647 This list is emptied out after each status run -- extensions should
2647 2648 make sure it adds to this list each time dirstate.status is called.
2648 2649 Extensions should also make sure they don't call this for statuses
2649 2650 that don't involve the dirstate.
2650 2651 """
2651 2652
2652 2653 # The list is located here for uniqueness reasons -- it is actually
2653 2654 # managed by the workingctx, but that isn't unique per-repo.
2654 2655 self._postdsstatus.append(ps)
2655 2656
2656 2657 def postdsstatus(self):
2657 2658 """Used by workingctx to get the list of post-dirstate-status hooks."""
2658 2659 return self._postdsstatus
2659 2660
2660 2661 def clearpostdsstatus(self):
2661 2662 """Used by workingctx to clear post-dirstate-status hooks."""
2662 2663 del self._postdsstatus[:]
2663 2664
2664 2665 def heads(self, start=None):
2665 2666 if start is None:
2666 2667 cl = self.changelog
2667 2668 headrevs = reversed(cl.headrevs())
2668 2669 return [cl.node(rev) for rev in headrevs]
2669 2670
2670 2671 heads = self.changelog.heads(start)
2671 2672 # sort the output in rev descending order
2672 2673 return sorted(heads, key=self.changelog.rev, reverse=True)
2673 2674
2674 2675 def branchheads(self, branch=None, start=None, closed=False):
2675 2676 '''return a (possibly filtered) list of heads for the given branch
2676 2677
2677 2678 Heads are returned in topological order, from newest to oldest.
2678 2679 If branch is None, use the dirstate branch.
2679 2680 If start is not None, return only heads reachable from start.
2680 2681 If closed is True, return heads that are marked as closed as well.
2681 2682 '''
2682 2683 if branch is None:
2683 2684 branch = self[None].branch()
2684 2685 branches = self.branchmap()
2685 2686 if branch not in branches:
2686 2687 return []
2687 2688 # the cache returns heads ordered lowest to highest
2688 2689 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
2689 2690 if start is not None:
2690 2691 # filter out the heads that cannot be reached from startrev
2691 2692 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
2692 2693 bheads = [h for h in bheads if h in fbheads]
2693 2694 return bheads
2694 2695
2695 2696 def branches(self, nodes):
2696 2697 if not nodes:
2697 2698 nodes = [self.changelog.tip()]
2698 2699 b = []
2699 2700 for n in nodes:
2700 2701 t = n
2701 2702 while True:
2702 2703 p = self.changelog.parents(n)
2703 2704 if p[1] != nullid or p[0] == nullid:
2704 2705 b.append((t, n, p[0], p[1]))
2705 2706 break
2706 2707 n = p[0]
2707 2708 return b
2708 2709
2709 2710 def between(self, pairs):
2710 2711 r = []
2711 2712
2712 2713 for top, bottom in pairs:
2713 2714 n, l, i = top, [], 0
2714 2715 f = 1
2715 2716
2716 2717 while n != bottom and n != nullid:
2717 2718 p = self.changelog.parents(n)[0]
2718 2719 if i == f:
2719 2720 l.append(n)
2720 2721 f = f * 2
2721 2722 n = p
2722 2723 i += 1
2723 2724
2724 2725 r.append(l)
2725 2726
2726 2727 return r
2727 2728
2728 2729 def checkpush(self, pushop):
2729 2730 """Extensions can override this function if additional checks have
2730 2731 to be performed before pushing, or call it if they override push
2731 2732 command.
2732 2733 """
2733 2734
2734 2735 @unfilteredpropertycache
2735 2736 def prepushoutgoinghooks(self):
2736 2737 """Return util.hooks consists of a pushop with repo, remote, outgoing
2737 2738 methods, which are called before pushing changesets.
2738 2739 """
2739 2740 return util.hooks()
2740 2741
2741 2742 def pushkey(self, namespace, key, old, new):
2742 2743 try:
2743 2744 tr = self.currenttransaction()
2744 2745 hookargs = {}
2745 2746 if tr is not None:
2746 2747 hookargs.update(tr.hookargs)
2747 2748 hookargs = pycompat.strkwargs(hookargs)
2748 2749 hookargs[r'namespace'] = namespace
2749 2750 hookargs[r'key'] = key
2750 2751 hookargs[r'old'] = old
2751 2752 hookargs[r'new'] = new
2752 2753 self.hook('prepushkey', throw=True, **hookargs)
2753 2754 except error.HookAbort as exc:
2754 2755 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
2755 2756 if exc.hint:
2756 2757 self.ui.write_err(_("(%s)\n") % exc.hint)
2757 2758 return False
2758 2759 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2759 2760 ret = pushkey.push(self, namespace, key, old, new)
2760 2761 def runhook():
2761 2762 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2762 2763 ret=ret)
2763 2764 self._afterlock(runhook)
2764 2765 return ret
2765 2766
2766 2767 def listkeys(self, namespace):
2767 2768 self.hook('prelistkeys', throw=True, namespace=namespace)
2768 2769 self.ui.debug('listing keys for "%s"\n' % namespace)
2769 2770 values = pushkey.list(self, namespace)
2770 2771 self.hook('listkeys', namespace=namespace, values=values)
2771 2772 return values
2772 2773
2773 2774 def debugwireargs(self, one, two, three=None, four=None, five=None):
2774 2775 '''used to test argument passing over the wire'''
2775 2776 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
2776 2777 pycompat.bytestr(four),
2777 2778 pycompat.bytestr(five))
2778 2779
2779 2780 def savecommitmessage(self, text):
2780 2781 fp = self.vfs('last-message.txt', 'wb')
2781 2782 try:
2782 2783 fp.write(text)
2783 2784 finally:
2784 2785 fp.close()
2785 2786 return self.pathto(fp.name[len(self.root) + 1:])
2786 2787
2787 2788 # used to avoid circular references so destructors work
2788 2789 def aftertrans(files):
2789 2790 renamefiles = [tuple(t) for t in files]
2790 2791 def a():
2791 2792 for vfs, src, dest in renamefiles:
2792 2793 # if src and dest refer to a same file, vfs.rename is a no-op,
2793 2794 # leaving both src and dest on disk. delete dest to make sure
2794 2795 # the rename couldn't be such a no-op.
2795 2796 vfs.tryunlink(dest)
2796 2797 try:
2797 2798 vfs.rename(src, dest)
2798 2799 except OSError: # journal file does not yet exist
2799 2800 pass
2800 2801 return a
2801 2802
2802 2803 def undoname(fn):
2803 2804 base, name = os.path.split(fn)
2804 2805 assert name.startswith('journal')
2805 2806 return os.path.join(base, name.replace('journal', 'undo', 1))
2806 2807
2807 2808 def instance(ui, path, create, intents=None, createopts=None):
2808 2809 localpath = util.urllocalpath(path)
2809 2810 if create:
2810 2811 createrepository(ui, localpath, createopts=createopts)
2811 2812
2812 2813 return makelocalrepository(ui, localpath, intents=intents)
2813 2814
2814 2815 def islocal(path):
2815 2816 return True
2816 2817
2817 2818 def defaultcreateopts(ui, createopts=None):
2818 2819 """Populate the default creation options for a repository.
2819 2820
2820 2821 A dictionary of explicitly requested creation options can be passed
2821 2822 in. Missing keys will be populated.
2822 2823 """
2823 2824 createopts = dict(createopts or {})
2824 2825
2825 2826 if 'backend' not in createopts:
2826 2827 # experimental config: storage.new-repo-backend
2827 2828 createopts['backend'] = ui.config('storage', 'new-repo-backend')
2828 2829
2829 2830 return createopts
2830 2831
2831 2832 def newreporequirements(ui, createopts):
2832 2833 """Determine the set of requirements for a new local repository.
2833 2834
2834 2835 Extensions can wrap this function to specify custom requirements for
2835 2836 new repositories.
2836 2837 """
2837 2838 # If the repo is being created from a shared repository, we copy
2838 2839 # its requirements.
2839 2840 if 'sharedrepo' in createopts:
2840 2841 requirements = set(createopts['sharedrepo'].requirements)
2841 2842 if createopts.get('sharedrelative'):
2842 2843 requirements.add('relshared')
2843 2844 else:
2844 2845 requirements.add('shared')
2845 2846
2846 2847 return requirements
2847 2848
2848 2849 if 'backend' not in createopts:
2849 2850 raise error.ProgrammingError('backend key not present in createopts; '
2850 2851 'was defaultcreateopts() called?')
2851 2852
2852 2853 if createopts['backend'] != 'revlogv1':
2853 2854 raise error.Abort(_('unable to determine repository requirements for '
2854 2855 'storage backend: %s') % createopts['backend'])
2855 2856
2856 2857 requirements = {'revlogv1'}
2857 2858 if ui.configbool('format', 'usestore'):
2858 2859 requirements.add('store')
2859 2860 if ui.configbool('format', 'usefncache'):
2860 2861 requirements.add('fncache')
2861 2862 if ui.configbool('format', 'dotencode'):
2862 2863 requirements.add('dotencode')
2863 2864
2864 2865 compengine = ui.config('experimental', 'format.compression')
2865 2866 if compengine not in util.compengines:
2866 2867 raise error.Abort(_('compression engine %s defined by '
2867 2868 'experimental.format.compression not available') %
2868 2869 compengine,
2869 2870 hint=_('run "hg debuginstall" to list available '
2870 2871 'compression engines'))
2871 2872
2872 2873 # zlib is the historical default and doesn't need an explicit requirement.
2873 2874 if compengine != 'zlib':
2874 2875 requirements.add('exp-compression-%s' % compengine)
2875 2876
2876 2877 if scmutil.gdinitconfig(ui):
2877 2878 requirements.add('generaldelta')
2878 2879 if ui.configbool('experimental', 'treemanifest'):
2879 2880 requirements.add('treemanifest')
2880 2881 # experimental config: format.sparse-revlog
2881 2882 if ui.configbool('format', 'sparse-revlog'):
2882 2883 requirements.add(SPARSEREVLOG_REQUIREMENT)
2883 2884
2884 2885 revlogv2 = ui.config('experimental', 'revlogv2')
2885 2886 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
2886 2887 requirements.remove('revlogv1')
2887 2888 # generaldelta is implied by revlogv2.
2888 2889 requirements.discard('generaldelta')
2889 2890 requirements.add(REVLOGV2_REQUIREMENT)
2890 2891 # experimental config: format.internal-phase
2891 2892 if ui.configbool('format', 'internal-phase'):
2892 2893 requirements.add('internal-phase')
2893 2894
2894 2895 if createopts.get('narrowfiles'):
2895 2896 requirements.add(repository.NARROW_REQUIREMENT)
2896 2897
2897 2898 return requirements
2898 2899
2899 2900 def filterknowncreateopts(ui, createopts):
2900 2901 """Filters a dict of repo creation options against options that are known.
2901 2902
2902 2903 Receives a dict of repo creation options and returns a dict of those
2903 2904 options that we don't know how to handle.
2904 2905
2905 2906 This function is called as part of repository creation. If the
2906 2907 returned dict contains any items, repository creation will not
2907 2908 be allowed, as it means there was a request to create a repository
2908 2909 with options not recognized by loaded code.
2909 2910
2910 2911 Extensions can wrap this function to filter out creation options
2911 2912 they know how to handle.
2912 2913 """
2913 2914 known = {
2914 2915 'backend',
2915 2916 'narrowfiles',
2916 2917 'sharedrepo',
2917 2918 'sharedrelative',
2918 2919 'shareditems',
2919 2920 }
2920 2921
2921 2922 return {k: v for k, v in createopts.items() if k not in known}
2922 2923
2923 2924 def createrepository(ui, path, createopts=None):
2924 2925 """Create a new repository in a vfs.
2925 2926
2926 2927 ``path`` path to the new repo's working directory.
2927 2928 ``createopts`` options for the new repository.
2928 2929
2929 2930 The following keys for ``createopts`` are recognized:
2930 2931
2931 2932 backend
2932 2933 The storage backend to use.
2933 2934 narrowfiles
2934 2935 Set up repository to support narrow file storage.
2935 2936 sharedrepo
2936 2937 Repository object from which storage should be shared.
2937 2938 sharedrelative
2938 2939 Boolean indicating if the path to the shared repo should be
2939 2940 stored as relative. By default, the pointer to the "parent" repo
2940 2941 is stored as an absolute path.
2941 2942 shareditems
2942 2943 Set of items to share to the new repository (in addition to storage).
2943 2944 """
2944 2945 createopts = defaultcreateopts(ui, createopts=createopts)
2945 2946
2946 2947 unknownopts = filterknowncreateopts(ui, createopts)
2947 2948
2948 2949 if not isinstance(unknownopts, dict):
2949 2950 raise error.ProgrammingError('filterknowncreateopts() did not return '
2950 2951 'a dict')
2951 2952
2952 2953 if unknownopts:
2953 2954 raise error.Abort(_('unable to create repository because of unknown '
2954 2955 'creation option: %s') %
2955 2956 ', '.join(sorted(unknownopts)),
2956 2957 hint=_('is a required extension not loaded?'))
2957 2958
2958 2959 requirements = newreporequirements(ui, createopts=createopts)
2959 2960
2960 2961 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
2961 2962
2962 2963 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
2963 2964 if hgvfs.exists():
2964 2965 raise error.RepoError(_('repository %s already exists') % path)
2965 2966
2966 2967 if 'sharedrepo' in createopts:
2967 2968 sharedpath = createopts['sharedrepo'].sharedpath
2968 2969
2969 2970 if createopts.get('sharedrelative'):
2970 2971 try:
2971 2972 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
2972 2973 except (IOError, ValueError) as e:
2973 2974 # ValueError is raised on Windows if the drive letters differ
2974 2975 # on each path.
2975 2976 raise error.Abort(_('cannot calculate relative path'),
2976 2977 hint=stringutil.forcebytestr(e))
2977 2978
2978 2979 if not wdirvfs.exists():
2979 2980 wdirvfs.makedirs()
2980 2981
2981 2982 hgvfs.makedir(notindexed=True)
2982 2983
2983 2984 if b'store' in requirements and 'sharedrepo' not in createopts:
2984 2985 hgvfs.mkdir(b'store')
2985 2986
2986 2987 # We create an invalid changelog outside the store so very old
2987 2988 # Mercurial versions (which didn't know about the requirements
2988 2989 # file) encounter an error on reading the changelog. This
2989 2990 # effectively locks out old clients and prevents them from
2990 2991 # mucking with a repo in an unknown format.
2991 2992 #
2992 2993 # The revlog header has version 2, which won't be recognized by
2993 2994 # such old clients.
2994 2995 hgvfs.append(b'00changelog.i',
2995 2996 b'\0\0\0\2 dummy changelog to prevent using the old repo '
2996 2997 b'layout')
2997 2998
2998 2999 scmutil.writerequires(hgvfs, requirements)
2999 3000
3000 3001 # Write out file telling readers where to find the shared store.
3001 3002 if 'sharedrepo' in createopts:
3002 3003 hgvfs.write(b'sharedpath', sharedpath)
3003 3004
3004 3005 if createopts.get('shareditems'):
3005 3006 shared = b'\n'.join(sorted(createopts['shareditems'])) + b'\n'
3006 3007 hgvfs.write(b'shared', shared)
3007 3008
3008 3009 def poisonrepository(repo):
3009 3010 """Poison a repository instance so it can no longer be used."""
3010 3011 # Perform any cleanup on the instance.
3011 3012 repo.close()
3012 3013
3013 3014 # Our strategy is to replace the type of the object with one that
3014 3015 # has all attribute lookups result in error.
3015 3016 #
3016 3017 # But we have to allow the close() method because some constructors
3017 3018 # of repos call close() on repo references.
3018 3019 class poisonedrepository(object):
3019 3020 def __getattribute__(self, item):
3020 3021 if item == r'close':
3021 3022 return object.__getattribute__(self, item)
3022 3023
3023 3024 raise error.ProgrammingError('repo instances should not be used '
3024 3025 'after unshare')
3025 3026
3026 3027 def close(self):
3027 3028 pass
3028 3029
3029 3030 # We may have a repoview, which intercepts __setattr__. So be sure
3030 3031 # we operate at the lowest level possible.
3031 3032 object.__setattr__(repo, r'__class__', poisonedrepository)
@@ -1,2549 +1,2556 b''
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 contextlib
18 18 import errno
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 nullhex,
28 28 nullid,
29 29 nullrev,
30 30 short,
31 31 wdirfilenodeids,
32 32 wdirhex,
33 33 wdirid,
34 34 wdirrev,
35 35 )
36 36 from .i18n import _
37 37 from .revlogutils.constants import (
38 38 FLAG_GENERALDELTA,
39 39 FLAG_INLINE_DATA,
40 40 REVIDX_DEFAULT_FLAGS,
41 41 REVIDX_ELLIPSIS,
42 42 REVIDX_EXTSTORED,
43 43 REVIDX_FLAGS_ORDER,
44 44 REVIDX_ISCENSORED,
45 45 REVIDX_KNOWN_FLAGS,
46 46 REVIDX_RAWTEXT_CHANGING_FLAGS,
47 47 REVLOGV0,
48 48 REVLOGV1,
49 49 REVLOGV1_FLAGS,
50 50 REVLOGV2,
51 51 REVLOGV2_FLAGS,
52 52 REVLOG_DEFAULT_FLAGS,
53 53 REVLOG_DEFAULT_FORMAT,
54 54 REVLOG_DEFAULT_VERSION,
55 55 )
56 56 from .thirdparty import (
57 57 attr,
58 58 )
59 59 from . import (
60 60 ancestor,
61 61 dagop,
62 62 error,
63 63 mdiff,
64 64 policy,
65 65 pycompat,
66 66 repository,
67 67 templatefilters,
68 68 util,
69 69 )
70 70 from .revlogutils import (
71 71 deltas as deltautil,
72 72 )
73 73 from .utils import (
74 74 interfaceutil,
75 75 storageutil,
76 76 stringutil,
77 77 )
78 78
79 79 # blanked usage of all the name to prevent pyflakes constraints
80 80 # We need these name available in the module for extensions.
81 81 REVLOGV0
82 82 REVLOGV1
83 83 REVLOGV2
84 84 FLAG_INLINE_DATA
85 85 FLAG_GENERALDELTA
86 86 REVLOG_DEFAULT_FLAGS
87 87 REVLOG_DEFAULT_FORMAT
88 88 REVLOG_DEFAULT_VERSION
89 89 REVLOGV1_FLAGS
90 90 REVLOGV2_FLAGS
91 91 REVIDX_ISCENSORED
92 92 REVIDX_ELLIPSIS
93 93 REVIDX_EXTSTORED
94 94 REVIDX_DEFAULT_FLAGS
95 95 REVIDX_FLAGS_ORDER
96 96 REVIDX_KNOWN_FLAGS
97 97 REVIDX_RAWTEXT_CHANGING_FLAGS
98 98
99 99 parsers = policy.importmod(r'parsers')
100 100
101 101 # Aliased for performance.
102 102 _zlibdecompress = zlib.decompress
103 103
104 104 # max size of revlog with inline data
105 105 _maxinline = 131072
106 106 _chunksize = 1048576
107 107
108 108 # Store flag processors (cf. 'addflagprocessor()' to register)
109 109 _flagprocessors = {
110 110 REVIDX_ISCENSORED: None,
111 111 }
112 112
113 113 # Flag processors for REVIDX_ELLIPSIS.
114 114 def ellipsisreadprocessor(rl, text):
115 115 return text, False
116 116
117 117 def ellipsiswriteprocessor(rl, text):
118 118 return text, False
119 119
120 120 def ellipsisrawprocessor(rl, text):
121 121 return False
122 122
123 123 ellipsisprocessor = (
124 124 ellipsisreadprocessor,
125 125 ellipsiswriteprocessor,
126 126 ellipsisrawprocessor,
127 127 )
128 128
129 129 def addflagprocessor(flag, processor):
130 130 """Register a flag processor on a revision data flag.
131 131
132 132 Invariant:
133 133 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER,
134 134 and REVIDX_RAWTEXT_CHANGING_FLAGS if they can alter rawtext.
135 135 - Only one flag processor can be registered on a specific flag.
136 136 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
137 137 following signatures:
138 138 - (read) f(self, rawtext) -> text, bool
139 139 - (write) f(self, text) -> rawtext, bool
140 140 - (raw) f(self, rawtext) -> bool
141 141 "text" is presented to the user. "rawtext" is stored in revlog data, not
142 142 directly visible to the user.
143 143 The boolean returned by these transforms is used to determine whether
144 144 the returned text can be used for hash integrity checking. For example,
145 145 if "write" returns False, then "text" is used to generate hash. If
146 146 "write" returns True, that basically means "rawtext" returned by "write"
147 147 should be used to generate hash. Usually, "write" and "read" return
148 148 different booleans. And "raw" returns a same boolean as "write".
149 149
150 150 Note: The 'raw' transform is used for changegroup generation and in some
151 151 debug commands. In this case the transform only indicates whether the
152 152 contents can be used for hash integrity checks.
153 153 """
154 _insertflagprocessor(flag, processor, _flagprocessors)
155
156 def _insertflagprocessor(flag, processor, flagprocessors):
154 157 if not flag & REVIDX_KNOWN_FLAGS:
155 158 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
156 159 raise error.ProgrammingError(msg)
157 160 if flag not in REVIDX_FLAGS_ORDER:
158 161 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
159 162 raise error.ProgrammingError(msg)
160 if flag in _flagprocessors:
163 if flag in flagprocessors:
161 164 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
162 165 raise error.Abort(msg)
163 _flagprocessors[flag] = processor
166 flagprocessors[flag] = processor
164 167
165 168 def getoffset(q):
166 169 return int(q >> 16)
167 170
168 171 def gettype(q):
169 172 return int(q & 0xFFFF)
170 173
171 174 def offset_type(offset, type):
172 175 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
173 176 raise ValueError('unknown revlog index flags')
174 177 return int(int(offset) << 16 | type)
175 178
176 179 @attr.s(slots=True, frozen=True)
177 180 class _revisioninfo(object):
178 181 """Information about a revision that allows building its fulltext
179 182 node: expected hash of the revision
180 183 p1, p2: parent revs of the revision
181 184 btext: built text cache consisting of a one-element list
182 185 cachedelta: (baserev, uncompressed_delta) or None
183 186 flags: flags associated to the revision storage
184 187
185 188 One of btext[0] or cachedelta must be set.
186 189 """
187 190 node = attr.ib()
188 191 p1 = attr.ib()
189 192 p2 = attr.ib()
190 193 btext = attr.ib()
191 194 textlen = attr.ib()
192 195 cachedelta = attr.ib()
193 196 flags = attr.ib()
194 197
195 198 @interfaceutil.implementer(repository.irevisiondelta)
196 199 @attr.s(slots=True)
197 200 class revlogrevisiondelta(object):
198 201 node = attr.ib()
199 202 p1node = attr.ib()
200 203 p2node = attr.ib()
201 204 basenode = attr.ib()
202 205 flags = attr.ib()
203 206 baserevisionsize = attr.ib()
204 207 revision = attr.ib()
205 208 delta = attr.ib()
206 209 linknode = attr.ib(default=None)
207 210
208 211 @interfaceutil.implementer(repository.iverifyproblem)
209 212 @attr.s(frozen=True)
210 213 class revlogproblem(object):
211 214 warning = attr.ib(default=None)
212 215 error = attr.ib(default=None)
213 216 node = attr.ib(default=None)
214 217
215 218 # index v0:
216 219 # 4 bytes: offset
217 220 # 4 bytes: compressed length
218 221 # 4 bytes: base rev
219 222 # 4 bytes: link rev
220 223 # 20 bytes: parent 1 nodeid
221 224 # 20 bytes: parent 2 nodeid
222 225 # 20 bytes: nodeid
223 226 indexformatv0 = struct.Struct(">4l20s20s20s")
224 227 indexformatv0_pack = indexformatv0.pack
225 228 indexformatv0_unpack = indexformatv0.unpack
226 229
227 230 class revlogoldindex(list):
228 231 def __getitem__(self, i):
229 232 if i == -1:
230 233 return (0, 0, 0, -1, -1, -1, -1, nullid)
231 234 return list.__getitem__(self, i)
232 235
233 236 class revlogoldio(object):
234 237 def __init__(self):
235 238 self.size = indexformatv0.size
236 239
237 240 def parseindex(self, data, inline):
238 241 s = self.size
239 242 index = []
240 243 nodemap = {nullid: nullrev}
241 244 n = off = 0
242 245 l = len(data)
243 246 while off + s <= l:
244 247 cur = data[off:off + s]
245 248 off += s
246 249 e = indexformatv0_unpack(cur)
247 250 # transform to revlogv1 format
248 251 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
249 252 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
250 253 index.append(e2)
251 254 nodemap[e[6]] = n
252 255 n += 1
253 256
254 257 return revlogoldindex(index), nodemap, None
255 258
256 259 def packentry(self, entry, node, version, rev):
257 260 if gettype(entry[0]):
258 261 raise error.RevlogError(_('index entry flags need revlog '
259 262 'version 1'))
260 263 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
261 264 node(entry[5]), node(entry[6]), entry[7])
262 265 return indexformatv0_pack(*e2)
263 266
264 267 # index ng:
265 268 # 6 bytes: offset
266 269 # 2 bytes: flags
267 270 # 4 bytes: compressed length
268 271 # 4 bytes: uncompressed length
269 272 # 4 bytes: base rev
270 273 # 4 bytes: link rev
271 274 # 4 bytes: parent 1 rev
272 275 # 4 bytes: parent 2 rev
273 276 # 32 bytes: nodeid
274 277 indexformatng = struct.Struct(">Qiiiiii20s12x")
275 278 indexformatng_pack = indexformatng.pack
276 279 versionformat = struct.Struct(">I")
277 280 versionformat_pack = versionformat.pack
278 281 versionformat_unpack = versionformat.unpack
279 282
280 283 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
281 284 # signed integer)
282 285 _maxentrysize = 0x7fffffff
283 286
284 287 class revlogio(object):
285 288 def __init__(self):
286 289 self.size = indexformatng.size
287 290
288 291 def parseindex(self, data, inline):
289 292 # call the C implementation to parse the index data
290 293 index, cache = parsers.parse_index2(data, inline)
291 294 return index, getattr(index, 'nodemap', None), cache
292 295
293 296 def packentry(self, entry, node, version, rev):
294 297 p = indexformatng_pack(*entry)
295 298 if rev == 0:
296 299 p = versionformat_pack(version) + p[4:]
297 300 return p
298 301
299 302 class revlog(object):
300 303 """
301 304 the underlying revision storage object
302 305
303 306 A revlog consists of two parts, an index and the revision data.
304 307
305 308 The index is a file with a fixed record size containing
306 309 information on each revision, including its nodeid (hash), the
307 310 nodeids of its parents, the position and offset of its data within
308 311 the data file, and the revision it's based on. Finally, each entry
309 312 contains a linkrev entry that can serve as a pointer to external
310 313 data.
311 314
312 315 The revision data itself is a linear collection of data chunks.
313 316 Each chunk represents a revision and is usually represented as a
314 317 delta against the previous chunk. To bound lookup time, runs of
315 318 deltas are limited to about 2 times the length of the original
316 319 version data. This makes retrieval of a version proportional to
317 320 its size, or O(1) relative to the number of revisions.
318 321
319 322 Both pieces of the revlog are written to in an append-only
320 323 fashion, which means we never need to rewrite a file to insert or
321 324 remove data, and can use some simple techniques to avoid the need
322 325 for locking while reading.
323 326
324 327 If checkambig, indexfile is opened with checkambig=True at
325 328 writing, to avoid file stat ambiguity.
326 329
327 330 If mmaplargeindex is True, and an mmapindexthreshold is set, the
328 331 index will be mmapped rather than read if it is larger than the
329 332 configured threshold.
330 333
331 334 If censorable is True, the revlog can have censored revisions.
332 335 """
333 336 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
334 337 mmaplargeindex=False, censorable=False):
335 338 """
336 339 create a revlog object
337 340
338 341 opener is a function that abstracts the file opening operation
339 342 and can be used to implement COW semantics or the like.
340 343 """
341 344 self.indexfile = indexfile
342 345 self.datafile = datafile or (indexfile[:-2] + ".d")
343 346 self.opener = opener
344 347 # When True, indexfile is opened with checkambig=True at writing, to
345 348 # avoid file stat ambiguity.
346 349 self._checkambig = checkambig
347 350 self._censorable = censorable
348 351 # 3-tuple of (node, rev, text) for a raw revision.
349 352 self._revisioncache = None
350 353 # Maps rev to chain base rev.
351 354 self._chainbasecache = util.lrucachedict(100)
352 355 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
353 356 self._chunkcache = (0, '')
354 357 # How much data to read and cache into the raw revlog data cache.
355 358 self._chunkcachesize = 65536
356 359 self._maxchainlen = None
357 360 self._deltabothparents = True
358 361 self.index = []
359 362 # Mapping of partial identifiers to full nodes.
360 363 self._pcache = {}
361 364 # Mapping of revision integer to full node.
362 365 self._nodecache = {nullid: nullrev}
363 366 self._nodepos = None
364 367 self._compengine = 'zlib'
365 368 self._maxdeltachainspan = -1
366 369 self._withsparseread = False
367 370 self._sparserevlog = False
368 371 self._srdensitythreshold = 0.50
369 372 self._srmingapsize = 262144
370 373
371 374 # Make copy of flag processors so each revlog instance can support
372 375 # custom flags.
373 376 self._flagprocessors = dict(_flagprocessors)
374 377
375 378 mmapindexthreshold = None
376 379 v = REVLOG_DEFAULT_VERSION
377 380 opts = getattr(opener, 'options', None)
378 381 if opts is not None:
379 382 if 'revlogv2' in opts:
380 383 # version 2 revlogs always use generaldelta.
381 384 v = REVLOGV2 | FLAG_GENERALDELTA | FLAG_INLINE_DATA
382 385 elif 'revlogv1' in opts:
383 386 if 'generaldelta' in opts:
384 387 v |= FLAG_GENERALDELTA
385 388 else:
386 389 v = 0
387 390 if 'chunkcachesize' in opts:
388 391 self._chunkcachesize = opts['chunkcachesize']
389 392 if 'maxchainlen' in opts:
390 393 self._maxchainlen = opts['maxchainlen']
391 394 if 'deltabothparents' in opts:
392 395 self._deltabothparents = opts['deltabothparents']
393 396 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
394 397 if 'compengine' in opts:
395 398 self._compengine = opts['compengine']
396 399 if 'maxdeltachainspan' in opts:
397 400 self._maxdeltachainspan = opts['maxdeltachainspan']
398 401 if mmaplargeindex and 'mmapindexthreshold' in opts:
399 402 mmapindexthreshold = opts['mmapindexthreshold']
400 403 self._sparserevlog = bool(opts.get('sparse-revlog', False))
401 404 withsparseread = bool(opts.get('with-sparse-read', False))
402 405 # sparse-revlog forces sparse-read
403 406 self._withsparseread = self._sparserevlog or withsparseread
404 407 if 'sparse-read-density-threshold' in opts:
405 408 self._srdensitythreshold = opts['sparse-read-density-threshold']
406 409 if 'sparse-read-min-gap-size' in opts:
407 410 self._srmingapsize = opts['sparse-read-min-gap-size']
408 411 if opts.get('enableellipsis'):
409 412 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
410 413
414 # revlog v0 doesn't have flag processors
415 for flag, processor in opts.get(b'flagprocessors', {}).iteritems():
416 _insertflagprocessor(flag, processor, self._flagprocessors)
417
411 418 if self._chunkcachesize <= 0:
412 419 raise error.RevlogError(_('revlog chunk cache size %r is not '
413 420 'greater than 0') % self._chunkcachesize)
414 421 elif self._chunkcachesize & (self._chunkcachesize - 1):
415 422 raise error.RevlogError(_('revlog chunk cache size %r is not a '
416 423 'power of 2') % self._chunkcachesize)
417 424
418 425 self._loadindex(v, mmapindexthreshold)
419 426
420 427 def _loadindex(self, v, mmapindexthreshold):
421 428 indexdata = ''
422 429 self._initempty = True
423 430 try:
424 431 with self._indexfp() as f:
425 432 if (mmapindexthreshold is not None and
426 433 self.opener.fstat(f).st_size >= mmapindexthreshold):
427 434 indexdata = util.buffer(util.mmapread(f))
428 435 else:
429 436 indexdata = f.read()
430 437 if len(indexdata) > 0:
431 438 v = versionformat_unpack(indexdata[:4])[0]
432 439 self._initempty = False
433 440 except IOError as inst:
434 441 if inst.errno != errno.ENOENT:
435 442 raise
436 443
437 444 self.version = v
438 445 self._inline = v & FLAG_INLINE_DATA
439 446 self._generaldelta = v & FLAG_GENERALDELTA
440 447 flags = v & ~0xFFFF
441 448 fmt = v & 0xFFFF
442 449 if fmt == REVLOGV0:
443 450 if flags:
444 451 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
445 452 'revlog %s') %
446 453 (flags >> 16, fmt, self.indexfile))
447 454 elif fmt == REVLOGV1:
448 455 if flags & ~REVLOGV1_FLAGS:
449 456 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
450 457 'revlog %s') %
451 458 (flags >> 16, fmt, self.indexfile))
452 459 elif fmt == REVLOGV2:
453 460 if flags & ~REVLOGV2_FLAGS:
454 461 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
455 462 'revlog %s') %
456 463 (flags >> 16, fmt, self.indexfile))
457 464 else:
458 465 raise error.RevlogError(_('unknown version (%d) in revlog %s') %
459 466 (fmt, self.indexfile))
460 467
461 468 self._storedeltachains = True
462 469
463 470 self._io = revlogio()
464 471 if self.version == REVLOGV0:
465 472 self._io = revlogoldio()
466 473 try:
467 474 d = self._io.parseindex(indexdata, self._inline)
468 475 except (ValueError, IndexError):
469 476 raise error.RevlogError(_("index %s is corrupted") %
470 477 self.indexfile)
471 478 self.index, nodemap, self._chunkcache = d
472 479 if nodemap is not None:
473 480 self.nodemap = self._nodecache = nodemap
474 481 if not self._chunkcache:
475 482 self._chunkclear()
476 483 # revnum -> (chain-length, sum-delta-length)
477 484 self._chaininfocache = {}
478 485 # revlog header -> revlog compressor
479 486 self._decompressors = {}
480 487
481 488 @util.propertycache
482 489 def _compressor(self):
483 490 return util.compengines[self._compengine].revlogcompressor()
484 491
485 492 def _indexfp(self, mode='r'):
486 493 """file object for the revlog's index file"""
487 494 args = {r'mode': mode}
488 495 if mode != 'r':
489 496 args[r'checkambig'] = self._checkambig
490 497 if mode == 'w':
491 498 args[r'atomictemp'] = True
492 499 return self.opener(self.indexfile, **args)
493 500
494 501 def _datafp(self, mode='r'):
495 502 """file object for the revlog's data file"""
496 503 return self.opener(self.datafile, mode=mode)
497 504
498 505 @contextlib.contextmanager
499 506 def _datareadfp(self, existingfp=None):
500 507 """file object suitable to read data"""
501 508 if existingfp is not None:
502 509 yield existingfp
503 510 else:
504 511 if self._inline:
505 512 func = self._indexfp
506 513 else:
507 514 func = self._datafp
508 515 with func() as fp:
509 516 yield fp
510 517
511 518 def tip(self):
512 519 return self.node(len(self.index) - 1)
513 520 def __contains__(self, rev):
514 521 return 0 <= rev < len(self)
515 522 def __len__(self):
516 523 return len(self.index)
517 524 def __iter__(self):
518 525 return iter(pycompat.xrange(len(self)))
519 526 def revs(self, start=0, stop=None):
520 527 """iterate over all rev in this revlog (from start to stop)"""
521 528 return storageutil.iterrevs(len(self), start=start, stop=stop)
522 529
523 530 @util.propertycache
524 531 def nodemap(self):
525 532 if self.index:
526 533 # populate mapping down to the initial node
527 534 node0 = self.index[0][7] # get around changelog filtering
528 535 self.rev(node0)
529 536 return self._nodecache
530 537
531 538 def hasnode(self, node):
532 539 try:
533 540 self.rev(node)
534 541 return True
535 542 except KeyError:
536 543 return False
537 544
538 545 def candelta(self, baserev, rev):
539 546 """whether two revisions (baserev, rev) can be delta-ed or not"""
540 547 # Disable delta if either rev requires a content-changing flag
541 548 # processor (ex. LFS). This is because such flag processor can alter
542 549 # the rawtext content that the delta will be based on, and two clients
543 550 # could have a same revlog node with different flags (i.e. different
544 551 # rawtext contents) and the delta could be incompatible.
545 552 if ((self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS)
546 553 or (self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS)):
547 554 return False
548 555 return True
549 556
550 557 def clearcaches(self):
551 558 self._revisioncache = None
552 559 self._chainbasecache.clear()
553 560 self._chunkcache = (0, '')
554 561 self._pcache = {}
555 562
556 563 try:
557 564 self._nodecache.clearcaches()
558 565 except AttributeError:
559 566 self._nodecache = {nullid: nullrev}
560 567 self._nodepos = None
561 568
562 569 def rev(self, node):
563 570 try:
564 571 return self._nodecache[node]
565 572 except TypeError:
566 573 raise
567 574 except error.RevlogError:
568 575 # parsers.c radix tree lookup failed
569 576 if node == wdirid or node in wdirfilenodeids:
570 577 raise error.WdirUnsupported
571 578 raise error.LookupError(node, self.indexfile, _('no node'))
572 579 except KeyError:
573 580 # pure python cache lookup failed
574 581 n = self._nodecache
575 582 i = self.index
576 583 p = self._nodepos
577 584 if p is None:
578 585 p = len(i) - 1
579 586 else:
580 587 assert p < len(i)
581 588 for r in pycompat.xrange(p, -1, -1):
582 589 v = i[r][7]
583 590 n[v] = r
584 591 if v == node:
585 592 self._nodepos = r - 1
586 593 return r
587 594 if node == wdirid or node in wdirfilenodeids:
588 595 raise error.WdirUnsupported
589 596 raise error.LookupError(node, self.indexfile, _('no node'))
590 597
591 598 # Accessors for index entries.
592 599
593 600 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
594 601 # are flags.
595 602 def start(self, rev):
596 603 return int(self.index[rev][0] >> 16)
597 604
598 605 def flags(self, rev):
599 606 return self.index[rev][0] & 0xFFFF
600 607
601 608 def length(self, rev):
602 609 return self.index[rev][1]
603 610
604 611 def rawsize(self, rev):
605 612 """return the length of the uncompressed text for a given revision"""
606 613 l = self.index[rev][2]
607 614 if l >= 0:
608 615 return l
609 616
610 617 t = self.revision(rev, raw=True)
611 618 return len(t)
612 619
613 620 def size(self, rev):
614 621 """length of non-raw text (processed by a "read" flag processor)"""
615 622 # fast path: if no "read" flag processor could change the content,
616 623 # size is rawsize. note: ELLIPSIS is known to not change the content.
617 624 flags = self.flags(rev)
618 625 if flags & (REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
619 626 return self.rawsize(rev)
620 627
621 628 return len(self.revision(rev, raw=False))
622 629
623 630 def chainbase(self, rev):
624 631 base = self._chainbasecache.get(rev)
625 632 if base is not None:
626 633 return base
627 634
628 635 index = self.index
629 636 iterrev = rev
630 637 base = index[iterrev][3]
631 638 while base != iterrev:
632 639 iterrev = base
633 640 base = index[iterrev][3]
634 641
635 642 self._chainbasecache[rev] = base
636 643 return base
637 644
638 645 def linkrev(self, rev):
639 646 return self.index[rev][4]
640 647
641 648 def parentrevs(self, rev):
642 649 try:
643 650 entry = self.index[rev]
644 651 except IndexError:
645 652 if rev == wdirrev:
646 653 raise error.WdirUnsupported
647 654 raise
648 655
649 656 return entry[5], entry[6]
650 657
651 658 # fast parentrevs(rev) where rev isn't filtered
652 659 _uncheckedparentrevs = parentrevs
653 660
654 661 def node(self, rev):
655 662 try:
656 663 return self.index[rev][7]
657 664 except IndexError:
658 665 if rev == wdirrev:
659 666 raise error.WdirUnsupported
660 667 raise
661 668
662 669 # Derived from index values.
663 670
664 671 def end(self, rev):
665 672 return self.start(rev) + self.length(rev)
666 673
667 674 def parents(self, node):
668 675 i = self.index
669 676 d = i[self.rev(node)]
670 677 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
671 678
672 679 def chainlen(self, rev):
673 680 return self._chaininfo(rev)[0]
674 681
675 682 def _chaininfo(self, rev):
676 683 chaininfocache = self._chaininfocache
677 684 if rev in chaininfocache:
678 685 return chaininfocache[rev]
679 686 index = self.index
680 687 generaldelta = self._generaldelta
681 688 iterrev = rev
682 689 e = index[iterrev]
683 690 clen = 0
684 691 compresseddeltalen = 0
685 692 while iterrev != e[3]:
686 693 clen += 1
687 694 compresseddeltalen += e[1]
688 695 if generaldelta:
689 696 iterrev = e[3]
690 697 else:
691 698 iterrev -= 1
692 699 if iterrev in chaininfocache:
693 700 t = chaininfocache[iterrev]
694 701 clen += t[0]
695 702 compresseddeltalen += t[1]
696 703 break
697 704 e = index[iterrev]
698 705 else:
699 706 # Add text length of base since decompressing that also takes
700 707 # work. For cache hits the length is already included.
701 708 compresseddeltalen += e[1]
702 709 r = (clen, compresseddeltalen)
703 710 chaininfocache[rev] = r
704 711 return r
705 712
706 713 def _deltachain(self, rev, stoprev=None):
707 714 """Obtain the delta chain for a revision.
708 715
709 716 ``stoprev`` specifies a revision to stop at. If not specified, we
710 717 stop at the base of the chain.
711 718
712 719 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
713 720 revs in ascending order and ``stopped`` is a bool indicating whether
714 721 ``stoprev`` was hit.
715 722 """
716 723 # Try C implementation.
717 724 try:
718 725 return self.index.deltachain(rev, stoprev, self._generaldelta)
719 726 except AttributeError:
720 727 pass
721 728
722 729 chain = []
723 730
724 731 # Alias to prevent attribute lookup in tight loop.
725 732 index = self.index
726 733 generaldelta = self._generaldelta
727 734
728 735 iterrev = rev
729 736 e = index[iterrev]
730 737 while iterrev != e[3] and iterrev != stoprev:
731 738 chain.append(iterrev)
732 739 if generaldelta:
733 740 iterrev = e[3]
734 741 else:
735 742 iterrev -= 1
736 743 e = index[iterrev]
737 744
738 745 if iterrev == stoprev:
739 746 stopped = True
740 747 else:
741 748 chain.append(iterrev)
742 749 stopped = False
743 750
744 751 chain.reverse()
745 752 return chain, stopped
746 753
747 754 def ancestors(self, revs, stoprev=0, inclusive=False):
748 755 """Generate the ancestors of 'revs' in reverse topological order.
749 756 Does not generate revs lower than stoprev.
750 757
751 758 See the documentation for ancestor.lazyancestors for more details."""
752 759
753 760 # first, make sure start revisions aren't filtered
754 761 revs = list(revs)
755 762 checkrev = self.node
756 763 for r in revs:
757 764 checkrev(r)
758 765 # and we're sure ancestors aren't filtered as well
759 766 return ancestor.lazyancestors(self._uncheckedparentrevs, revs,
760 767 stoprev=stoprev, inclusive=inclusive)
761 768
762 769 def descendants(self, revs):
763 770 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
764 771
765 772 def findcommonmissing(self, common=None, heads=None):
766 773 """Return a tuple of the ancestors of common and the ancestors of heads
767 774 that are not ancestors of common. In revset terminology, we return the
768 775 tuple:
769 776
770 777 ::common, (::heads) - (::common)
771 778
772 779 The list is sorted by revision number, meaning it is
773 780 topologically sorted.
774 781
775 782 'heads' and 'common' are both lists of node IDs. If heads is
776 783 not supplied, uses all of the revlog's heads. If common is not
777 784 supplied, uses nullid."""
778 785 if common is None:
779 786 common = [nullid]
780 787 if heads is None:
781 788 heads = self.heads()
782 789
783 790 common = [self.rev(n) for n in common]
784 791 heads = [self.rev(n) for n in heads]
785 792
786 793 # we want the ancestors, but inclusive
787 794 class lazyset(object):
788 795 def __init__(self, lazyvalues):
789 796 self.addedvalues = set()
790 797 self.lazyvalues = lazyvalues
791 798
792 799 def __contains__(self, value):
793 800 return value in self.addedvalues or value in self.lazyvalues
794 801
795 802 def __iter__(self):
796 803 added = self.addedvalues
797 804 for r in added:
798 805 yield r
799 806 for r in self.lazyvalues:
800 807 if not r in added:
801 808 yield r
802 809
803 810 def add(self, value):
804 811 self.addedvalues.add(value)
805 812
806 813 def update(self, values):
807 814 self.addedvalues.update(values)
808 815
809 816 has = lazyset(self.ancestors(common))
810 817 has.add(nullrev)
811 818 has.update(common)
812 819
813 820 # take all ancestors from heads that aren't in has
814 821 missing = set()
815 822 visit = collections.deque(r for r in heads if r not in has)
816 823 while visit:
817 824 r = visit.popleft()
818 825 if r in missing:
819 826 continue
820 827 else:
821 828 missing.add(r)
822 829 for p in self.parentrevs(r):
823 830 if p not in has:
824 831 visit.append(p)
825 832 missing = list(missing)
826 833 missing.sort()
827 834 return has, [self.node(miss) for miss in missing]
828 835
829 836 def incrementalmissingrevs(self, common=None):
830 837 """Return an object that can be used to incrementally compute the
831 838 revision numbers of the ancestors of arbitrary sets that are not
832 839 ancestors of common. This is an ancestor.incrementalmissingancestors
833 840 object.
834 841
835 842 'common' is a list of revision numbers. If common is not supplied, uses
836 843 nullrev.
837 844 """
838 845 if common is None:
839 846 common = [nullrev]
840 847
841 848 return ancestor.incrementalmissingancestors(self.parentrevs, common)
842 849
843 850 def findmissingrevs(self, common=None, heads=None):
844 851 """Return the revision numbers of the ancestors of heads that
845 852 are not ancestors of common.
846 853
847 854 More specifically, return a list of revision numbers corresponding to
848 855 nodes N such that every N satisfies the following constraints:
849 856
850 857 1. N is an ancestor of some node in 'heads'
851 858 2. N is not an ancestor of any node in 'common'
852 859
853 860 The list is sorted by revision number, meaning it is
854 861 topologically sorted.
855 862
856 863 'heads' and 'common' are both lists of revision numbers. If heads is
857 864 not supplied, uses all of the revlog's heads. If common is not
858 865 supplied, uses nullid."""
859 866 if common is None:
860 867 common = [nullrev]
861 868 if heads is None:
862 869 heads = self.headrevs()
863 870
864 871 inc = self.incrementalmissingrevs(common=common)
865 872 return inc.missingancestors(heads)
866 873
867 874 def findmissing(self, common=None, heads=None):
868 875 """Return the ancestors of heads that are not ancestors of common.
869 876
870 877 More specifically, return a list of nodes N such that every N
871 878 satisfies the following constraints:
872 879
873 880 1. N is an ancestor of some node in 'heads'
874 881 2. N is not an ancestor of any node in 'common'
875 882
876 883 The list is sorted by revision number, meaning it is
877 884 topologically sorted.
878 885
879 886 'heads' and 'common' are both lists of node IDs. If heads is
880 887 not supplied, uses all of the revlog's heads. If common is not
881 888 supplied, uses nullid."""
882 889 if common is None:
883 890 common = [nullid]
884 891 if heads is None:
885 892 heads = self.heads()
886 893
887 894 common = [self.rev(n) for n in common]
888 895 heads = [self.rev(n) for n in heads]
889 896
890 897 inc = self.incrementalmissingrevs(common=common)
891 898 return [self.node(r) for r in inc.missingancestors(heads)]
892 899
893 900 def nodesbetween(self, roots=None, heads=None):
894 901 """Return a topological path from 'roots' to 'heads'.
895 902
896 903 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
897 904 topologically sorted list of all nodes N that satisfy both of
898 905 these constraints:
899 906
900 907 1. N is a descendant of some node in 'roots'
901 908 2. N is an ancestor of some node in 'heads'
902 909
903 910 Every node is considered to be both a descendant and an ancestor
904 911 of itself, so every reachable node in 'roots' and 'heads' will be
905 912 included in 'nodes'.
906 913
907 914 'outroots' is the list of reachable nodes in 'roots', i.e., the
908 915 subset of 'roots' that is returned in 'nodes'. Likewise,
909 916 'outheads' is the subset of 'heads' that is also in 'nodes'.
910 917
911 918 'roots' and 'heads' are both lists of node IDs. If 'roots' is
912 919 unspecified, uses nullid as the only root. If 'heads' is
913 920 unspecified, uses list of all of the revlog's heads."""
914 921 nonodes = ([], [], [])
915 922 if roots is not None:
916 923 roots = list(roots)
917 924 if not roots:
918 925 return nonodes
919 926 lowestrev = min([self.rev(n) for n in roots])
920 927 else:
921 928 roots = [nullid] # Everybody's a descendant of nullid
922 929 lowestrev = nullrev
923 930 if (lowestrev == nullrev) and (heads is None):
924 931 # We want _all_ the nodes!
925 932 return ([self.node(r) for r in self], [nullid], list(self.heads()))
926 933 if heads is None:
927 934 # All nodes are ancestors, so the latest ancestor is the last
928 935 # node.
929 936 highestrev = len(self) - 1
930 937 # Set ancestors to None to signal that every node is an ancestor.
931 938 ancestors = None
932 939 # Set heads to an empty dictionary for later discovery of heads
933 940 heads = {}
934 941 else:
935 942 heads = list(heads)
936 943 if not heads:
937 944 return nonodes
938 945 ancestors = set()
939 946 # Turn heads into a dictionary so we can remove 'fake' heads.
940 947 # Also, later we will be using it to filter out the heads we can't
941 948 # find from roots.
942 949 heads = dict.fromkeys(heads, False)
943 950 # Start at the top and keep marking parents until we're done.
944 951 nodestotag = set(heads)
945 952 # Remember where the top was so we can use it as a limit later.
946 953 highestrev = max([self.rev(n) for n in nodestotag])
947 954 while nodestotag:
948 955 # grab a node to tag
949 956 n = nodestotag.pop()
950 957 # Never tag nullid
951 958 if n == nullid:
952 959 continue
953 960 # A node's revision number represents its place in a
954 961 # topologically sorted list of nodes.
955 962 r = self.rev(n)
956 963 if r >= lowestrev:
957 964 if n not in ancestors:
958 965 # If we are possibly a descendant of one of the roots
959 966 # and we haven't already been marked as an ancestor
960 967 ancestors.add(n) # Mark as ancestor
961 968 # Add non-nullid parents to list of nodes to tag.
962 969 nodestotag.update([p for p in self.parents(n) if
963 970 p != nullid])
964 971 elif n in heads: # We've seen it before, is it a fake head?
965 972 # So it is, real heads should not be the ancestors of
966 973 # any other heads.
967 974 heads.pop(n)
968 975 if not ancestors:
969 976 return nonodes
970 977 # Now that we have our set of ancestors, we want to remove any
971 978 # roots that are not ancestors.
972 979
973 980 # If one of the roots was nullid, everything is included anyway.
974 981 if lowestrev > nullrev:
975 982 # But, since we weren't, let's recompute the lowest rev to not
976 983 # include roots that aren't ancestors.
977 984
978 985 # Filter out roots that aren't ancestors of heads
979 986 roots = [root for root in roots if root in ancestors]
980 987 # Recompute the lowest revision
981 988 if roots:
982 989 lowestrev = min([self.rev(root) for root in roots])
983 990 else:
984 991 # No more roots? Return empty list
985 992 return nonodes
986 993 else:
987 994 # We are descending from nullid, and don't need to care about
988 995 # any other roots.
989 996 lowestrev = nullrev
990 997 roots = [nullid]
991 998 # Transform our roots list into a set.
992 999 descendants = set(roots)
993 1000 # Also, keep the original roots so we can filter out roots that aren't
994 1001 # 'real' roots (i.e. are descended from other roots).
995 1002 roots = descendants.copy()
996 1003 # Our topologically sorted list of output nodes.
997 1004 orderedout = []
998 1005 # Don't start at nullid since we don't want nullid in our output list,
999 1006 # and if nullid shows up in descendants, empty parents will look like
1000 1007 # they're descendants.
1001 1008 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1002 1009 n = self.node(r)
1003 1010 isdescendant = False
1004 1011 if lowestrev == nullrev: # Everybody is a descendant of nullid
1005 1012 isdescendant = True
1006 1013 elif n in descendants:
1007 1014 # n is already a descendant
1008 1015 isdescendant = True
1009 1016 # This check only needs to be done here because all the roots
1010 1017 # will start being marked is descendants before the loop.
1011 1018 if n in roots:
1012 1019 # If n was a root, check if it's a 'real' root.
1013 1020 p = tuple(self.parents(n))
1014 1021 # If any of its parents are descendants, it's not a root.
1015 1022 if (p[0] in descendants) or (p[1] in descendants):
1016 1023 roots.remove(n)
1017 1024 else:
1018 1025 p = tuple(self.parents(n))
1019 1026 # A node is a descendant if either of its parents are
1020 1027 # descendants. (We seeded the dependents list with the roots
1021 1028 # up there, remember?)
1022 1029 if (p[0] in descendants) or (p[1] in descendants):
1023 1030 descendants.add(n)
1024 1031 isdescendant = True
1025 1032 if isdescendant and ((ancestors is None) or (n in ancestors)):
1026 1033 # Only include nodes that are both descendants and ancestors.
1027 1034 orderedout.append(n)
1028 1035 if (ancestors is not None) and (n in heads):
1029 1036 # We're trying to figure out which heads are reachable
1030 1037 # from roots.
1031 1038 # Mark this head as having been reached
1032 1039 heads[n] = True
1033 1040 elif ancestors is None:
1034 1041 # Otherwise, we're trying to discover the heads.
1035 1042 # Assume this is a head because if it isn't, the next step
1036 1043 # will eventually remove it.
1037 1044 heads[n] = True
1038 1045 # But, obviously its parents aren't.
1039 1046 for p in self.parents(n):
1040 1047 heads.pop(p, None)
1041 1048 heads = [head for head, flag in heads.iteritems() if flag]
1042 1049 roots = list(roots)
1043 1050 assert orderedout
1044 1051 assert roots
1045 1052 assert heads
1046 1053 return (orderedout, roots, heads)
1047 1054
1048 1055 def headrevs(self):
1049 1056 try:
1050 1057 return self.index.headrevs()
1051 1058 except AttributeError:
1052 1059 return self._headrevs()
1053 1060
1054 1061 def computephases(self, roots):
1055 1062 return self.index.computephasesmapsets(roots)
1056 1063
1057 1064 def _headrevs(self):
1058 1065 count = len(self)
1059 1066 if not count:
1060 1067 return [nullrev]
1061 1068 # we won't iter over filtered rev so nobody is a head at start
1062 1069 ishead = [0] * (count + 1)
1063 1070 index = self.index
1064 1071 for r in self:
1065 1072 ishead[r] = 1 # I may be an head
1066 1073 e = index[r]
1067 1074 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1068 1075 return [r for r, val in enumerate(ishead) if val]
1069 1076
1070 1077 def heads(self, start=None, stop=None):
1071 1078 """return the list of all nodes that have no children
1072 1079
1073 1080 if start is specified, only heads that are descendants of
1074 1081 start will be returned
1075 1082 if stop is specified, it will consider all the revs from stop
1076 1083 as if they had no children
1077 1084 """
1078 1085 if start is None and stop is None:
1079 1086 if not len(self):
1080 1087 return [nullid]
1081 1088 return [self.node(r) for r in self.headrevs()]
1082 1089
1083 1090 if start is None:
1084 1091 start = nullrev
1085 1092 else:
1086 1093 start = self.rev(start)
1087 1094
1088 1095 stoprevs = set(self.rev(n) for n in stop or [])
1089 1096
1090 1097 revs = dagop.headrevssubset(self.revs, self.parentrevs, startrev=start,
1091 1098 stoprevs=stoprevs)
1092 1099
1093 1100 return [self.node(rev) for rev in revs]
1094 1101
1095 1102 def children(self, node):
1096 1103 """find the children of a given node"""
1097 1104 c = []
1098 1105 p = self.rev(node)
1099 1106 for r in self.revs(start=p + 1):
1100 1107 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1101 1108 if prevs:
1102 1109 for pr in prevs:
1103 1110 if pr == p:
1104 1111 c.append(self.node(r))
1105 1112 elif p == nullrev:
1106 1113 c.append(self.node(r))
1107 1114 return c
1108 1115
1109 1116 def commonancestorsheads(self, a, b):
1110 1117 """calculate all the heads of the common ancestors of nodes a and b"""
1111 1118 a, b = self.rev(a), self.rev(b)
1112 1119 ancs = self._commonancestorsheads(a, b)
1113 1120 return pycompat.maplist(self.node, ancs)
1114 1121
1115 1122 def _commonancestorsheads(self, *revs):
1116 1123 """calculate all the heads of the common ancestors of revs"""
1117 1124 try:
1118 1125 ancs = self.index.commonancestorsheads(*revs)
1119 1126 except (AttributeError, OverflowError): # C implementation failed
1120 1127 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1121 1128 return ancs
1122 1129
1123 1130 def isancestor(self, a, b):
1124 1131 """return True if node a is an ancestor of node b
1125 1132
1126 1133 A revision is considered an ancestor of itself."""
1127 1134 a, b = self.rev(a), self.rev(b)
1128 1135 return self.isancestorrev(a, b)
1129 1136
1130 1137 def isancestorrev(self, a, b):
1131 1138 """return True if revision a is an ancestor of revision b
1132 1139
1133 1140 A revision is considered an ancestor of itself.
1134 1141
1135 1142 The implementation of this is trivial but the use of
1136 1143 commonancestorsheads is not."""
1137 1144 if a == nullrev:
1138 1145 return True
1139 1146 elif a == b:
1140 1147 return True
1141 1148 elif a > b:
1142 1149 return False
1143 1150 return a in self._commonancestorsheads(a, b)
1144 1151
1145 1152 def ancestor(self, a, b):
1146 1153 """calculate the "best" common ancestor of nodes a and b"""
1147 1154
1148 1155 a, b = self.rev(a), self.rev(b)
1149 1156 try:
1150 1157 ancs = self.index.ancestors(a, b)
1151 1158 except (AttributeError, OverflowError):
1152 1159 ancs = ancestor.ancestors(self.parentrevs, a, b)
1153 1160 if ancs:
1154 1161 # choose a consistent winner when there's a tie
1155 1162 return min(map(self.node, ancs))
1156 1163 return nullid
1157 1164
1158 1165 def _match(self, id):
1159 1166 if isinstance(id, int):
1160 1167 # rev
1161 1168 return self.node(id)
1162 1169 if len(id) == 20:
1163 1170 # possibly a binary node
1164 1171 # odds of a binary node being all hex in ASCII are 1 in 10**25
1165 1172 try:
1166 1173 node = id
1167 1174 self.rev(node) # quick search the index
1168 1175 return node
1169 1176 except error.LookupError:
1170 1177 pass # may be partial hex id
1171 1178 try:
1172 1179 # str(rev)
1173 1180 rev = int(id)
1174 1181 if "%d" % rev != id:
1175 1182 raise ValueError
1176 1183 if rev < 0:
1177 1184 rev = len(self) + rev
1178 1185 if rev < 0 or rev >= len(self):
1179 1186 raise ValueError
1180 1187 return self.node(rev)
1181 1188 except (ValueError, OverflowError):
1182 1189 pass
1183 1190 if len(id) == 40:
1184 1191 try:
1185 1192 # a full hex nodeid?
1186 1193 node = bin(id)
1187 1194 self.rev(node)
1188 1195 return node
1189 1196 except (TypeError, error.LookupError):
1190 1197 pass
1191 1198
1192 1199 def _partialmatch(self, id):
1193 1200 # we don't care wdirfilenodeids as they should be always full hash
1194 1201 maybewdir = wdirhex.startswith(id)
1195 1202 try:
1196 1203 partial = self.index.partialmatch(id)
1197 1204 if partial and self.hasnode(partial):
1198 1205 if maybewdir:
1199 1206 # single 'ff...' match in radix tree, ambiguous with wdir
1200 1207 raise error.RevlogError
1201 1208 return partial
1202 1209 if maybewdir:
1203 1210 # no 'ff...' match in radix tree, wdir identified
1204 1211 raise error.WdirUnsupported
1205 1212 return None
1206 1213 except error.RevlogError:
1207 1214 # parsers.c radix tree lookup gave multiple matches
1208 1215 # fast path: for unfiltered changelog, radix tree is accurate
1209 1216 if not getattr(self, 'filteredrevs', None):
1210 1217 raise error.AmbiguousPrefixLookupError(
1211 1218 id, self.indexfile, _('ambiguous identifier'))
1212 1219 # fall through to slow path that filters hidden revisions
1213 1220 except (AttributeError, ValueError):
1214 1221 # we are pure python, or key was too short to search radix tree
1215 1222 pass
1216 1223
1217 1224 if id in self._pcache:
1218 1225 return self._pcache[id]
1219 1226
1220 1227 if len(id) <= 40:
1221 1228 try:
1222 1229 # hex(node)[:...]
1223 1230 l = len(id) // 2 # grab an even number of digits
1224 1231 prefix = bin(id[:l * 2])
1225 1232 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1226 1233 nl = [n for n in nl if hex(n).startswith(id) and
1227 1234 self.hasnode(n)]
1228 1235 if nullhex.startswith(id):
1229 1236 nl.append(nullid)
1230 1237 if len(nl) > 0:
1231 1238 if len(nl) == 1 and not maybewdir:
1232 1239 self._pcache[id] = nl[0]
1233 1240 return nl[0]
1234 1241 raise error.AmbiguousPrefixLookupError(
1235 1242 id, self.indexfile, _('ambiguous identifier'))
1236 1243 if maybewdir:
1237 1244 raise error.WdirUnsupported
1238 1245 return None
1239 1246 except TypeError:
1240 1247 pass
1241 1248
1242 1249 def lookup(self, id):
1243 1250 """locate a node based on:
1244 1251 - revision number or str(revision number)
1245 1252 - nodeid or subset of hex nodeid
1246 1253 """
1247 1254 n = self._match(id)
1248 1255 if n is not None:
1249 1256 return n
1250 1257 n = self._partialmatch(id)
1251 1258 if n:
1252 1259 return n
1253 1260
1254 1261 raise error.LookupError(id, self.indexfile, _('no match found'))
1255 1262
1256 1263 def shortest(self, node, minlength=1):
1257 1264 """Find the shortest unambiguous prefix that matches node."""
1258 1265 def isvalid(prefix):
1259 1266 try:
1260 1267 node = self._partialmatch(prefix)
1261 1268 except error.AmbiguousPrefixLookupError:
1262 1269 return False
1263 1270 except error.WdirUnsupported:
1264 1271 # single 'ff...' match
1265 1272 return True
1266 1273 if node is None:
1267 1274 raise error.LookupError(node, self.indexfile, _('no node'))
1268 1275 return True
1269 1276
1270 1277 def maybewdir(prefix):
1271 1278 return all(c == 'f' for c in prefix)
1272 1279
1273 1280 hexnode = hex(node)
1274 1281
1275 1282 def disambiguate(hexnode, minlength):
1276 1283 """Disambiguate against wdirid."""
1277 1284 for length in range(minlength, 41):
1278 1285 prefix = hexnode[:length]
1279 1286 if not maybewdir(prefix):
1280 1287 return prefix
1281 1288
1282 1289 if not getattr(self, 'filteredrevs', None):
1283 1290 try:
1284 1291 length = max(self.index.shortest(node), minlength)
1285 1292 return disambiguate(hexnode, length)
1286 1293 except error.RevlogError:
1287 1294 if node != wdirid:
1288 1295 raise error.LookupError(node, self.indexfile, _('no node'))
1289 1296 except AttributeError:
1290 1297 # Fall through to pure code
1291 1298 pass
1292 1299
1293 1300 if node == wdirid:
1294 1301 for length in range(minlength, 41):
1295 1302 prefix = hexnode[:length]
1296 1303 if isvalid(prefix):
1297 1304 return prefix
1298 1305
1299 1306 for length in range(minlength, 41):
1300 1307 prefix = hexnode[:length]
1301 1308 if isvalid(prefix):
1302 1309 return disambiguate(hexnode, length)
1303 1310
1304 1311 def cmp(self, node, text):
1305 1312 """compare text with a given file revision
1306 1313
1307 1314 returns True if text is different than what is stored.
1308 1315 """
1309 1316 p1, p2 = self.parents(node)
1310 1317 return storageutil.hashrevisionsha1(text, p1, p2) != node
1311 1318
1312 1319 def _cachesegment(self, offset, data):
1313 1320 """Add a segment to the revlog cache.
1314 1321
1315 1322 Accepts an absolute offset and the data that is at that location.
1316 1323 """
1317 1324 o, d = self._chunkcache
1318 1325 # try to add to existing cache
1319 1326 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1320 1327 self._chunkcache = o, d + data
1321 1328 else:
1322 1329 self._chunkcache = offset, data
1323 1330
1324 1331 def _readsegment(self, offset, length, df=None):
1325 1332 """Load a segment of raw data from the revlog.
1326 1333
1327 1334 Accepts an absolute offset, length to read, and an optional existing
1328 1335 file handle to read from.
1329 1336
1330 1337 If an existing file handle is passed, it will be seeked and the
1331 1338 original seek position will NOT be restored.
1332 1339
1333 1340 Returns a str or buffer of raw byte data.
1334 1341 """
1335 1342 # Cache data both forward and backward around the requested
1336 1343 # data, in a fixed size window. This helps speed up operations
1337 1344 # involving reading the revlog backwards.
1338 1345 cachesize = self._chunkcachesize
1339 1346 realoffset = offset & ~(cachesize - 1)
1340 1347 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1341 1348 - realoffset)
1342 1349 with self._datareadfp(df) as df:
1343 1350 df.seek(realoffset)
1344 1351 d = df.read(reallength)
1345 1352 self._cachesegment(realoffset, d)
1346 1353 if offset != realoffset or reallength != length:
1347 1354 return util.buffer(d, offset - realoffset, length)
1348 1355 return d
1349 1356
1350 1357 def _getsegment(self, offset, length, df=None):
1351 1358 """Obtain a segment of raw data from the revlog.
1352 1359
1353 1360 Accepts an absolute offset, length of bytes to obtain, and an
1354 1361 optional file handle to the already-opened revlog. If the file
1355 1362 handle is used, it's original seek position will not be preserved.
1356 1363
1357 1364 Requests for data may be returned from a cache.
1358 1365
1359 1366 Returns a str or a buffer instance of raw byte data.
1360 1367 """
1361 1368 o, d = self._chunkcache
1362 1369 l = len(d)
1363 1370
1364 1371 # is it in the cache?
1365 1372 cachestart = offset - o
1366 1373 cacheend = cachestart + length
1367 1374 if cachestart >= 0 and cacheend <= l:
1368 1375 if cachestart == 0 and cacheend == l:
1369 1376 return d # avoid a copy
1370 1377 return util.buffer(d, cachestart, cacheend - cachestart)
1371 1378
1372 1379 return self._readsegment(offset, length, df=df)
1373 1380
1374 1381 def _getsegmentforrevs(self, startrev, endrev, df=None):
1375 1382 """Obtain a segment of raw data corresponding to a range of revisions.
1376 1383
1377 1384 Accepts the start and end revisions and an optional already-open
1378 1385 file handle to be used for reading. If the file handle is read, its
1379 1386 seek position will not be preserved.
1380 1387
1381 1388 Requests for data may be satisfied by a cache.
1382 1389
1383 1390 Returns a 2-tuple of (offset, data) for the requested range of
1384 1391 revisions. Offset is the integer offset from the beginning of the
1385 1392 revlog and data is a str or buffer of the raw byte data.
1386 1393
1387 1394 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1388 1395 to determine where each revision's data begins and ends.
1389 1396 """
1390 1397 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1391 1398 # (functions are expensive).
1392 1399 index = self.index
1393 1400 istart = index[startrev]
1394 1401 start = int(istart[0] >> 16)
1395 1402 if startrev == endrev:
1396 1403 end = start + istart[1]
1397 1404 else:
1398 1405 iend = index[endrev]
1399 1406 end = int(iend[0] >> 16) + iend[1]
1400 1407
1401 1408 if self._inline:
1402 1409 start += (startrev + 1) * self._io.size
1403 1410 end += (endrev + 1) * self._io.size
1404 1411 length = end - start
1405 1412
1406 1413 return start, self._getsegment(start, length, df=df)
1407 1414
1408 1415 def _chunk(self, rev, df=None):
1409 1416 """Obtain a single decompressed chunk for a revision.
1410 1417
1411 1418 Accepts an integer revision and an optional already-open file handle
1412 1419 to be used for reading. If used, the seek position of the file will not
1413 1420 be preserved.
1414 1421
1415 1422 Returns a str holding uncompressed data for the requested revision.
1416 1423 """
1417 1424 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1418 1425
1419 1426 def _chunks(self, revs, df=None, targetsize=None):
1420 1427 """Obtain decompressed chunks for the specified revisions.
1421 1428
1422 1429 Accepts an iterable of numeric revisions that are assumed to be in
1423 1430 ascending order. Also accepts an optional already-open file handle
1424 1431 to be used for reading. If used, the seek position of the file will
1425 1432 not be preserved.
1426 1433
1427 1434 This function is similar to calling ``self._chunk()`` multiple times,
1428 1435 but is faster.
1429 1436
1430 1437 Returns a list with decompressed data for each requested revision.
1431 1438 """
1432 1439 if not revs:
1433 1440 return []
1434 1441 start = self.start
1435 1442 length = self.length
1436 1443 inline = self._inline
1437 1444 iosize = self._io.size
1438 1445 buffer = util.buffer
1439 1446
1440 1447 l = []
1441 1448 ladd = l.append
1442 1449
1443 1450 if not self._withsparseread:
1444 1451 slicedchunks = (revs,)
1445 1452 else:
1446 1453 slicedchunks = deltautil.slicechunk(self, revs,
1447 1454 targetsize=targetsize)
1448 1455
1449 1456 for revschunk in slicedchunks:
1450 1457 firstrev = revschunk[0]
1451 1458 # Skip trailing revisions with empty diff
1452 1459 for lastrev in revschunk[::-1]:
1453 1460 if length(lastrev) != 0:
1454 1461 break
1455 1462
1456 1463 try:
1457 1464 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1458 1465 except OverflowError:
1459 1466 # issue4215 - we can't cache a run of chunks greater than
1460 1467 # 2G on Windows
1461 1468 return [self._chunk(rev, df=df) for rev in revschunk]
1462 1469
1463 1470 decomp = self.decompress
1464 1471 for rev in revschunk:
1465 1472 chunkstart = start(rev)
1466 1473 if inline:
1467 1474 chunkstart += (rev + 1) * iosize
1468 1475 chunklength = length(rev)
1469 1476 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1470 1477
1471 1478 return l
1472 1479
1473 1480 def _chunkclear(self):
1474 1481 """Clear the raw chunk cache."""
1475 1482 self._chunkcache = (0, '')
1476 1483
1477 1484 def deltaparent(self, rev):
1478 1485 """return deltaparent of the given revision"""
1479 1486 base = self.index[rev][3]
1480 1487 if base == rev:
1481 1488 return nullrev
1482 1489 elif self._generaldelta:
1483 1490 return base
1484 1491 else:
1485 1492 return rev - 1
1486 1493
1487 1494 def issnapshot(self, rev):
1488 1495 """tells whether rev is a snapshot
1489 1496 """
1490 1497 if rev == nullrev:
1491 1498 return True
1492 1499 deltap = self.deltaparent(rev)
1493 1500 if deltap == nullrev:
1494 1501 return True
1495 1502 p1, p2 = self.parentrevs(rev)
1496 1503 if deltap in (p1, p2):
1497 1504 return False
1498 1505 return self.issnapshot(deltap)
1499 1506
1500 1507 def snapshotdepth(self, rev):
1501 1508 """number of snapshot in the chain before this one"""
1502 1509 if not self.issnapshot(rev):
1503 1510 raise error.ProgrammingError('revision %d not a snapshot')
1504 1511 return len(self._deltachain(rev)[0]) - 1
1505 1512
1506 1513 def revdiff(self, rev1, rev2):
1507 1514 """return or calculate a delta between two revisions
1508 1515
1509 1516 The delta calculated is in binary form and is intended to be written to
1510 1517 revlog data directly. So this function needs raw revision data.
1511 1518 """
1512 1519 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1513 1520 return bytes(self._chunk(rev2))
1514 1521
1515 1522 return mdiff.textdiff(self.revision(rev1, raw=True),
1516 1523 self.revision(rev2, raw=True))
1517 1524
1518 1525 def revision(self, nodeorrev, _df=None, raw=False):
1519 1526 """return an uncompressed revision of a given node or revision
1520 1527 number.
1521 1528
1522 1529 _df - an existing file handle to read from. (internal-only)
1523 1530 raw - an optional argument specifying if the revision data is to be
1524 1531 treated as raw data when applying flag transforms. 'raw' should be set
1525 1532 to True when generating changegroups or in debug commands.
1526 1533 """
1527 1534 if isinstance(nodeorrev, int):
1528 1535 rev = nodeorrev
1529 1536 node = self.node(rev)
1530 1537 else:
1531 1538 node = nodeorrev
1532 1539 rev = None
1533 1540
1534 1541 cachedrev = None
1535 1542 flags = None
1536 1543 rawtext = None
1537 1544 if node == nullid:
1538 1545 return ""
1539 1546 if self._revisioncache:
1540 1547 if self._revisioncache[0] == node:
1541 1548 # _cache only stores rawtext
1542 1549 if raw:
1543 1550 return self._revisioncache[2]
1544 1551 # duplicated, but good for perf
1545 1552 if rev is None:
1546 1553 rev = self.rev(node)
1547 1554 if flags is None:
1548 1555 flags = self.flags(rev)
1549 1556 # no extra flags set, no flag processor runs, text = rawtext
1550 1557 if flags == REVIDX_DEFAULT_FLAGS:
1551 1558 return self._revisioncache[2]
1552 1559 # rawtext is reusable. need to run flag processor
1553 1560 rawtext = self._revisioncache[2]
1554 1561
1555 1562 cachedrev = self._revisioncache[1]
1556 1563
1557 1564 # look up what we need to read
1558 1565 if rawtext is None:
1559 1566 if rev is None:
1560 1567 rev = self.rev(node)
1561 1568
1562 1569 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1563 1570 if stopped:
1564 1571 rawtext = self._revisioncache[2]
1565 1572
1566 1573 # drop cache to save memory
1567 1574 self._revisioncache = None
1568 1575
1569 1576 targetsize = None
1570 1577 rawsize = self.index[rev][2]
1571 1578 if 0 <= rawsize:
1572 1579 targetsize = 4 * rawsize
1573 1580
1574 1581 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1575 1582 if rawtext is None:
1576 1583 rawtext = bytes(bins[0])
1577 1584 bins = bins[1:]
1578 1585
1579 1586 rawtext = mdiff.patches(rawtext, bins)
1580 1587 self._revisioncache = (node, rev, rawtext)
1581 1588
1582 1589 if flags is None:
1583 1590 if rev is None:
1584 1591 rev = self.rev(node)
1585 1592 flags = self.flags(rev)
1586 1593
1587 1594 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
1588 1595 if validatehash:
1589 1596 self.checkhash(text, node, rev=rev)
1590 1597
1591 1598 return text
1592 1599
1593 1600 def hash(self, text, p1, p2):
1594 1601 """Compute a node hash.
1595 1602
1596 1603 Available as a function so that subclasses can replace the hash
1597 1604 as needed.
1598 1605 """
1599 1606 return storageutil.hashrevisionsha1(text, p1, p2)
1600 1607
1601 1608 def _processflags(self, text, flags, operation, raw=False):
1602 1609 """Inspect revision data flags and applies transforms defined by
1603 1610 registered flag processors.
1604 1611
1605 1612 ``text`` - the revision data to process
1606 1613 ``flags`` - the revision flags
1607 1614 ``operation`` - the operation being performed (read or write)
1608 1615 ``raw`` - an optional argument describing if the raw transform should be
1609 1616 applied.
1610 1617
1611 1618 This method processes the flags in the order (or reverse order if
1612 1619 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
1613 1620 flag processors registered for present flags. The order of flags defined
1614 1621 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
1615 1622
1616 1623 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
1617 1624 processed text and ``validatehash`` is a bool indicating whether the
1618 1625 returned text should be checked for hash integrity.
1619 1626
1620 1627 Note: If the ``raw`` argument is set, it has precedence over the
1621 1628 operation and will only update the value of ``validatehash``.
1622 1629 """
1623 1630 # fast path: no flag processors will run
1624 1631 if flags == 0:
1625 1632 return text, True
1626 1633 if not operation in ('read', 'write'):
1627 1634 raise error.ProgrammingError(_("invalid '%s' operation") %
1628 1635 operation)
1629 1636 # Check all flags are known.
1630 1637 if flags & ~REVIDX_KNOWN_FLAGS:
1631 1638 raise error.RevlogError(_("incompatible revision flag '%#x'") %
1632 1639 (flags & ~REVIDX_KNOWN_FLAGS))
1633 1640 validatehash = True
1634 1641 # Depending on the operation (read or write), the order might be
1635 1642 # reversed due to non-commutative transforms.
1636 1643 orderedflags = REVIDX_FLAGS_ORDER
1637 1644 if operation == 'write':
1638 1645 orderedflags = reversed(orderedflags)
1639 1646
1640 1647 for flag in orderedflags:
1641 1648 # If a flagprocessor has been registered for a known flag, apply the
1642 1649 # related operation transform and update result tuple.
1643 1650 if flag & flags:
1644 1651 vhash = True
1645 1652
1646 1653 if flag not in self._flagprocessors:
1647 1654 message = _("missing processor for flag '%#x'") % (flag)
1648 1655 raise error.RevlogError(message)
1649 1656
1650 1657 processor = self._flagprocessors[flag]
1651 1658 if processor is not None:
1652 1659 readtransform, writetransform, rawtransform = processor
1653 1660
1654 1661 if raw:
1655 1662 vhash = rawtransform(self, text)
1656 1663 elif operation == 'read':
1657 1664 text, vhash = readtransform(self, text)
1658 1665 else: # write operation
1659 1666 text, vhash = writetransform(self, text)
1660 1667 validatehash = validatehash and vhash
1661 1668
1662 1669 return text, validatehash
1663 1670
1664 1671 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1665 1672 """Check node hash integrity.
1666 1673
1667 1674 Available as a function so that subclasses can extend hash mismatch
1668 1675 behaviors as needed.
1669 1676 """
1670 1677 try:
1671 1678 if p1 is None and p2 is None:
1672 1679 p1, p2 = self.parents(node)
1673 1680 if node != self.hash(text, p1, p2):
1674 1681 # Clear the revision cache on hash failure. The revision cache
1675 1682 # only stores the raw revision and clearing the cache does have
1676 1683 # the side-effect that we won't have a cache hit when the raw
1677 1684 # revision data is accessed. But this case should be rare and
1678 1685 # it is extra work to teach the cache about the hash
1679 1686 # verification state.
1680 1687 if self._revisioncache and self._revisioncache[0] == node:
1681 1688 self._revisioncache = None
1682 1689
1683 1690 revornode = rev
1684 1691 if revornode is None:
1685 1692 revornode = templatefilters.short(hex(node))
1686 1693 raise error.RevlogError(_("integrity check failed on %s:%s")
1687 1694 % (self.indexfile, pycompat.bytestr(revornode)))
1688 1695 except error.RevlogError:
1689 1696 if self._censorable and storageutil.iscensoredtext(text):
1690 1697 raise error.CensoredNodeError(self.indexfile, node, text)
1691 1698 raise
1692 1699
1693 1700 def _enforceinlinesize(self, tr, fp=None):
1694 1701 """Check if the revlog is too big for inline and convert if so.
1695 1702
1696 1703 This should be called after revisions are added to the revlog. If the
1697 1704 revlog has grown too large to be an inline revlog, it will convert it
1698 1705 to use multiple index and data files.
1699 1706 """
1700 1707 tiprev = len(self) - 1
1701 1708 if (not self._inline or
1702 1709 (self.start(tiprev) + self.length(tiprev)) < _maxinline):
1703 1710 return
1704 1711
1705 1712 trinfo = tr.find(self.indexfile)
1706 1713 if trinfo is None:
1707 1714 raise error.RevlogError(_("%s not found in the transaction")
1708 1715 % self.indexfile)
1709 1716
1710 1717 trindex = trinfo[2]
1711 1718 if trindex is not None:
1712 1719 dataoff = self.start(trindex)
1713 1720 else:
1714 1721 # revlog was stripped at start of transaction, use all leftover data
1715 1722 trindex = len(self) - 1
1716 1723 dataoff = self.end(tiprev)
1717 1724
1718 1725 tr.add(self.datafile, dataoff)
1719 1726
1720 1727 if fp:
1721 1728 fp.flush()
1722 1729 fp.close()
1723 1730
1724 1731 with self._datafp('w') as df:
1725 1732 for r in self:
1726 1733 df.write(self._getsegmentforrevs(r, r)[1])
1727 1734
1728 1735 with self._indexfp('w') as fp:
1729 1736 self.version &= ~FLAG_INLINE_DATA
1730 1737 self._inline = False
1731 1738 io = self._io
1732 1739 for i in self:
1733 1740 e = io.packentry(self.index[i], self.node, self.version, i)
1734 1741 fp.write(e)
1735 1742
1736 1743 # the temp file replace the real index when we exit the context
1737 1744 # manager
1738 1745
1739 1746 tr.replace(self.indexfile, trindex * self._io.size)
1740 1747 self._chunkclear()
1741 1748
1742 1749 def _nodeduplicatecallback(self, transaction, node):
1743 1750 """called when trying to add a node already stored.
1744 1751 """
1745 1752
1746 1753 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1747 1754 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None):
1748 1755 """add a revision to the log
1749 1756
1750 1757 text - the revision data to add
1751 1758 transaction - the transaction object used for rollback
1752 1759 link - the linkrev data to add
1753 1760 p1, p2 - the parent nodeids of the revision
1754 1761 cachedelta - an optional precomputed delta
1755 1762 node - nodeid of revision; typically node is not specified, and it is
1756 1763 computed by default as hash(text, p1, p2), however subclasses might
1757 1764 use different hashing method (and override checkhash() in such case)
1758 1765 flags - the known flags to set on the revision
1759 1766 deltacomputer - an optional deltacomputer instance shared between
1760 1767 multiple calls
1761 1768 """
1762 1769 if link == nullrev:
1763 1770 raise error.RevlogError(_("attempted to add linkrev -1 to %s")
1764 1771 % self.indexfile)
1765 1772
1766 1773 if flags:
1767 1774 node = node or self.hash(text, p1, p2)
1768 1775
1769 1776 rawtext, validatehash = self._processflags(text, flags, 'write')
1770 1777
1771 1778 # If the flag processor modifies the revision data, ignore any provided
1772 1779 # cachedelta.
1773 1780 if rawtext != text:
1774 1781 cachedelta = None
1775 1782
1776 1783 if len(rawtext) > _maxentrysize:
1777 1784 raise error.RevlogError(
1778 1785 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1779 1786 % (self.indexfile, len(rawtext)))
1780 1787
1781 1788 node = node or self.hash(rawtext, p1, p2)
1782 1789 if node in self.nodemap:
1783 1790 return node
1784 1791
1785 1792 if validatehash:
1786 1793 self.checkhash(rawtext, node, p1=p1, p2=p2)
1787 1794
1788 1795 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
1789 1796 flags, cachedelta=cachedelta,
1790 1797 deltacomputer=deltacomputer)
1791 1798
1792 1799 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
1793 1800 cachedelta=None, deltacomputer=None):
1794 1801 """add a raw revision with known flags, node and parents
1795 1802 useful when reusing a revision not stored in this revlog (ex: received
1796 1803 over wire, or read from an external bundle).
1797 1804 """
1798 1805 dfh = None
1799 1806 if not self._inline:
1800 1807 dfh = self._datafp("a+")
1801 1808 ifh = self._indexfp("a+")
1802 1809 try:
1803 1810 return self._addrevision(node, rawtext, transaction, link, p1, p2,
1804 1811 flags, cachedelta, ifh, dfh,
1805 1812 deltacomputer=deltacomputer)
1806 1813 finally:
1807 1814 if dfh:
1808 1815 dfh.close()
1809 1816 ifh.close()
1810 1817
1811 1818 def compress(self, data):
1812 1819 """Generate a possibly-compressed representation of data."""
1813 1820 if not data:
1814 1821 return '', data
1815 1822
1816 1823 compressed = self._compressor.compress(data)
1817 1824
1818 1825 if compressed:
1819 1826 # The revlog compressor added the header in the returned data.
1820 1827 return '', compressed
1821 1828
1822 1829 if data[0:1] == '\0':
1823 1830 return '', data
1824 1831 return 'u', data
1825 1832
1826 1833 def decompress(self, data):
1827 1834 """Decompress a revlog chunk.
1828 1835
1829 1836 The chunk is expected to begin with a header identifying the
1830 1837 format type so it can be routed to an appropriate decompressor.
1831 1838 """
1832 1839 if not data:
1833 1840 return data
1834 1841
1835 1842 # Revlogs are read much more frequently than they are written and many
1836 1843 # chunks only take microseconds to decompress, so performance is
1837 1844 # important here.
1838 1845 #
1839 1846 # We can make a few assumptions about revlogs:
1840 1847 #
1841 1848 # 1) the majority of chunks will be compressed (as opposed to inline
1842 1849 # raw data).
1843 1850 # 2) decompressing *any* data will likely by at least 10x slower than
1844 1851 # returning raw inline data.
1845 1852 # 3) we want to prioritize common and officially supported compression
1846 1853 # engines
1847 1854 #
1848 1855 # It follows that we want to optimize for "decompress compressed data
1849 1856 # when encoded with common and officially supported compression engines"
1850 1857 # case over "raw data" and "data encoded by less common or non-official
1851 1858 # compression engines." That is why we have the inline lookup first
1852 1859 # followed by the compengines lookup.
1853 1860 #
1854 1861 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
1855 1862 # compressed chunks. And this matters for changelog and manifest reads.
1856 1863 t = data[0:1]
1857 1864
1858 1865 if t == 'x':
1859 1866 try:
1860 1867 return _zlibdecompress(data)
1861 1868 except zlib.error as e:
1862 1869 raise error.RevlogError(_('revlog decompress error: %s') %
1863 1870 stringutil.forcebytestr(e))
1864 1871 # '\0' is more common than 'u' so it goes first.
1865 1872 elif t == '\0':
1866 1873 return data
1867 1874 elif t == 'u':
1868 1875 return util.buffer(data, 1)
1869 1876
1870 1877 try:
1871 1878 compressor = self._decompressors[t]
1872 1879 except KeyError:
1873 1880 try:
1874 1881 engine = util.compengines.forrevlogheader(t)
1875 1882 compressor = engine.revlogcompressor()
1876 1883 self._decompressors[t] = compressor
1877 1884 except KeyError:
1878 1885 raise error.RevlogError(_('unknown compression type %r') % t)
1879 1886
1880 1887 return compressor.decompress(data)
1881 1888
1882 1889 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
1883 1890 cachedelta, ifh, dfh, alwayscache=False,
1884 1891 deltacomputer=None):
1885 1892 """internal function to add revisions to the log
1886 1893
1887 1894 see addrevision for argument descriptions.
1888 1895
1889 1896 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
1890 1897
1891 1898 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
1892 1899 be used.
1893 1900
1894 1901 invariants:
1895 1902 - rawtext is optional (can be None); if not set, cachedelta must be set.
1896 1903 if both are set, they must correspond to each other.
1897 1904 """
1898 1905 if node == nullid:
1899 1906 raise error.RevlogError(_("%s: attempt to add null revision") %
1900 1907 self.indexfile)
1901 1908 if node == wdirid or node in wdirfilenodeids:
1902 1909 raise error.RevlogError(_("%s: attempt to add wdir revision") %
1903 1910 self.indexfile)
1904 1911
1905 1912 if self._inline:
1906 1913 fh = ifh
1907 1914 else:
1908 1915 fh = dfh
1909 1916
1910 1917 btext = [rawtext]
1911 1918
1912 1919 curr = len(self)
1913 1920 prev = curr - 1
1914 1921 offset = self.end(prev)
1915 1922 p1r, p2r = self.rev(p1), self.rev(p2)
1916 1923
1917 1924 # full versions are inserted when the needed deltas
1918 1925 # become comparable to the uncompressed text
1919 1926 if rawtext is None:
1920 1927 # need rawtext size, before changed by flag processors, which is
1921 1928 # the non-raw size. use revlog explicitly to avoid filelog's extra
1922 1929 # logic that might remove metadata size.
1923 1930 textlen = mdiff.patchedsize(revlog.size(self, cachedelta[0]),
1924 1931 cachedelta[1])
1925 1932 else:
1926 1933 textlen = len(rawtext)
1927 1934
1928 1935 if deltacomputer is None:
1929 1936 deltacomputer = deltautil.deltacomputer(self)
1930 1937
1931 1938 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
1932 1939
1933 1940 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
1934 1941
1935 1942 e = (offset_type(offset, flags), deltainfo.deltalen, textlen,
1936 1943 deltainfo.base, link, p1r, p2r, node)
1937 1944 self.index.append(e)
1938 1945 self.nodemap[node] = curr
1939 1946
1940 1947 # Reset the pure node cache start lookup offset to account for new
1941 1948 # revision.
1942 1949 if self._nodepos is not None:
1943 1950 self._nodepos = curr
1944 1951
1945 1952 entry = self._io.packentry(e, self.node, self.version, curr)
1946 1953 self._writeentry(transaction, ifh, dfh, entry, deltainfo.data,
1947 1954 link, offset)
1948 1955
1949 1956 rawtext = btext[0]
1950 1957
1951 1958 if alwayscache and rawtext is None:
1952 1959 rawtext = deltacomputer.buildtext(revinfo, fh)
1953 1960
1954 1961 if type(rawtext) == bytes: # only accept immutable objects
1955 1962 self._revisioncache = (node, curr, rawtext)
1956 1963 self._chainbasecache[curr] = deltainfo.chainbase
1957 1964 return node
1958 1965
1959 1966 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1960 1967 # Files opened in a+ mode have inconsistent behavior on various
1961 1968 # platforms. Windows requires that a file positioning call be made
1962 1969 # when the file handle transitions between reads and writes. See
1963 1970 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
1964 1971 # platforms, Python or the platform itself can be buggy. Some versions
1965 1972 # of Solaris have been observed to not append at the end of the file
1966 1973 # if the file was seeked to before the end. See issue4943 for more.
1967 1974 #
1968 1975 # We work around this issue by inserting a seek() before writing.
1969 1976 # Note: This is likely not necessary on Python 3.
1970 1977 ifh.seek(0, os.SEEK_END)
1971 1978 if dfh:
1972 1979 dfh.seek(0, os.SEEK_END)
1973 1980
1974 1981 curr = len(self) - 1
1975 1982 if not self._inline:
1976 1983 transaction.add(self.datafile, offset)
1977 1984 transaction.add(self.indexfile, curr * len(entry))
1978 1985 if data[0]:
1979 1986 dfh.write(data[0])
1980 1987 dfh.write(data[1])
1981 1988 ifh.write(entry)
1982 1989 else:
1983 1990 offset += curr * self._io.size
1984 1991 transaction.add(self.indexfile, offset, curr)
1985 1992 ifh.write(entry)
1986 1993 ifh.write(data[0])
1987 1994 ifh.write(data[1])
1988 1995 self._enforceinlinesize(transaction, ifh)
1989 1996
1990 1997 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
1991 1998 """
1992 1999 add a delta group
1993 2000
1994 2001 given a set of deltas, add them to the revision log. the
1995 2002 first delta is against its parent, which should be in our
1996 2003 log, the rest are against the previous delta.
1997 2004
1998 2005 If ``addrevisioncb`` is defined, it will be called with arguments of
1999 2006 this revlog and the node that was added.
2000 2007 """
2001 2008
2002 2009 nodes = []
2003 2010
2004 2011 r = len(self)
2005 2012 end = 0
2006 2013 if r:
2007 2014 end = self.end(r - 1)
2008 2015 ifh = self._indexfp("a+")
2009 2016 isize = r * self._io.size
2010 2017 if self._inline:
2011 2018 transaction.add(self.indexfile, end + isize, r)
2012 2019 dfh = None
2013 2020 else:
2014 2021 transaction.add(self.indexfile, isize, r)
2015 2022 transaction.add(self.datafile, end)
2016 2023 dfh = self._datafp("a+")
2017 2024 def flush():
2018 2025 if dfh:
2019 2026 dfh.flush()
2020 2027 ifh.flush()
2021 2028 try:
2022 2029 deltacomputer = deltautil.deltacomputer(self)
2023 2030 # loop through our set of deltas
2024 2031 for data in deltas:
2025 2032 node, p1, p2, linknode, deltabase, delta, flags = data
2026 2033 link = linkmapper(linknode)
2027 2034 flags = flags or REVIDX_DEFAULT_FLAGS
2028 2035
2029 2036 nodes.append(node)
2030 2037
2031 2038 if node in self.nodemap:
2032 2039 self._nodeduplicatecallback(transaction, node)
2033 2040 # this can happen if two branches make the same change
2034 2041 continue
2035 2042
2036 2043 for p in (p1, p2):
2037 2044 if p not in self.nodemap:
2038 2045 raise error.LookupError(p, self.indexfile,
2039 2046 _('unknown parent'))
2040 2047
2041 2048 if deltabase not in self.nodemap:
2042 2049 raise error.LookupError(deltabase, self.indexfile,
2043 2050 _('unknown delta base'))
2044 2051
2045 2052 baserev = self.rev(deltabase)
2046 2053
2047 2054 if baserev != nullrev and self.iscensored(baserev):
2048 2055 # if base is censored, delta must be full replacement in a
2049 2056 # single patch operation
2050 2057 hlen = struct.calcsize(">lll")
2051 2058 oldlen = self.rawsize(baserev)
2052 2059 newlen = len(delta) - hlen
2053 2060 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2054 2061 raise error.CensoredBaseError(self.indexfile,
2055 2062 self.node(baserev))
2056 2063
2057 2064 if not flags and self._peek_iscensored(baserev, delta, flush):
2058 2065 flags |= REVIDX_ISCENSORED
2059 2066
2060 2067 # We assume consumers of addrevisioncb will want to retrieve
2061 2068 # the added revision, which will require a call to
2062 2069 # revision(). revision() will fast path if there is a cache
2063 2070 # hit. So, we tell _addrevision() to always cache in this case.
2064 2071 # We're only using addgroup() in the context of changegroup
2065 2072 # generation so the revision data can always be handled as raw
2066 2073 # by the flagprocessor.
2067 2074 self._addrevision(node, None, transaction, link,
2068 2075 p1, p2, flags, (baserev, delta),
2069 2076 ifh, dfh,
2070 2077 alwayscache=bool(addrevisioncb),
2071 2078 deltacomputer=deltacomputer)
2072 2079
2073 2080 if addrevisioncb:
2074 2081 addrevisioncb(self, node)
2075 2082
2076 2083 if not dfh and not self._inline:
2077 2084 # addrevision switched from inline to conventional
2078 2085 # reopen the index
2079 2086 ifh.close()
2080 2087 dfh = self._datafp("a+")
2081 2088 ifh = self._indexfp("a+")
2082 2089 finally:
2083 2090 if dfh:
2084 2091 dfh.close()
2085 2092 ifh.close()
2086 2093
2087 2094 return nodes
2088 2095
2089 2096 def iscensored(self, rev):
2090 2097 """Check if a file revision is censored."""
2091 2098 if not self._censorable:
2092 2099 return False
2093 2100
2094 2101 return self.flags(rev) & REVIDX_ISCENSORED
2095 2102
2096 2103 def _peek_iscensored(self, baserev, delta, flush):
2097 2104 """Quickly check if a delta produces a censored revision."""
2098 2105 if not self._censorable:
2099 2106 return False
2100 2107
2101 2108 # Fragile heuristic: unless new file meta keys are added alphabetically
2102 2109 # preceding "censored", all censored revisions are prefixed by
2103 2110 # "\1\ncensored:". A delta producing such a censored revision must be a
2104 2111 # full-replacement delta, so we inspect the first and only patch in the
2105 2112 # delta for this prefix.
2106 2113 hlen = struct.calcsize(">lll")
2107 2114 if len(delta) <= hlen:
2108 2115 return False
2109 2116
2110 2117 oldlen = self.rawsize(baserev)
2111 2118 newlen = len(delta) - hlen
2112 2119 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2113 2120 return False
2114 2121
2115 2122 add = "\1\ncensored:"
2116 2123 addlen = len(add)
2117 2124 return newlen >= addlen and delta[hlen:hlen + addlen] == add
2118 2125
2119 2126 def getstrippoint(self, minlink):
2120 2127 """find the minimum rev that must be stripped to strip the linkrev
2121 2128
2122 2129 Returns a tuple containing the minimum rev and a set of all revs that
2123 2130 have linkrevs that will be broken by this strip.
2124 2131 """
2125 2132 return storageutil.resolvestripinfo(minlink, len(self) - 1,
2126 2133 self.headrevs(),
2127 2134 self.linkrev, self.parentrevs)
2128 2135
2129 2136 def strip(self, minlink, transaction):
2130 2137 """truncate the revlog on the first revision with a linkrev >= minlink
2131 2138
2132 2139 This function is called when we're stripping revision minlink and
2133 2140 its descendants from the repository.
2134 2141
2135 2142 We have to remove all revisions with linkrev >= minlink, because
2136 2143 the equivalent changelog revisions will be renumbered after the
2137 2144 strip.
2138 2145
2139 2146 So we truncate the revlog on the first of these revisions, and
2140 2147 trust that the caller has saved the revisions that shouldn't be
2141 2148 removed and that it'll re-add them after this truncation.
2142 2149 """
2143 2150 if len(self) == 0:
2144 2151 return
2145 2152
2146 2153 rev, _ = self.getstrippoint(minlink)
2147 2154 if rev == len(self):
2148 2155 return
2149 2156
2150 2157 # first truncate the files on disk
2151 2158 end = self.start(rev)
2152 2159 if not self._inline:
2153 2160 transaction.add(self.datafile, end)
2154 2161 end = rev * self._io.size
2155 2162 else:
2156 2163 end += rev * self._io.size
2157 2164
2158 2165 transaction.add(self.indexfile, end)
2159 2166
2160 2167 # then reset internal state in memory to forget those revisions
2161 2168 self._revisioncache = None
2162 2169 self._chaininfocache = {}
2163 2170 self._chunkclear()
2164 2171 for x in pycompat.xrange(rev, len(self)):
2165 2172 del self.nodemap[self.node(x)]
2166 2173
2167 2174 del self.index[rev:-1]
2168 2175 self._nodepos = None
2169 2176
2170 2177 def checksize(self):
2171 2178 expected = 0
2172 2179 if len(self):
2173 2180 expected = max(0, self.end(len(self) - 1))
2174 2181
2175 2182 try:
2176 2183 with self._datafp() as f:
2177 2184 f.seek(0, 2)
2178 2185 actual = f.tell()
2179 2186 dd = actual - expected
2180 2187 except IOError as inst:
2181 2188 if inst.errno != errno.ENOENT:
2182 2189 raise
2183 2190 dd = 0
2184 2191
2185 2192 try:
2186 2193 f = self.opener(self.indexfile)
2187 2194 f.seek(0, 2)
2188 2195 actual = f.tell()
2189 2196 f.close()
2190 2197 s = self._io.size
2191 2198 i = max(0, actual // s)
2192 2199 di = actual - (i * s)
2193 2200 if self._inline:
2194 2201 databytes = 0
2195 2202 for r in self:
2196 2203 databytes += max(0, self.length(r))
2197 2204 dd = 0
2198 2205 di = actual - len(self) * s - databytes
2199 2206 except IOError as inst:
2200 2207 if inst.errno != errno.ENOENT:
2201 2208 raise
2202 2209 di = 0
2203 2210
2204 2211 return (dd, di)
2205 2212
2206 2213 def files(self):
2207 2214 res = [self.indexfile]
2208 2215 if not self._inline:
2209 2216 res.append(self.datafile)
2210 2217 return res
2211 2218
2212 2219 def emitrevisions(self, nodes, nodesorder=None, revisiondata=False,
2213 2220 assumehaveparentrevisions=False, deltaprevious=False):
2214 2221 if nodesorder not in ('nodes', 'storage', None):
2215 2222 raise error.ProgrammingError('unhandled value for nodesorder: %s' %
2216 2223 nodesorder)
2217 2224
2218 2225 if nodesorder is None and not self._generaldelta:
2219 2226 nodesorder = 'storage'
2220 2227
2221 2228 return storageutil.emitrevisions(
2222 2229 self, nodes, nodesorder, revlogrevisiondelta,
2223 2230 deltaparentfn=self.deltaparent,
2224 2231 candeltafn=self.candelta,
2225 2232 rawsizefn=self.rawsize,
2226 2233 revdifffn=self.revdiff,
2227 2234 flagsfn=self.flags,
2228 2235 sendfulltext=not self._storedeltachains,
2229 2236 revisiondata=revisiondata,
2230 2237 assumehaveparentrevisions=assumehaveparentrevisions,
2231 2238 deltaprevious=deltaprevious)
2232 2239
2233 2240 DELTAREUSEALWAYS = 'always'
2234 2241 DELTAREUSESAMEREVS = 'samerevs'
2235 2242 DELTAREUSENEVER = 'never'
2236 2243
2237 2244 DELTAREUSEFULLADD = 'fulladd'
2238 2245
2239 2246 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
2240 2247
2241 2248 def clone(self, tr, destrevlog, addrevisioncb=None,
2242 2249 deltareuse=DELTAREUSESAMEREVS, deltabothparents=None):
2243 2250 """Copy this revlog to another, possibly with format changes.
2244 2251
2245 2252 The destination revlog will contain the same revisions and nodes.
2246 2253 However, it may not be bit-for-bit identical due to e.g. delta encoding
2247 2254 differences.
2248 2255
2249 2256 The ``deltareuse`` argument control how deltas from the existing revlog
2250 2257 are preserved in the destination revlog. The argument can have the
2251 2258 following values:
2252 2259
2253 2260 DELTAREUSEALWAYS
2254 2261 Deltas will always be reused (if possible), even if the destination
2255 2262 revlog would not select the same revisions for the delta. This is the
2256 2263 fastest mode of operation.
2257 2264 DELTAREUSESAMEREVS
2258 2265 Deltas will be reused if the destination revlog would pick the same
2259 2266 revisions for the delta. This mode strikes a balance between speed
2260 2267 and optimization.
2261 2268 DELTAREUSENEVER
2262 2269 Deltas will never be reused. This is the slowest mode of execution.
2263 2270 This mode can be used to recompute deltas (e.g. if the diff/delta
2264 2271 algorithm changes).
2265 2272
2266 2273 Delta computation can be slow, so the choice of delta reuse policy can
2267 2274 significantly affect run time.
2268 2275
2269 2276 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2270 2277 two extremes. Deltas will be reused if they are appropriate. But if the
2271 2278 delta could choose a better revision, it will do so. This means if you
2272 2279 are converting a non-generaldelta revlog to a generaldelta revlog,
2273 2280 deltas will be recomputed if the delta's parent isn't a parent of the
2274 2281 revision.
2275 2282
2276 2283 In addition to the delta policy, the ``deltabothparents`` argument
2277 2284 controls whether to compute deltas against both parents for merges.
2278 2285 By default, the current default is used.
2279 2286 """
2280 2287 if deltareuse not in self.DELTAREUSEALL:
2281 2288 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2282 2289
2283 2290 if len(destrevlog):
2284 2291 raise ValueError(_('destination revlog is not empty'))
2285 2292
2286 2293 if getattr(self, 'filteredrevs', None):
2287 2294 raise ValueError(_('source revlog has filtered revisions'))
2288 2295 if getattr(destrevlog, 'filteredrevs', None):
2289 2296 raise ValueError(_('destination revlog has filtered revisions'))
2290 2297
2291 2298 # lazydeltabase controls whether to reuse a cached delta, if possible.
2292 2299 oldlazydeltabase = destrevlog._lazydeltabase
2293 2300 oldamd = destrevlog._deltabothparents
2294 2301
2295 2302 try:
2296 2303 if deltareuse == self.DELTAREUSEALWAYS:
2297 2304 destrevlog._lazydeltabase = True
2298 2305 elif deltareuse == self.DELTAREUSESAMEREVS:
2299 2306 destrevlog._lazydeltabase = False
2300 2307
2301 2308 destrevlog._deltabothparents = deltabothparents or oldamd
2302 2309
2303 2310 populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
2304 2311 self.DELTAREUSESAMEREVS)
2305 2312
2306 2313 deltacomputer = deltautil.deltacomputer(destrevlog)
2307 2314 index = self.index
2308 2315 for rev in self:
2309 2316 entry = index[rev]
2310 2317
2311 2318 # Some classes override linkrev to take filtered revs into
2312 2319 # account. Use raw entry from index.
2313 2320 flags = entry[0] & 0xffff
2314 2321 linkrev = entry[4]
2315 2322 p1 = index[entry[5]][7]
2316 2323 p2 = index[entry[6]][7]
2317 2324 node = entry[7]
2318 2325
2319 2326 # (Possibly) reuse the delta from the revlog if allowed and
2320 2327 # the revlog chunk is a delta.
2321 2328 cachedelta = None
2322 2329 rawtext = None
2323 2330 if populatecachedelta:
2324 2331 dp = self.deltaparent(rev)
2325 2332 if dp != nullrev:
2326 2333 cachedelta = (dp, bytes(self._chunk(rev)))
2327 2334
2328 2335 if not cachedelta:
2329 2336 rawtext = self.revision(rev, raw=True)
2330 2337
2331 2338
2332 2339 if deltareuse == self.DELTAREUSEFULLADD:
2333 2340 destrevlog.addrevision(rawtext, tr, linkrev, p1, p2,
2334 2341 cachedelta=cachedelta,
2335 2342 node=node, flags=flags,
2336 2343 deltacomputer=deltacomputer)
2337 2344 else:
2338 2345 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2339 2346 checkambig=False)
2340 2347 dfh = None
2341 2348 if not destrevlog._inline:
2342 2349 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2343 2350 try:
2344 2351 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
2345 2352 p2, flags, cachedelta, ifh, dfh,
2346 2353 deltacomputer=deltacomputer)
2347 2354 finally:
2348 2355 if dfh:
2349 2356 dfh.close()
2350 2357 ifh.close()
2351 2358
2352 2359 if addrevisioncb:
2353 2360 addrevisioncb(self, rev, node)
2354 2361 finally:
2355 2362 destrevlog._lazydeltabase = oldlazydeltabase
2356 2363 destrevlog._deltabothparents = oldamd
2357 2364
2358 2365 def censorrevision(self, tr, censornode, tombstone=b''):
2359 2366 if (self.version & 0xFFFF) == REVLOGV0:
2360 2367 raise error.RevlogError(_('cannot censor with version %d revlogs') %
2361 2368 self.version)
2362 2369
2363 2370 censorrev = self.rev(censornode)
2364 2371 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2365 2372
2366 2373 if len(tombstone) > self.rawsize(censorrev):
2367 2374 raise error.Abort(_('censor tombstone must be no longer than '
2368 2375 'censored data'))
2369 2376
2370 2377 # Rewriting the revlog in place is hard. Our strategy for censoring is
2371 2378 # to create a new revlog, copy all revisions to it, then replace the
2372 2379 # revlogs on transaction close.
2373 2380
2374 2381 newindexfile = self.indexfile + b'.tmpcensored'
2375 2382 newdatafile = self.datafile + b'.tmpcensored'
2376 2383
2377 2384 # This is a bit dangerous. We could easily have a mismatch of state.
2378 2385 newrl = revlog(self.opener, newindexfile, newdatafile,
2379 2386 censorable=True)
2380 2387 newrl.version = self.version
2381 2388 newrl._generaldelta = self._generaldelta
2382 2389 newrl._io = self._io
2383 2390
2384 2391 for rev in self.revs():
2385 2392 node = self.node(rev)
2386 2393 p1, p2 = self.parents(node)
2387 2394
2388 2395 if rev == censorrev:
2389 2396 newrl.addrawrevision(tombstone, tr, self.linkrev(censorrev),
2390 2397 p1, p2, censornode, REVIDX_ISCENSORED)
2391 2398
2392 2399 if newrl.deltaparent(rev) != nullrev:
2393 2400 raise error.Abort(_('censored revision stored as delta; '
2394 2401 'cannot censor'),
2395 2402 hint=_('censoring of revlogs is not '
2396 2403 'fully implemented; please report '
2397 2404 'this bug'))
2398 2405 continue
2399 2406
2400 2407 if self.iscensored(rev):
2401 2408 if self.deltaparent(rev) != nullrev:
2402 2409 raise error.Abort(_('cannot censor due to censored '
2403 2410 'revision having delta stored'))
2404 2411 rawtext = self._chunk(rev)
2405 2412 else:
2406 2413 rawtext = self.revision(rev, raw=True)
2407 2414
2408 2415 newrl.addrawrevision(rawtext, tr, self.linkrev(rev), p1, p2, node,
2409 2416 self.flags(rev))
2410 2417
2411 2418 tr.addbackup(self.indexfile, location='store')
2412 2419 if not self._inline:
2413 2420 tr.addbackup(self.datafile, location='store')
2414 2421
2415 2422 self.opener.rename(newrl.indexfile, self.indexfile)
2416 2423 if not self._inline:
2417 2424 self.opener.rename(newrl.datafile, self.datafile)
2418 2425
2419 2426 self.clearcaches()
2420 2427 self._loadindex(self.version, None)
2421 2428
2422 2429 def verifyintegrity(self, state):
2423 2430 """Verifies the integrity of the revlog.
2424 2431
2425 2432 Yields ``revlogproblem`` instances describing problems that are
2426 2433 found.
2427 2434 """
2428 2435 dd, di = self.checksize()
2429 2436 if dd:
2430 2437 yield revlogproblem(error=_('data length off by %d bytes') % dd)
2431 2438 if di:
2432 2439 yield revlogproblem(error=_('index contains %d extra bytes') % di)
2433 2440
2434 2441 version = self.version & 0xFFFF
2435 2442
2436 2443 # The verifier tells us what version revlog we should be.
2437 2444 if version != state['expectedversion']:
2438 2445 yield revlogproblem(
2439 2446 warning=_("warning: '%s' uses revlog format %d; expected %d") %
2440 2447 (self.indexfile, version, state['expectedversion']))
2441 2448
2442 2449 state['skipread'] = set()
2443 2450
2444 2451 for rev in self:
2445 2452 node = self.node(rev)
2446 2453
2447 2454 # Verify contents. 4 cases to care about:
2448 2455 #
2449 2456 # common: the most common case
2450 2457 # rename: with a rename
2451 2458 # meta: file content starts with b'\1\n', the metadata
2452 2459 # header defined in filelog.py, but without a rename
2453 2460 # ext: content stored externally
2454 2461 #
2455 2462 # More formally, their differences are shown below:
2456 2463 #
2457 2464 # | common | rename | meta | ext
2458 2465 # -------------------------------------------------------
2459 2466 # flags() | 0 | 0 | 0 | not 0
2460 2467 # renamed() | False | True | False | ?
2461 2468 # rawtext[0:2]=='\1\n'| False | True | True | ?
2462 2469 #
2463 2470 # "rawtext" means the raw text stored in revlog data, which
2464 2471 # could be retrieved by "revision(rev, raw=True)". "text"
2465 2472 # mentioned below is "revision(rev, raw=False)".
2466 2473 #
2467 2474 # There are 3 different lengths stored physically:
2468 2475 # 1. L1: rawsize, stored in revlog index
2469 2476 # 2. L2: len(rawtext), stored in revlog data
2470 2477 # 3. L3: len(text), stored in revlog data if flags==0, or
2471 2478 # possibly somewhere else if flags!=0
2472 2479 #
2473 2480 # L1 should be equal to L2. L3 could be different from them.
2474 2481 # "text" may or may not affect commit hash depending on flag
2475 2482 # processors (see revlog.addflagprocessor).
2476 2483 #
2477 2484 # | common | rename | meta | ext
2478 2485 # -------------------------------------------------
2479 2486 # rawsize() | L1 | L1 | L1 | L1
2480 2487 # size() | L1 | L2-LM | L1(*) | L1 (?)
2481 2488 # len(rawtext) | L2 | L2 | L2 | L2
2482 2489 # len(text) | L2 | L2 | L2 | L3
2483 2490 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
2484 2491 #
2485 2492 # LM: length of metadata, depending on rawtext
2486 2493 # (*): not ideal, see comment in filelog.size
2487 2494 # (?): could be "- len(meta)" if the resolved content has
2488 2495 # rename metadata
2489 2496 #
2490 2497 # Checks needed to be done:
2491 2498 # 1. length check: L1 == L2, in all cases.
2492 2499 # 2. hash check: depending on flag processor, we may need to
2493 2500 # use either "text" (external), or "rawtext" (in revlog).
2494 2501
2495 2502 try:
2496 2503 skipflags = state.get('skipflags', 0)
2497 2504 if skipflags:
2498 2505 skipflags &= self.flags(rev)
2499 2506
2500 2507 if skipflags:
2501 2508 state['skipread'].add(node)
2502 2509 else:
2503 2510 # Side-effect: read content and verify hash.
2504 2511 self.revision(node)
2505 2512
2506 2513 l1 = self.rawsize(rev)
2507 2514 l2 = len(self.revision(node, raw=True))
2508 2515
2509 2516 if l1 != l2:
2510 2517 yield revlogproblem(
2511 2518 error=_('unpacked size is %d, %d expected') % (l2, l1),
2512 2519 node=node)
2513 2520
2514 2521 except error.CensoredNodeError:
2515 2522 if state['erroroncensored']:
2516 2523 yield revlogproblem(error=_('censored file data'),
2517 2524 node=node)
2518 2525 state['skipread'].add(node)
2519 2526 except Exception as e:
2520 2527 yield revlogproblem(
2521 2528 error=_('unpacking %s: %s') % (short(node),
2522 2529 stringutil.forcebytestr(e)),
2523 2530 node=node)
2524 2531 state['skipread'].add(node)
2525 2532
2526 2533 def storageinfo(self, exclusivefiles=False, sharedfiles=False,
2527 2534 revisionscount=False, trackedsize=False,
2528 2535 storedsize=False):
2529 2536 d = {}
2530 2537
2531 2538 if exclusivefiles:
2532 2539 d['exclusivefiles'] = [(self.opener, self.indexfile)]
2533 2540 if not self._inline:
2534 2541 d['exclusivefiles'].append((self.opener, self.datafile))
2535 2542
2536 2543 if sharedfiles:
2537 2544 d['sharedfiles'] = []
2538 2545
2539 2546 if revisionscount:
2540 2547 d['revisionscount'] = len(self)
2541 2548
2542 2549 if trackedsize:
2543 2550 d['trackedsize'] = sum(map(self.rawsize, iter(self)))
2544 2551
2545 2552 if storedsize:
2546 2553 d['storedsize'] = sum(self.opener.stat(path).st_size
2547 2554 for path in self.files())
2548 2555
2549 2556 return d
@@ -1,300 +1,302 b''
1 1 # Create server
2 2 $ hg init server
3 3 $ cd server
4 4 $ cat >> .hg/hgrc << EOF
5 5 > [extensions]
6 6 > extension=$TESTDIR/flagprocessorext.py
7 7 > EOF
8 8 $ cd ../
9 9
10 10 # Clone server and enable extensions
11 11 $ hg clone -q server client
12 12 $ cd client
13 13 $ cat >> .hg/hgrc << EOF
14 14 > [extensions]
15 15 > extension=$TESTDIR/flagprocessorext.py
16 16 > EOF
17 17
18 18 # Commit file that will trigger the noop extension
19 19 $ echo '[NOOP]' > noop
20 20 $ hg commit -Aqm "noop"
21 21
22 22 # Commit file that will trigger the base64 extension
23 23 $ echo '[BASE64]' > base64
24 24 $ hg commit -Aqm 'base64'
25 25
26 26 # Commit file that will trigger the gzip extension
27 27 $ echo '[GZIP]' > gzip
28 28 $ hg commit -Aqm 'gzip'
29 29
30 30 # Commit file that will trigger noop and base64
31 31 $ echo '[NOOP][BASE64]' > noop-base64
32 32 $ hg commit -Aqm 'noop+base64'
33 33
34 34 # Commit file that will trigger noop and gzip
35 35 $ echo '[NOOP][GZIP]' > noop-gzip
36 36 $ hg commit -Aqm 'noop+gzip'
37 37
38 38 # Commit file that will trigger base64 and gzip
39 39 $ echo '[BASE64][GZIP]' > base64-gzip
40 40 $ hg commit -Aqm 'base64+gzip'
41 41
42 42 # Commit file that will trigger base64, gzip and noop
43 43 $ echo '[BASE64][GZIP][NOOP]' > base64-gzip-noop
44 44 $ hg commit -Aqm 'base64+gzip+noop'
45 45
46 46 # TEST: ensure the revision data is consistent
47 47 $ hg cat noop
48 48 [NOOP]
49 49 $ hg debugdata noop 0
50 50 [NOOP]
51 51
52 52 $ hg cat -r . base64
53 53 [BASE64]
54 54 $ hg debugdata base64 0
55 55 W0JBU0U2NF0K (no-eol)
56 56
57 57 $ hg cat -r . gzip
58 58 [GZIP]
59 59 $ hg debugdata gzip 0
60 60 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
61 61
62 62 $ hg cat -r . noop-base64
63 63 [NOOP][BASE64]
64 64 $ hg debugdata noop-base64 0
65 65 W05PT1BdW0JBU0U2NF0K (no-eol)
66 66
67 67 $ hg cat -r . noop-gzip
68 68 [NOOP][GZIP]
69 69 $ hg debugdata noop-gzip 0
70 70 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
71 71
72 72 $ hg cat -r . base64-gzip
73 73 [BASE64][GZIP]
74 74 $ hg debugdata base64-gzip 0
75 75 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
76 76
77 77 $ hg cat -r . base64-gzip-noop
78 78 [BASE64][GZIP][NOOP]
79 79 $ hg debugdata base64-gzip-noop 0
80 80 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
81 81
82 82 # Push to the server
83 83 $ hg push
84 84 pushing to $TESTTMP/server
85 85 searching for changes
86 86 adding changesets
87 87 adding manifests
88 88 adding file changes
89 89 added 7 changesets with 7 changes to 7 files
90 90
91 91 Ensure the data got to the server OK
92 92
93 93 $ cd ../server
94 94 $ hg cat -r 6e48f4215d24 noop
95 95 [NOOP]
96 96 $ hg debugdata noop 0
97 97 [NOOP]
98 98
99 99 $ hg cat -r 6e48f4215d24 base64
100 100 [BASE64]
101 101 $ hg debugdata base64 0
102 102 W0JBU0U2NF0K (no-eol)
103 103
104 104 $ hg cat -r 6e48f4215d24 gzip
105 105 [GZIP]
106 106 $ hg debugdata gzip 0
107 107 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
108 108
109 109 $ hg cat -r 6e48f4215d24 noop-base64
110 110 [NOOP][BASE64]
111 111 $ hg debugdata noop-base64 0
112 112 W05PT1BdW0JBU0U2NF0K (no-eol)
113 113
114 114 $ hg cat -r 6e48f4215d24 noop-gzip
115 115 [NOOP][GZIP]
116 116 $ hg debugdata noop-gzip 0
117 117 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
118 118
119 119 $ hg cat -r 6e48f4215d24 base64-gzip
120 120 [BASE64][GZIP]
121 121 $ hg debugdata base64-gzip 0
122 122 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
123 123
124 124 $ hg cat -r 6e48f4215d24 base64-gzip-noop
125 125 [BASE64][GZIP][NOOP]
126 126 $ hg debugdata base64-gzip-noop 0
127 127 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
128 128
129 129 # Initialize new client (not cloning) and setup extension
130 130 $ cd ..
131 131 $ hg init client2
132 132 $ cd client2
133 133 $ cat >> .hg/hgrc << EOF
134 134 > [paths]
135 135 > default = $TESTTMP/server
136 136 > [extensions]
137 137 > extension=$TESTDIR/flagprocessorext.py
138 138 > EOF
139 139
140 140 # Pull from server and update to latest revision
141 141 $ hg pull default
142 142 pulling from $TESTTMP/server
143 143 requesting all changes
144 144 adding changesets
145 145 adding manifests
146 146 adding file changes
147 147 added 7 changesets with 7 changes to 7 files
148 148 new changesets 07b1b9442c5b:6e48f4215d24
149 149 (run 'hg update' to get a working copy)
150 150 $ hg update
151 151 7 files updated, 0 files merged, 0 files removed, 0 files unresolved
152 152
153 153 # TEST: ensure the revision data is consistent
154 154 $ hg cat noop
155 155 [NOOP]
156 156 $ hg debugdata noop 0
157 157 [NOOP]
158 158
159 159 $ hg cat -r . base64
160 160 [BASE64]
161 161 $ hg debugdata base64 0
162 162 W0JBU0U2NF0K (no-eol)
163 163
164 164 $ hg cat -r . gzip
165 165 [GZIP]
166 166 $ hg debugdata gzip 0
167 167 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
168 168
169 169 $ hg cat -r . noop-base64
170 170 [NOOP][BASE64]
171 171 $ hg debugdata noop-base64 0
172 172 W05PT1BdW0JBU0U2NF0K (no-eol)
173 173
174 174 $ hg cat -r . noop-gzip
175 175 [NOOP][GZIP]
176 176 $ hg debugdata noop-gzip 0
177 177 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
178 178
179 179 $ hg cat -r . base64-gzip
180 180 [BASE64][GZIP]
181 181 $ hg debugdata base64-gzip 0
182 182 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
183 183
184 184 $ hg cat -r . base64-gzip-noop
185 185 [BASE64][GZIP][NOOP]
186 186 $ hg debugdata base64-gzip-noop 0
187 187 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
188 188
189 189 # TEST: ensure a missing processor is handled
190 190 $ echo '[FAIL][BASE64][GZIP][NOOP]' > fail-base64-gzip-noop
191 191 $ hg commit -Aqm 'fail+base64+gzip+noop'
192 192 abort: missing processor for flag '0x1'!
193 193 [255]
194 194 $ rm fail-base64-gzip-noop
195 195
196 196 # TEST: ensure we cannot register several flag processors on the same flag
197 197 $ cat >> .hg/hgrc << EOF
198 198 > [extensions]
199 199 > extension=$TESTDIR/flagprocessorext.py
200 200 > duplicate=$TESTDIR/flagprocessorext.py
201 201 > EOF
202 202 $ hg debugrebuilddirstate
203 203 Traceback (most recent call last):
204 204 File "*/mercurial/extensions.py", line *, in _runextsetup (glob)
205 205 extsetup(ui)
206 206 File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
207 207 validatehash,
208 208 File "*/mercurial/revlog.py", line *, in addflagprocessor (glob)
209 _insertflagprocessor(flag, processor, _flagprocessors)
210 File "*/mercurial/revlog.py", line *, in _insertflagprocessor (glob)
209 211 raise error.Abort(msg)
210 212 Abort: cannot register multiple processors on flag '0x8'.
211 213 *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
212 214 $ hg st 2>&1 | egrep 'cannot register multiple processors|flagprocessorext'
213 215 File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
214 216 Abort: cannot register multiple processors on flag '0x8'.
215 217 *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
216 218 File "*/tests/flagprocessorext.py", line *, in b64decode (glob)
217 219
218 220 $ cd ..
219 221
220 222 # TEST: bundle repo
221 223 $ hg init bundletest
222 224 $ cd bundletest
223 225
224 226 $ cat >> .hg/hgrc << EOF
225 227 > [extensions]
226 228 > flagprocessor=$TESTDIR/flagprocessorext.py
227 229 > EOF
228 230
229 231 $ for i in 0 single two three 4; do
230 232 > echo '[BASE64]a-bit-longer-'$i > base64
231 233 > hg commit -m base64-$i -A base64
232 234 > done
233 235
234 236 $ hg update 2 -q
235 237 $ echo '[BASE64]a-bit-longer-branching' > base64
236 238 $ hg commit -q -m branching
237 239
238 240 #if repobundlerepo
239 241 $ hg bundle --base 1 bundle.hg
240 242 4 changesets found
241 243 $ hg --config extensions.strip= strip -r 2 --no-backup --force -q
242 244 $ hg -R bundle.hg log --stat -T '{rev} {desc}\n' base64
243 245 5 branching
244 246 base64 | 2 +-
245 247 1 files changed, 1 insertions(+), 1 deletions(-)
246 248
247 249 4 base64-4
248 250 base64 | 2 +-
249 251 1 files changed, 1 insertions(+), 1 deletions(-)
250 252
251 253 3 base64-three
252 254 base64 | 2 +-
253 255 1 files changed, 1 insertions(+), 1 deletions(-)
254 256
255 257 2 base64-two
256 258 base64 | 2 +-
257 259 1 files changed, 1 insertions(+), 1 deletions(-)
258 260
259 261 1 base64-single
260 262 base64 | 2 +-
261 263 1 files changed, 1 insertions(+), 1 deletions(-)
262 264
263 265 0 base64-0
264 266 base64 | 1 +
265 267 1 files changed, 1 insertions(+), 0 deletions(-)
266 268
267 269
268 270 $ hg bundle -R bundle.hg --base 1 bundle-again.hg -q
269 271 $ hg -R bundle-again.hg log --stat -T '{rev} {desc}\n' base64
270 272 5 branching
271 273 base64 | 2 +-
272 274 1 files changed, 1 insertions(+), 1 deletions(-)
273 275
274 276 4 base64-4
275 277 base64 | 2 +-
276 278 1 files changed, 1 insertions(+), 1 deletions(-)
277 279
278 280 3 base64-three
279 281 base64 | 2 +-
280 282 1 files changed, 1 insertions(+), 1 deletions(-)
281 283
282 284 2 base64-two
283 285 base64 | 2 +-
284 286 1 files changed, 1 insertions(+), 1 deletions(-)
285 287
286 288 1 base64-single
287 289 base64 | 2 +-
288 290 1 files changed, 1 insertions(+), 1 deletions(-)
289 291
290 292 0 base64-0
291 293 base64 | 1 +
292 294 1 files changed, 1 insertions(+), 0 deletions(-)
293 295
294 296 $ rm bundle.hg bundle-again.hg
295 297 #endif
296 298
297 299 # TEST: hg status
298 300
299 301 $ hg status
300 302 $ hg diff
General Comments 0
You need to be logged in to leave comments. Login now