comparison mercurial/subrepo.py @ 18939:aab0c14c20d0

subrepo: implement "storeclean" method for mercurial subrepos The mercurial subrepo "storeclean" method works by calculating a "store hash" of the repository state and comparing it to a cached store hash. The store hash is always cached when the repository is cloned from or pushed to a remote repository, but also on pull as long as the repository already had a clean store. If the hashes match the store is "clean" versus the selected repository. Note that this method is currenty unused, but it will be used by a later patch. The store hash is calculated by hashing several key repository files, such as the bookmarks file the phaseroots file and the changelog. Note that the hash comparison is done file by file so that we can exit early if a pair of hashes do not match. Also the hashes are calculated starting with the file that is most likely to be smaller upto the file that is more likely to be larger.
author Angel Ezquerra <angel.ezquerra@gmail.com>
date Sat, 16 Feb 2013 01:18:53 +0100
parents 9a171baa9202
children 798bdb7f1517
comparison
equal deleted inserted replaced
18938:e22107cff6bf 18939:aab0c14c20d0
444 if v: 444 if v:
445 self._repo.ui.setconfig(s, k, v) 445 self._repo.ui.setconfig(s, k, v)
446 self._repo.ui.setconfig('ui', '_usedassubrepo', 'True') 446 self._repo.ui.setconfig('ui', '_usedassubrepo', 'True')
447 self._initrepo(r, state[0], create) 447 self._initrepo(r, state[0], create)
448 448
449 def storeclean(self, path):
450 clean = True
451 lock = self._repo.lock()
452 itercache = self._calcstorehash(path)
453 try:
454 for filehash in self._readstorehashcache(path):
455 if filehash != itercache.next():
456 clean = False
457 break
458 except StopIteration:
459 # the cached and current pull states have a different size
460 clean = False
461 if clean:
462 try:
463 itercache.next()
464 # the cached and current pull states have a different size
465 clean = False
466 except StopIteration:
467 pass
468 lock.release()
469 return clean
470
471 def _calcstorehash(self, remotepath):
472 '''calculate a unique "store hash"
473
474 This method is used to to detect when there are changes that may
475 require a push to a given remote path.'''
476 # sort the files that will be hashed in increasing (likely) file size
477 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
478 yield '# %s\n' % remotepath
479 for relname in filelist:
480 absname = os.path.normpath(self._repo.join(relname))
481 yield '%s = %s\n' % (absname, _calcfilehash(absname))
482
483 def _getstorehashcachepath(self, remotepath):
484 '''get a unique path for the store hash cache'''
485 return self._repo.join(os.path.join(
486 'cache', 'storehash', _getstorehashcachename(remotepath)))
487
488 def _readstorehashcache(self, remotepath):
489 '''read the store hash cache for a given remote repository'''
490 cachefile = self._getstorehashcachepath(remotepath)
491 if not os.path.exists(cachefile):
492 return ''
493 fd = open(cachefile, 'r')
494 pullstate = fd.readlines()
495 fd.close()
496 return pullstate
497
498 def _cachestorehash(self, remotepath):
499 '''cache the current store hash
500
501 Each remote repo requires its own store hash cache, because a subrepo
502 store may be "clean" versus a given remote repo, but not versus another
503 '''
504 cachefile = self._getstorehashcachepath(remotepath)
505 lock = self._repo.lock()
506 storehash = list(self._calcstorehash(remotepath))
507 cachedir = os.path.dirname(cachefile)
508 if not os.path.exists(cachedir):
509 util.makedirs(cachedir, notindexed=True)
510 fd = open(cachefile, 'w')
511 fd.writelines(storehash)
512 fd.close()
513 lock.release()
514
449 @annotatesubrepoerror 515 @annotatesubrepoerror
450 def _initrepo(self, parentrepo, source, create): 516 def _initrepo(self, parentrepo, source, create):
451 self._repo._subparent = parentrepo 517 self._repo._subparent = parentrepo
452 self._repo._subsource = source 518 self._repo._subsource = source
453 519
562 other, cloned = hg.clone(self._repo._subparent.baseui, {}, 628 other, cloned = hg.clone(self._repo._subparent.baseui, {},
563 other, self._repo.root, 629 other, self._repo.root,
564 update=False) 630 update=False)
565 self._repo = cloned.local() 631 self._repo = cloned.local()
566 self._initrepo(parentrepo, source, create=True) 632 self._initrepo(parentrepo, source, create=True)
633 self._cachestorehash(srcurl)
567 else: 634 else:
568 self._repo.ui.status(_('pulling subrepo %s from %s\n') 635 self._repo.ui.status(_('pulling subrepo %s from %s\n')
569 % (subrelpath(self), srcurl)) 636 % (subrelpath(self), srcurl))
637 cleansub = self.storeclean(srcurl)
570 remotebookmarks = other.listkeys('bookmarks') 638 remotebookmarks = other.listkeys('bookmarks')
571 self._repo.pull(other) 639 self._repo.pull(other)
572 bookmarks.updatefromremote(self._repo.ui, self._repo, 640 bookmarks.updatefromremote(self._repo.ui, self._repo,
573 remotebookmarks, srcurl) 641 remotebookmarks, srcurl)
642 if cleansub:
643 # keep the repo clean after pull
644 self._cachestorehash(srcurl)
574 645
575 @annotatesubrepoerror 646 @annotatesubrepoerror
576 def get(self, state, overwrite=False): 647 def get(self, state, overwrite=False):
577 self._get(state) 648 self._get(state)
578 source, revision, kind = state 649 source, revision, kind = state
621 692
622 dsturl = _abssource(self._repo, True) 693 dsturl = _abssource(self._repo, True)
623 self._repo.ui.status(_('pushing subrepo %s to %s\n') % 694 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
624 (subrelpath(self), dsturl)) 695 (subrelpath(self), dsturl))
625 other = hg.peer(self._repo, {'ssh': ssh}, dsturl) 696 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
626 return self._repo.push(other, force, newbranch=newbranch) 697 res = self._repo.push(other, force, newbranch=newbranch)
698
699 # the repo is now clean
700 self._cachestorehash(dsturl)
701 return res
627 702
628 @annotatesubrepoerror 703 @annotatesubrepoerror
629 def outgoing(self, ui, dest, opts): 704 def outgoing(self, ui, dest, opts):
630 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts) 705 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
631 706