|
1 import weakref |
|
2 from mercurial import branchmap |
|
3 from mercurial import error |
|
4 from mercurial import exchange |
|
5 from mercurial.i18n import _ |
|
6 from . import topicmap |
|
7 |
|
8 def _headssummary(orig, repo, remote, outgoing): |
|
9 publishing = ('phases' not in remote.listkeys('namespaces') |
|
10 or bool(remote.listkeys('phases').get('publishing', False))) |
|
11 if publishing: |
|
12 return orig(repo, remote, outgoing) |
|
13 oldgetitem = repo.__getitem__ |
|
14 oldrepo = repo.__class__ |
|
15 oldbranchcache = branchmap.branchcache |
|
16 oldfilename = branchmap._filename |
|
17 try: |
|
18 class repocls(repo.__class__): |
|
19 def __getitem__(self, key): |
|
20 ctx = super(repocls, self).__getitem__(key) |
|
21 oldbranch = ctx.branch |
|
22 def branch(): |
|
23 branch = oldbranch() |
|
24 topic = ctx.topic() |
|
25 if topic: |
|
26 branch = "%s:%s" % (branch, topic) |
|
27 return branch |
|
28 ctx.branch = branch |
|
29 return ctx |
|
30 repo.__class__ = repocls |
|
31 branchmap.branchcache = topicmap.topiccache |
|
32 branchmap._filename = topicmap._filename |
|
33 summary = orig(repo, remote, outgoing) |
|
34 for key, value in summary.iteritems(): |
|
35 if ':' in key: # This is a topic |
|
36 if value[0] is None and value[1]: |
|
37 summary[key] = ([value[1].pop(0)], ) + value[1:] |
|
38 return summary |
|
39 finally: |
|
40 repo.__class__ = oldrepo |
|
41 branchmap.branchcache = oldbranchcache |
|
42 branchmap._filename = oldfilename |
|
43 |
|
44 def wireprotobranchmap(orig, repo, proto): |
|
45 oldrepo = repo.__class__ |
|
46 try: |
|
47 class repocls(repo.__class__): |
|
48 def branchmap(self): |
|
49 usetopic = not self.publishing() |
|
50 return super(repocls, self).branchmap(topic=usetopic) |
|
51 repo.__class__ = repocls |
|
52 return orig(repo, proto) |
|
53 finally: |
|
54 repo.__class__ = oldrepo |
|
55 |
|
56 |
|
57 # Discovery have deficiency around phases, branch can get new heads with pure |
|
58 # phases change. This happened with a changeset was allowed to be pushed |
|
59 # because it had a topic, but it later become public and create a new branch |
|
60 # head. |
|
61 # |
|
62 # Handle this by doing an extra check for new head creation server side |
|
63 def _nbheads(repo): |
|
64 data = {} |
|
65 for b in repo.branchmap().iterbranches(): |
|
66 if ':' in b[0]: |
|
67 continue |
|
68 data[b[0]] = len(b[1]) |
|
69 return data |
|
70 |
|
71 def handlecheckheads(orig, op, inpart): |
|
72 orig(op, inpart) |
|
73 if op.repo.publishing(): |
|
74 return |
|
75 tr = op.gettransaction() |
|
76 if tr.hookargs['source'] not in ('push', 'serve'): # not a push |
|
77 return |
|
78 tr._prepushheads = _nbheads(op.repo) |
|
79 reporef = weakref.ref(op.repo) |
|
80 oldvalidator = tr.validator |
|
81 def validator(tr): |
|
82 repo = reporef() |
|
83 if repo is not None: |
|
84 repo.invalidatecaches() |
|
85 finalheads = _nbheads(repo) |
|
86 for branch, oldnb in tr._prepushheads.iteritems(): |
|
87 newnb = finalheads.pop(branch, 0) |
|
88 if oldnb < newnb: |
|
89 msg = _('push create a new head on branch "%s"' % branch) |
|
90 raise error.Abort(msg) |
|
91 for branch, newnb in finalheads.iteritems(): |
|
92 if 1 < newnb: |
|
93 msg = _('push create more than 1 head on new branch "%s"' % branch) |
|
94 raise error.Abort(msg) |
|
95 return oldvalidator(tr) |
|
96 tr.validator = validator |
|
97 handlecheckheads.params = frozenset() |
|
98 |
|
99 def _pushb2phases(orig, pushop, bundler): |
|
100 hascheck = any(p.type == 'check:heads' for p in bundler._parts) |
|
101 if pushop.outdatedphases and not hascheck: |
|
102 exchange._pushb2ctxcheckheads(pushop, bundler) |
|
103 return orig(pushop, bundler) |
|
104 |
|
105 |
|
106 |