--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/topic/discovery.py Thu Mar 17 09:12:18 2016 -0700
@@ -0,0 +1,106 @@
+import weakref
+from mercurial import branchmap
+from mercurial import error
+from mercurial import exchange
+from mercurial.i18n import _
+from . import topicmap
+
+def _headssummary(orig, repo, remote, outgoing):
+ publishing = ('phases' not in remote.listkeys('namespaces')
+ or bool(remote.listkeys('phases').get('publishing', False)))
+ if publishing:
+ return orig(repo, remote, outgoing)
+ oldgetitem = repo.__getitem__
+ oldrepo = repo.__class__
+ oldbranchcache = branchmap.branchcache
+ oldfilename = branchmap._filename
+ try:
+ class repocls(repo.__class__):
+ def __getitem__(self, key):
+ ctx = super(repocls, self).__getitem__(key)
+ oldbranch = ctx.branch
+ def branch():
+ branch = oldbranch()
+ topic = ctx.topic()
+ if topic:
+ branch = "%s:%s" % (branch, topic)
+ return branch
+ ctx.branch = branch
+ return ctx
+ repo.__class__ = repocls
+ branchmap.branchcache = topicmap.topiccache
+ branchmap._filename = topicmap._filename
+ summary = orig(repo, remote, outgoing)
+ for key, value in summary.iteritems():
+ if ':' in key: # This is a topic
+ if value[0] is None and value[1]:
+ summary[key] = ([value[1].pop(0)], ) + value[1:]
+ return summary
+ finally:
+ repo.__class__ = oldrepo
+ branchmap.branchcache = oldbranchcache
+ branchmap._filename = oldfilename
+
+def wireprotobranchmap(orig, repo, proto):
+ oldrepo = repo.__class__
+ try:
+ class repocls(repo.__class__):
+ def branchmap(self):
+ usetopic = not self.publishing()
+ return super(repocls, self).branchmap(topic=usetopic)
+ repo.__class__ = repocls
+ return orig(repo, proto)
+ finally:
+ repo.__class__ = oldrepo
+
+
+# Discovery have deficiency around phases, branch can get new heads with pure
+# phases change. This happened with a changeset was allowed to be pushed
+# because it had a topic, but it later become public and create a new branch
+# head.
+#
+# Handle this by doing an extra check for new head creation server side
+def _nbheads(repo):
+ data = {}
+ for b in repo.branchmap().iterbranches():
+ if ':' in b[0]:
+ continue
+ data[b[0]] = len(b[1])
+ return data
+
+def handlecheckheads(orig, op, inpart):
+ orig(op, inpart)
+ if op.repo.publishing():
+ return
+ tr = op.gettransaction()
+ if tr.hookargs['source'] not in ('push', 'serve'): # not a push
+ return
+ tr._prepushheads = _nbheads(op.repo)
+ reporef = weakref.ref(op.repo)
+ oldvalidator = tr.validator
+ def validator(tr):
+ repo = reporef()
+ if repo is not None:
+ repo.invalidatecaches()
+ finalheads = _nbheads(repo)
+ for branch, oldnb in tr._prepushheads.iteritems():
+ newnb = finalheads.pop(branch, 0)
+ if oldnb < newnb:
+ msg = _('push create a new head on branch "%s"' % branch)
+ raise error.Abort(msg)
+ for branch, newnb in finalheads.iteritems():
+ if 1 < newnb:
+ msg = _('push create more than 1 head on new branch "%s"' % branch)
+ raise error.Abort(msg)
+ return oldvalidator(tr)
+ tr.validator = validator
+handlecheckheads.params = frozenset()
+
+def _pushb2phases(orig, pushop, bundler):
+ hascheck = any(p.type == 'check:heads' for p in bundler._parts)
+ if pushop.outdatedphases and not hascheck:
+ exchange._pushb2ctxcheckheads(pushop, bundler)
+ return orig(pushop, bundler)
+
+
+