|
1 # topic/server.py - server specific behavior with topic |
|
2 # |
|
3 # This software may be used and distributed according to the terms of the |
|
4 # GNU General Public License version 2 or any later version. |
|
5 from mercurial import ( |
|
6 extensions, |
|
7 repoview, |
|
8 repoviewutil, |
|
9 wireprototypes, |
|
10 wireprotov1peer, |
|
11 wireprotov1server, |
|
12 ) |
|
13 |
|
14 from . import ( |
|
15 common, |
|
16 constants, |
|
17 ) |
|
18 |
|
19 ### Visibility restriction |
|
20 # |
|
21 # Serving draft changesets with topics to clients without topic extension can |
|
22 # confuse them, because they won't see the topic label and will consider them |
|
23 # normal anonymous heads. Instead we have the option to not serve changesets |
|
24 # with topics to clients without topic support. |
|
25 # |
|
26 # To achieve this, we alter the behavior of the standard `heads` commands and |
|
27 # introduce a new `heads` command that only clients with topic will know about. |
|
28 |
|
29 # compat version of the wireprotocommand decorator, taken from evolve compat |
|
30 |
|
31 FILTERNAME = b'served-no-topic' |
|
32 |
|
33 def computeunservedtopic(repo, visibilityexceptions=None): |
|
34 assert not repo.changelog.filteredrevs |
|
35 filteredrevs = repoview.filtertable[b'served'](repo, visibilityexceptions).copy() |
|
36 mutable = repoview.filtertable[b'immutable'](repo, visibilityexceptions) |
|
37 consider = mutable - filteredrevs |
|
38 cl = repo.changelog |
|
39 extrafiltered = set() |
|
40 for r in consider: |
|
41 if cl.changelogrevision(r).extra.get(constants.extrakey, b''): |
|
42 extrafiltered.add(r) |
|
43 if extrafiltered: |
|
44 filteredrevs = frozenset(filteredrevs | extrafiltered) |
|
45 return filteredrevs |
|
46 |
|
47 def wireprotocommand(name, args=b'', permission=b'pull'): |
|
48 try: |
|
49 from mercurial.wireprotov1server import wireprotocommand |
|
50 except (ImportError, AttributeError): |
|
51 # hg <= 4.6 (b4d85bc122bd) |
|
52 from mercurial.wireproto import wireprotocommand |
|
53 return wireprotocommand(name, args, permission=permission) |
|
54 |
|
55 def wrapheads(orig, repo, proto): |
|
56 """wrap head to hide topic^W draft changeset to old client""" |
|
57 hidetopics = repo.ui.configbool(b'experimental', b'topic.server-gate-topic-changesets') |
|
58 if common.hastopicext(repo) and hidetopics: |
|
59 h = repo.filtered(FILTERNAME).heads() |
|
60 return wireprototypes.bytesresponse(wireprototypes.encodelist(h) + b'\n') |
|
61 return orig(repo, proto) |
|
62 |
|
63 def topicheads(repo, proto): |
|
64 """Same as the normal wireprotocol command, but accessing with a different end point.""" |
|
65 h = repo.heads() |
|
66 return wireprototypes.bytesresponse(wireprototypes.encodelist(h) + b'\n') |
|
67 |
|
68 def wireprotocaps(orig, repo, proto): |
|
69 """advertise the new topic specific `head` command for client with topic""" |
|
70 caps = orig(repo, proto) |
|
71 if common.hastopicext(repo) and repo.peer().capable(b'topics'): |
|
72 caps.append(b'_exttopics_heads') |
|
73 return caps |
|
74 |
|
75 def setupserver(ui): |
|
76 extensions.wrapfunction(wireprotov1server, 'heads', wrapheads) |
|
77 wireprotov1server.commands.pop(b'heads') |
|
78 wireprotocommand(b'heads', permission=b'pull')(wireprotov1server.heads) |
|
79 wireprotocommand(b'_exttopics_heads', permission=b'pull')(topicheads) |
|
80 extensions.wrapfunction(wireprotov1server, '_capabilities', wireprotocaps) |
|
81 |
|
82 class topicpeerexecutor(wireprotov1peer.peerexecutor): |
|
83 |
|
84 def callcommand(self, command, args): |
|
85 if command == b'heads': |
|
86 if self._peer.capable(b'_exttopics_heads'): |
|
87 command = b'_exttopics_heads' |
|
88 if getattr(self._peer, '_exttopics_heads', None) is None: |
|
89 self._peer._exttopics_heads = self._peer.heads |
|
90 s = super(topicpeerexecutor, self) |
|
91 return s.callcommand(command, args) |
|
92 |
|
93 wireprotov1peer.peerexecutor = topicpeerexecutor |
|
94 |
|
95 if FILTERNAME not in repoview.filtertable: |
|
96 repoview.filtertable[FILTERNAME] = computeunservedtopic |
|
97 repoviewutil.subsettable[FILTERNAME] = b'immutable' |
|
98 repoviewutil.subsettable[b'served'] = FILTERNAME |