def validfor(self, repo): """Is the cache content valid regarding a repo - False when cached tipnode is unknown or if we detect a strip. - True when cache is up to date or a subset of current repo.""" try: return ((self.tipnode == repo.changelog.node(self.tiprev)) and (self.filteredhash == \ scmutil.filteredhash(repo, self.tiprev))) except IndexError: return False
def _readtagcache(ui, repo): '''Read the tag cache. Returns a tuple (heads, fnodes, validinfo, cachetags, shouldwrite). If the cache is completely up-to-date, "cachetags" is a dict of the form returned by _readtags() and "heads", "fnodes", and "validinfo" are None and "shouldwrite" is False. If the cache is not up to date, "cachetags" is None. "heads" is a list of all heads currently in the repository, ordered from tip to oldest. "validinfo" is a tuple describing cache validation info. This is used when writing the tags cache. "fnodes" is a mapping from head to .hgtags filenode. "shouldwrite" is True. If the cache is not up to date, the caller is responsible for reading tag info from each returned head. (See findglobaltags().) ''' import scmutil # avoid cycle try: cachefile = repo.vfs(_filename(repo), 'r') # force reading the file for static-http cachelines = iter(cachefile) except IOError: cachefile = None cacherev = None cachenode = None cachehash = None if cachefile: try: validline = cachelines.next() validline = validline.split() cacherev = int(validline[0]) cachenode = bin(validline[1]) if len(validline) > 2: cachehash = bin(validline[2]) except Exception: # corruption of the cache, just recompute it. pass tipnode = repo.changelog.tip() tiprev = len(repo.changelog) - 1 # Case 1 (common): tip is the same, so nothing has changed. # (Unchanged tip trivially means no changesets have been added. # But, thanks to localrepository.destroyed(), it also means none # have been destroyed by strip or rollback.) if (cacherev == tiprev and cachenode == tipnode and cachehash == scmutil.filteredhash(repo, tiprev)): tags = _readtags(ui, repo, cachelines, cachefile.name) cachefile.close() return (None, None, None, tags, False) if cachefile: cachefile.close() # ignore rest of file valid = (tiprev, tipnode, scmutil.filteredhash(repo, tiprev)) repoheads = repo.heads() # Case 2 (uncommon): empty repo; get out quickly and don't bother # writing an empty cache. if repoheads == [nullid]: return ([], {}, valid, {}, False) # Case 3 (uncommon): cache file missing or empty. # Case 4 (uncommon): tip rev decreased. This should only happen # when we're called from localrepository.destroyed(). Refresh the # cache so future invocations will not see disappeared heads in the # cache. # Case 5 (common): tip has changed, so we've added/replaced heads. # As it happens, the code to handle cases 3, 4, 5 is the same. # N.B. in case 4 (nodes destroyed), "new head" really means "newly # exposed". if not len(repo.file('.hgtags')): # No tags have ever been committed, so we can avoid a # potentially expensive search. return ([], {}, valid, None, True) starttime = time.time() # Now we have to lookup the .hgtags filenode for every new head. # This is the most expensive part of finding tags, so performance # depends primarily on the size of newheads. Worst case: no cache # file, so newheads == repoheads. fnodescache = hgtagsfnodescache(repo.unfiltered()) cachefnode = {} for head in reversed(repoheads): fnode = fnodescache.getfnode(head) if fnode != nullid: cachefnode[head] = fnode fnodescache.write() duration = time.time() - starttime ui.log('tagscache', '%d/%d cache hits/lookups in %0.4f ' 'seconds\n', fnodescache.hitcount, fnodescache.lookupcount, duration) # Caller has to iterate over all heads, but can use the filenodes in # cachefnode to get to each .hgtags revision quickly. return (repoheads, cachefnode, valid, None, True)
def update(self, repo, revgen): """Given a branchhead cache, self, that may have extra nodes or be missing heads, and a generator of nodes that are strictly a superset of heads missing, this function updates self to be correct. """ starttime = time.time() cl = repo.changelog # collect new branch entries newbranches = {} getbranchinfo = repo.revbranchcache().branchinfo for r in revgen: branch, closesbranch = getbranchinfo(r) newbranches.setdefault(branch, []).append(r) if closesbranch: self._closednodes.add(cl.node(r)) # fetch current topological heads to speed up filtering topoheads = set(cl.headrevs()) # if older branchheads are reachable from new ones, they aren't # really branchheads. Note checking parents is insufficient: # 1 (branch a) -> 2 (branch b) -> 3 (branch a) for branch, newheadrevs in newbranches.iteritems(): bheads = self.setdefault(branch, []) bheadset = set(cl.rev(node) for node in bheads) # This have been tested True on all internal usage of this function. # run it again in case of doubt # assert not (set(bheadrevs) & set(newheadrevs)) newheadrevs.sort() bheadset.update(newheadrevs) # This prunes out two kinds of heads - heads that are superseded by # a head in newheadrevs, and newheadrevs that are not heads because # an existing head is their descendant. uncertain = bheadset - topoheads if uncertain: floorrev = min(uncertain) ancestors = set(cl.ancestors(newheadrevs, floorrev)) bheadset -= ancestors bheadrevs = sorted(bheadset) self[branch] = [cl.node(rev) for rev in bheadrevs] tiprev = bheadrevs[-1] if tiprev > self.tiprev: self.tipnode = cl.node(tiprev) self.tiprev = tiprev if not self.validfor(repo): # cache key are not valid anymore self.tipnode = nullid self.tiprev = nullrev for heads in self.values(): tiprev = max(cl.rev(node) for node in heads) if tiprev > self.tiprev: self.tipnode = cl.node(tiprev) self.tiprev = tiprev self.filteredhash = scmutil.filteredhash(repo, self.tiprev) duration = time.time() - starttime repo.ui.log('branchcache', 'updated %s branch cache in %.4f seconds\n', repo.filtername, duration)