comparison mercurial/localrepo.py @ 7654:816b708f23af

store all heads of a branch in the branch cache All heads of branches will be stored in a new cache file 'branchheads.cache' within the .hg directory. The old 'branch.cache' file from older versions will be ignored. The new cache contents are formatted line-by-line as '{node} {branchtag}\n'. This is the same as the previous format. Now, every head is recorded in an oldest -> tipmost order. The localrepo.branchheads function is reworked to use the data from the cache.
author John Mulligan <phlogistonjohn@asynchrono.us>
date Wed, 14 Jan 2009 21:47:38 -0500
parents 182b7114d35a
children cce37dab7ad6
comparison
equal deleted inserted replaced
7653:0641fd8a4bb4 7654:816b708f23af
358 for t, n in self.tags().iteritems(): 358 for t, n in self.tags().iteritems():
359 self.nodetagscache.setdefault(n, []).append(t) 359 self.nodetagscache.setdefault(n, []).append(t)
360 return self.nodetagscache.get(node, []) 360 return self.nodetagscache.get(node, [])
361 361
362 def _branchtags(self, partial, lrev): 362 def _branchtags(self, partial, lrev):
363 # TODO: rename this function?
363 tiprev = len(self) - 1 364 tiprev = len(self) - 1
364 if lrev != tiprev: 365 if lrev != tiprev:
365 self._updatebranchcache(partial, lrev+1, tiprev+1) 366 self._updatebranchcache(partial, lrev+1, tiprev+1)
366 self._writebranchcache(partial, self.changelog.tip(), tiprev) 367 self._writebranchcache(partial, self.changelog.tip(), tiprev)
367 368
368 return partial 369 return partial
369 370
370 def branchtags(self): 371 def _branchheads(self):
371 tip = self.changelog.tip() 372 tip = self.changelog.tip()
372 if self.branchcache is not None and self._branchcachetip == tip: 373 if self.branchcache is not None and self._branchcachetip == tip:
373 return self.branchcache 374 return self.branchcache
374 375
375 oldtip = self._branchcachetip 376 oldtip = self._branchcachetip
383 else: 384 else:
384 lrev = self.changelog.rev(oldtip) 385 lrev = self.changelog.rev(oldtip)
385 partial = self._ubranchcache 386 partial = self._ubranchcache
386 387
387 self._branchtags(partial, lrev) 388 self._branchtags(partial, lrev)
389 # this private cache holds all heads (not just tips)
390 self._ubranchcache = partial
388 391
389 # the branch cache is stored on disk as UTF-8, but in the local 392 # the branch cache is stored on disk as UTF-8, but in the local
390 # charset internally 393 # charset internally
391 for k, v in partial.iteritems(): 394 for k, v in partial.iteritems():
392 self.branchcache[util.tolocal(k)] = v 395 self.branchcache[util.tolocal(k)] = v
393 self._ubranchcache = partial
394 return self.branchcache 396 return self.branchcache
397
398
399 def branchtags(self):
400 '''return a dict where branch names map to the tipmost head of
401 the branch'''
402 return dict([(k, v[-1]) for (k, v) in self._branchheads().iteritems()])
395 403
396 def _readbranchcache(self): 404 def _readbranchcache(self):
397 partial = {} 405 partial = {}
398 try: 406 try:
399 f = self.opener("branch.cache") 407 f = self.opener("branchheads.cache")
400 lines = f.read().split('\n') 408 lines = f.read().split('\n')
401 f.close() 409 f.close()
402 except (IOError, OSError): 410 except (IOError, OSError):
403 return {}, nullid, nullrev 411 return {}, nullid, nullrev
404 412
409 # invalidate the cache 417 # invalidate the cache
410 raise ValueError('invalidating branch cache (tip differs)') 418 raise ValueError('invalidating branch cache (tip differs)')
411 for l in lines: 419 for l in lines:
412 if not l: continue 420 if not l: continue
413 node, label = l.split(" ", 1) 421 node, label = l.split(" ", 1)
414 partial[label.strip()] = bin(node) 422 partial.setdefault(label.strip(), []).append(bin(node))
415 except KeyboardInterrupt: 423 except KeyboardInterrupt:
416 raise 424 raise
417 except Exception, inst: 425 except Exception, inst:
418 if self.ui.debugflag: 426 if self.ui.debugflag:
419 self.ui.warn(str(inst), '\n') 427 self.ui.warn(str(inst), '\n')
420 partial, last, lrev = {}, nullid, nullrev 428 partial, last, lrev = {}, nullid, nullrev
421 return partial, last, lrev 429 return partial, last, lrev
422 430
423 def _writebranchcache(self, branches, tip, tiprev): 431 def _writebranchcache(self, branches, tip, tiprev):
424 try: 432 try:
425 f = self.opener("branch.cache", "w", atomictemp=True) 433 f = self.opener("branchheads.cache", "w", atomictemp=True)
426 f.write("%s %s\n" % (hex(tip), tiprev)) 434 f.write("%s %s\n" % (hex(tip), tiprev))
427 for label, node in branches.iteritems(): 435 for label, nodes in branches.iteritems():
428 f.write("%s %s\n" % (hex(node), label)) 436 for node in nodes:
437 f.write("%s %s\n" % (hex(node), label))
429 f.rename() 438 f.rename()
430 except (IOError, OSError): 439 except (IOError, OSError):
431 pass 440 pass
432 441
433 def _updatebranchcache(self, partial, start, end): 442 def _updatebranchcache(self, partial, start, end):
434 for r in xrange(start, end): 443 for r in xrange(start, end):
435 c = self[r] 444 c = self[r]
436 b = c.branch() 445 b = c.branch()
437 partial[b] = c.node() 446 bheads = partial.setdefault(b, [])
447 bheads.append(c.node())
448 for p in c.parents():
449 pn = p.node()
450 if pn in bheads:
451 bheads.remove(pn)
438 452
439 def lookup(self, key): 453 def lookup(self, key):
440 if isinstance(key, int): 454 if isinstance(key, int):
441 return self.changelog.node(key) 455 return self.changelog.node(key)
442 elif key == '.': 456 elif key == '.':
1169 return [n for (r, n) in util.sort(heads)] 1183 return [n for (r, n) in util.sort(heads)]
1170 1184
1171 def branchheads(self, branch=None, start=None): 1185 def branchheads(self, branch=None, start=None):
1172 if branch is None: 1186 if branch is None:
1173 branch = self[None].branch() 1187 branch = self[None].branch()
1174 branches = self.branchtags() 1188 branches = self._branchheads()
1175 if branch not in branches: 1189 if branch not in branches:
1176 return [] 1190 return []
1177 # The basic algorithm is this: 1191 bheads = branches[branch]
1178 # 1192 # the cache returns heads ordered lowest to highest
1179 # Start from the branch tip since there are no later revisions that can 1193 bheads.reverse()
1180 # possibly be in this branch, and the tip is a guaranteed head.
1181 #
1182 # Remember the tip's parents as the first ancestors, since these by
1183 # definition are not heads.
1184 #
1185 # Step backwards from the brach tip through all the revisions. We are
1186 # guaranteed by the rules of Mercurial that we will now be visiting the
1187 # nodes in reverse topological order (children before parents).
1188 #
1189 # If a revision is one of the ancestors of a head then we can toss it
1190 # out of the ancestors set (we've already found it and won't be
1191 # visiting it again) and put its parents in the ancestors set.
1192 #
1193 # Otherwise, if a revision is in the branch it's another head, since it
1194 # wasn't in the ancestor list of an existing head. So add it to the
1195 # head list, and add its parents to the ancestor list.
1196 #
1197 # If it is not in the branch ignore it.
1198 #
1199 # Once we have a list of heads, use nodesbetween to filter out all the
1200 # heads that cannot be reached from startrev. There may be a more
1201 # efficient way to do this as part of the previous algorithm.
1202
1203 set = util.set
1204 heads = [self.changelog.rev(branches[branch])]
1205 # Don't care if ancestors contains nullrev or not.
1206 ancestors = set(self.changelog.parentrevs(heads[0]))
1207 for rev in xrange(heads[0] - 1, nullrev, -1):
1208 if rev in ancestors:
1209 ancestors.update(self.changelog.parentrevs(rev))
1210 ancestors.remove(rev)
1211 elif self[rev].branch() == branch:
1212 heads.append(rev)
1213 ancestors.update(self.changelog.parentrevs(rev))
1214 heads = [self.changelog.node(rev) for rev in heads]
1215 if start is not None: 1194 if start is not None:
1216 heads = self.changelog.nodesbetween([start], heads)[2] 1195 # filter out the heads that cannot be reached from startrev
1217 return heads 1196 bheads = self.changelog.nodesbetween([start], bheads)[2]
1197 return bheads
1218 1198
1219 def branches(self, nodes): 1199 def branches(self, nodes):
1220 if not nodes: 1200 if not nodes:
1221 nodes = [self.changelog.tip()] 1201 nodes = [self.changelog.tip()]
1222 b = [] 1202 b = []