##// END OF EJS Templates
remotenames: work around move of ABCs in collections...
Augie Fackler -
r36974:b710fdeb default
parent child Browse files
Show More
@@ -1,299 +1,305 b''
1 # remotenames.py - extension to display remotenames
1 # remotenames.py - extension to display remotenames
2 #
2 #
3 # Copyright 2017 Augie Fackler <raf@durin42.com>
3 # Copyright 2017 Augie Fackler <raf@durin42.com>
4 # Copyright 2017 Sean Farley <sean@farley.io>
4 # Copyright 2017 Sean Farley <sean@farley.io>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 """ showing remotebookmarks and remotebranches in UI
9 """ showing remotebookmarks and remotebranches in UI
10
10
11 By default both remotebookmarks and remotebranches are turned on. Config knob to
11 By default both remotebookmarks and remotebranches are turned on. Config knob to
12 control the individually are as follows.
12 control the individually are as follows.
13
13
14 Config options to tweak the default behaviour:
14 Config options to tweak the default behaviour:
15
15
16 remotenames.bookmarks
16 remotenames.bookmarks
17 Boolean value to enable or disable showing of remotebookmarks
17 Boolean value to enable or disable showing of remotebookmarks
18
18
19 remotenames.branches
19 remotenames.branches
20 Boolean value to enable or disable showing of remotebranches
20 Boolean value to enable or disable showing of remotebranches
21 """
21 """
22
22
23 from __future__ import absolute_import
23 from __future__ import absolute_import
24
24
25 import collections
26
27 from mercurial.i18n import _
25 from mercurial.i18n import _
28
26
29 from mercurial.node import (
27 from mercurial.node import (
30 bin,
28 bin,
31 )
29 )
32 from mercurial import (
30 from mercurial import (
33 logexchange,
31 logexchange,
34 namespaces,
32 namespaces,
33 pycompat,
35 registrar,
34 registrar,
36 revsetlang,
35 revsetlang,
37 smartset,
36 smartset,
38 templateutil,
37 templateutil,
39 )
38 )
40
39
40 if pycompat.ispy3:
41 import collections.abc
42 mutablemapping = collections.abc.MutableMapping
43 else:
44 import collections
45 mutablemapping = collections.MutableMapping
46
41 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
47 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
42 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
48 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
43 # be specifying the version(s) of Mercurial they are tested with, or
49 # be specifying the version(s) of Mercurial they are tested with, or
44 # leave the attribute unspecified.
50 # leave the attribute unspecified.
45 testedwith = 'ships-with-hg-core'
51 testedwith = 'ships-with-hg-core'
46
52
47 configtable = {}
53 configtable = {}
48 configitem = registrar.configitem(configtable)
54 configitem = registrar.configitem(configtable)
49 templatekeyword = registrar.templatekeyword()
55 templatekeyword = registrar.templatekeyword()
50 revsetpredicate = registrar.revsetpredicate()
56 revsetpredicate = registrar.revsetpredicate()
51
57
52 configitem('remotenames', 'bookmarks',
58 configitem('remotenames', 'bookmarks',
53 default=True,
59 default=True,
54 )
60 )
55 configitem('remotenames', 'branches',
61 configitem('remotenames', 'branches',
56 default=True,
62 default=True,
57 )
63 )
58
64
59 class lazyremotenamedict(collections.MutableMapping):
65 class lazyremotenamedict(mutablemapping):
60 """
66 """
61 Read-only dict-like Class to lazily resolve remotename entries
67 Read-only dict-like Class to lazily resolve remotename entries
62
68
63 We are doing that because remotenames startup was slow.
69 We are doing that because remotenames startup was slow.
64 We lazily read the remotenames file once to figure out the potential entries
70 We lazily read the remotenames file once to figure out the potential entries
65 and store them in self.potentialentries. Then when asked to resolve an
71 and store them in self.potentialentries. Then when asked to resolve an
66 entry, if it is not in self.potentialentries, then it isn't there, if it
72 entry, if it is not in self.potentialentries, then it isn't there, if it
67 is in self.potentialentries we resolve it and store the result in
73 is in self.potentialentries we resolve it and store the result in
68 self.cache. We cannot be lazy is when asked all the entries (keys).
74 self.cache. We cannot be lazy is when asked all the entries (keys).
69 """
75 """
70 def __init__(self, kind, repo):
76 def __init__(self, kind, repo):
71 self.cache = {}
77 self.cache = {}
72 self.potentialentries = {}
78 self.potentialentries = {}
73 self._kind = kind # bookmarks or branches
79 self._kind = kind # bookmarks or branches
74 self._repo = repo
80 self._repo = repo
75 self.loaded = False
81 self.loaded = False
76
82
77 def _load(self):
83 def _load(self):
78 """ Read the remotenames file, store entries matching selected kind """
84 """ Read the remotenames file, store entries matching selected kind """
79 self.loaded = True
85 self.loaded = True
80 repo = self._repo
86 repo = self._repo
81 for node, rpath, rname in logexchange.readremotenamefile(repo,
87 for node, rpath, rname in logexchange.readremotenamefile(repo,
82 self._kind):
88 self._kind):
83 name = rpath + '/' + rname
89 name = rpath + '/' + rname
84 self.potentialentries[name] = (node, rpath, name)
90 self.potentialentries[name] = (node, rpath, name)
85
91
86 def _resolvedata(self, potentialentry):
92 def _resolvedata(self, potentialentry):
87 """ Check that the node for potentialentry exists and return it """
93 """ Check that the node for potentialentry exists and return it """
88 if not potentialentry in self.potentialentries:
94 if not potentialentry in self.potentialentries:
89 return None
95 return None
90 node, remote, name = self.potentialentries[potentialentry]
96 node, remote, name = self.potentialentries[potentialentry]
91 repo = self._repo
97 repo = self._repo
92 binnode = bin(node)
98 binnode = bin(node)
93 # if the node doesn't exist, skip it
99 # if the node doesn't exist, skip it
94 try:
100 try:
95 repo.changelog.rev(binnode)
101 repo.changelog.rev(binnode)
96 except LookupError:
102 except LookupError:
97 return None
103 return None
98 # Skip closed branches
104 # Skip closed branches
99 if (self._kind == 'branches' and repo[binnode].closesbranch()):
105 if (self._kind == 'branches' and repo[binnode].closesbranch()):
100 return None
106 return None
101 return [binnode]
107 return [binnode]
102
108
103 def __getitem__(self, key):
109 def __getitem__(self, key):
104 if not self.loaded:
110 if not self.loaded:
105 self._load()
111 self._load()
106 val = self._fetchandcache(key)
112 val = self._fetchandcache(key)
107 if val is not None:
113 if val is not None:
108 return val
114 return val
109 else:
115 else:
110 raise KeyError()
116 raise KeyError()
111
117
112 def __iter__(self):
118 def __iter__(self):
113 return iter(self.potentialentries)
119 return iter(self.potentialentries)
114
120
115 def __len__(self):
121 def __len__(self):
116 return len(self.potentialentries)
122 return len(self.potentialentries)
117
123
118 def __setitem__(self):
124 def __setitem__(self):
119 raise NotImplementedError
125 raise NotImplementedError
120
126
121 def __delitem__(self):
127 def __delitem__(self):
122 raise NotImplementedError
128 raise NotImplementedError
123
129
124 def _fetchandcache(self, key):
130 def _fetchandcache(self, key):
125 if key in self.cache:
131 if key in self.cache:
126 return self.cache[key]
132 return self.cache[key]
127 val = self._resolvedata(key)
133 val = self._resolvedata(key)
128 if val is not None:
134 if val is not None:
129 self.cache[key] = val
135 self.cache[key] = val
130 return val
136 return val
131 else:
137 else:
132 return None
138 return None
133
139
134 def keys(self):
140 def keys(self):
135 """ Get a list of bookmark or branch names """
141 """ Get a list of bookmark or branch names """
136 if not self.loaded:
142 if not self.loaded:
137 self._load()
143 self._load()
138 return self.potentialentries.keys()
144 return self.potentialentries.keys()
139
145
140 def iteritems(self):
146 def iteritems(self):
141 """ Iterate over (name, node) tuples """
147 """ Iterate over (name, node) tuples """
142
148
143 if not self.loaded:
149 if not self.loaded:
144 self._load()
150 self._load()
145
151
146 for k, vtup in self.potentialentries.iteritems():
152 for k, vtup in self.potentialentries.iteritems():
147 yield (k, [bin(vtup[0])])
153 yield (k, [bin(vtup[0])])
148
154
149 class remotenames(object):
155 class remotenames(object):
150 """
156 """
151 This class encapsulates all the remotenames state. It also contains
157 This class encapsulates all the remotenames state. It also contains
152 methods to access that state in convenient ways. Remotenames are lazy
158 methods to access that state in convenient ways. Remotenames are lazy
153 loaded. Whenever client code needs to ensure the freshest copy of
159 loaded. Whenever client code needs to ensure the freshest copy of
154 remotenames, use the `clearnames` method to force an eventual load.
160 remotenames, use the `clearnames` method to force an eventual load.
155 """
161 """
156
162
157 def __init__(self, repo, *args):
163 def __init__(self, repo, *args):
158 self._repo = repo
164 self._repo = repo
159 self.clearnames()
165 self.clearnames()
160
166
161 def clearnames(self):
167 def clearnames(self):
162 """ Clear all remote names state """
168 """ Clear all remote names state """
163 self.bookmarks = lazyremotenamedict("bookmarks", self._repo)
169 self.bookmarks = lazyremotenamedict("bookmarks", self._repo)
164 self.branches = lazyremotenamedict("branches", self._repo)
170 self.branches = lazyremotenamedict("branches", self._repo)
165 self._invalidatecache()
171 self._invalidatecache()
166
172
167 def _invalidatecache(self):
173 def _invalidatecache(self):
168 self._nodetobmarks = None
174 self._nodetobmarks = None
169 self._nodetobranch = None
175 self._nodetobranch = None
170
176
171 def bmarktonodes(self):
177 def bmarktonodes(self):
172 return self.bookmarks
178 return self.bookmarks
173
179
174 def nodetobmarks(self):
180 def nodetobmarks(self):
175 if not self._nodetobmarks:
181 if not self._nodetobmarks:
176 bmarktonodes = self.bmarktonodes()
182 bmarktonodes = self.bmarktonodes()
177 self._nodetobmarks = {}
183 self._nodetobmarks = {}
178 for name, node in bmarktonodes.iteritems():
184 for name, node in bmarktonodes.iteritems():
179 self._nodetobmarks.setdefault(node[0], []).append(name)
185 self._nodetobmarks.setdefault(node[0], []).append(name)
180 return self._nodetobmarks
186 return self._nodetobmarks
181
187
182 def branchtonodes(self):
188 def branchtonodes(self):
183 return self.branches
189 return self.branches
184
190
185 def nodetobranch(self):
191 def nodetobranch(self):
186 if not self._nodetobranch:
192 if not self._nodetobranch:
187 branchtonodes = self.branchtonodes()
193 branchtonodes = self.branchtonodes()
188 self._nodetobranch = {}
194 self._nodetobranch = {}
189 for name, nodes in branchtonodes.iteritems():
195 for name, nodes in branchtonodes.iteritems():
190 for node in nodes:
196 for node in nodes:
191 self._nodetobranch.setdefault(node, []).append(name)
197 self._nodetobranch.setdefault(node, []).append(name)
192 return self._nodetobranch
198 return self._nodetobranch
193
199
194 def reposetup(ui, repo):
200 def reposetup(ui, repo):
195 if not repo.local():
201 if not repo.local():
196 return
202 return
197
203
198 repo._remotenames = remotenames(repo)
204 repo._remotenames = remotenames(repo)
199 ns = namespaces.namespace
205 ns = namespaces.namespace
200
206
201 if ui.configbool('remotenames', 'bookmarks'):
207 if ui.configbool('remotenames', 'bookmarks'):
202 remotebookmarkns = ns(
208 remotebookmarkns = ns(
203 'remotebookmarks',
209 'remotebookmarks',
204 templatename='remotebookmarks',
210 templatename='remotebookmarks',
205 colorname='remotebookmark',
211 colorname='remotebookmark',
206 logfmt='remote bookmark: %s\n',
212 logfmt='remote bookmark: %s\n',
207 listnames=lambda repo: repo._remotenames.bmarktonodes().keys(),
213 listnames=lambda repo: repo._remotenames.bmarktonodes().keys(),
208 namemap=lambda repo, name:
214 namemap=lambda repo, name:
209 repo._remotenames.bmarktonodes().get(name, []),
215 repo._remotenames.bmarktonodes().get(name, []),
210 nodemap=lambda repo, node:
216 nodemap=lambda repo, node:
211 repo._remotenames.nodetobmarks().get(node, []))
217 repo._remotenames.nodetobmarks().get(node, []))
212 repo.names.addnamespace(remotebookmarkns)
218 repo.names.addnamespace(remotebookmarkns)
213
219
214 if ui.configbool('remotenames', 'branches'):
220 if ui.configbool('remotenames', 'branches'):
215 remotebranchns = ns(
221 remotebranchns = ns(
216 'remotebranches',
222 'remotebranches',
217 templatename='remotebranches',
223 templatename='remotebranches',
218 colorname='remotebranch',
224 colorname='remotebranch',
219 logfmt='remote branch: %s\n',
225 logfmt='remote branch: %s\n',
220 listnames = lambda repo: repo._remotenames.branchtonodes().keys(),
226 listnames = lambda repo: repo._remotenames.branchtonodes().keys(),
221 namemap = lambda repo, name:
227 namemap = lambda repo, name:
222 repo._remotenames.branchtonodes().get(name, []),
228 repo._remotenames.branchtonodes().get(name, []),
223 nodemap = lambda repo, node:
229 nodemap = lambda repo, node:
224 repo._remotenames.nodetobranch().get(node, []))
230 repo._remotenames.nodetobranch().get(node, []))
225 repo.names.addnamespace(remotebranchns)
231 repo.names.addnamespace(remotebranchns)
226
232
227 @templatekeyword('remotenames', requires={'repo', 'ctx', 'templ'})
233 @templatekeyword('remotenames', requires={'repo', 'ctx', 'templ'})
228 def remotenameskw(context, mapping):
234 def remotenameskw(context, mapping):
229 """List of strings. Remote names associated with the changeset."""
235 """List of strings. Remote names associated with the changeset."""
230 repo = context.resource(mapping, 'repo')
236 repo = context.resource(mapping, 'repo')
231 ctx = context.resource(mapping, 'ctx')
237 ctx = context.resource(mapping, 'ctx')
232
238
233 remotenames = []
239 remotenames = []
234 if 'remotebookmarks' in repo.names:
240 if 'remotebookmarks' in repo.names:
235 remotenames = repo.names['remotebookmarks'].names(repo, ctx.node())
241 remotenames = repo.names['remotebookmarks'].names(repo, ctx.node())
236
242
237 if 'remotebranches' in repo.names:
243 if 'remotebranches' in repo.names:
238 remotenames += repo.names['remotebranches'].names(repo, ctx.node())
244 remotenames += repo.names['remotebranches'].names(repo, ctx.node())
239
245
240 return templateutil.compatlist(context, mapping, 'remotename', remotenames,
246 return templateutil.compatlist(context, mapping, 'remotename', remotenames,
241 plural='remotenames')
247 plural='remotenames')
242
248
243 @templatekeyword('remotebookmarks', requires={'repo', 'ctx', 'templ'})
249 @templatekeyword('remotebookmarks', requires={'repo', 'ctx', 'templ'})
244 def remotebookmarkskw(context, mapping):
250 def remotebookmarkskw(context, mapping):
245 """List of strings. Remote bookmarks associated with the changeset."""
251 """List of strings. Remote bookmarks associated with the changeset."""
246 repo = context.resource(mapping, 'repo')
252 repo = context.resource(mapping, 'repo')
247 ctx = context.resource(mapping, 'ctx')
253 ctx = context.resource(mapping, 'ctx')
248
254
249 remotebmarks = []
255 remotebmarks = []
250 if 'remotebookmarks' in repo.names:
256 if 'remotebookmarks' in repo.names:
251 remotebmarks = repo.names['remotebookmarks'].names(repo, ctx.node())
257 remotebmarks = repo.names['remotebookmarks'].names(repo, ctx.node())
252
258
253 return templateutil.compatlist(context, mapping, 'remotebookmark',
259 return templateutil.compatlist(context, mapping, 'remotebookmark',
254 remotebmarks, plural='remotebookmarks')
260 remotebmarks, plural='remotebookmarks')
255
261
256 @templatekeyword('remotebranches', requires={'repo', 'ctx', 'templ'})
262 @templatekeyword('remotebranches', requires={'repo', 'ctx', 'templ'})
257 def remotebrancheskw(context, mapping):
263 def remotebrancheskw(context, mapping):
258 """List of strings. Remote branches associated with the changeset."""
264 """List of strings. Remote branches associated with the changeset."""
259 repo = context.resource(mapping, 'repo')
265 repo = context.resource(mapping, 'repo')
260 ctx = context.resource(mapping, 'ctx')
266 ctx = context.resource(mapping, 'ctx')
261
267
262 remotebranches = []
268 remotebranches = []
263 if 'remotebranches' in repo.names:
269 if 'remotebranches' in repo.names:
264 remotebranches = repo.names['remotebranches'].names(repo, ctx.node())
270 remotebranches = repo.names['remotebranches'].names(repo, ctx.node())
265
271
266 return templateutil.compatlist(context, mapping, 'remotebranch',
272 return templateutil.compatlist(context, mapping, 'remotebranch',
267 remotebranches, plural='remotebranches')
273 remotebranches, plural='remotebranches')
268
274
269 def _revsetutil(repo, subset, x, rtypes):
275 def _revsetutil(repo, subset, x, rtypes):
270 """utility function to return a set of revs based on the rtypes"""
276 """utility function to return a set of revs based on the rtypes"""
271
277
272 revs = set()
278 revs = set()
273 cl = repo.changelog
279 cl = repo.changelog
274 for rtype in rtypes:
280 for rtype in rtypes:
275 if rtype in repo.names:
281 if rtype in repo.names:
276 ns = repo.names[rtype]
282 ns = repo.names[rtype]
277 for name in ns.listnames(repo):
283 for name in ns.listnames(repo):
278 revs.update(ns.nodes(repo, name))
284 revs.update(ns.nodes(repo, name))
279
285
280 results = (cl.rev(n) for n in revs if cl.hasnode(n))
286 results = (cl.rev(n) for n in revs if cl.hasnode(n))
281 return subset & smartset.baseset(sorted(results))
287 return subset & smartset.baseset(sorted(results))
282
288
283 @revsetpredicate('remotenames()')
289 @revsetpredicate('remotenames()')
284 def remotenamesrevset(repo, subset, x):
290 def remotenamesrevset(repo, subset, x):
285 """All changesets which have a remotename on them."""
291 """All changesets which have a remotename on them."""
286 revsetlang.getargs(x, 0, 0, _("remotenames takes no arguments"))
292 revsetlang.getargs(x, 0, 0, _("remotenames takes no arguments"))
287 return _revsetutil(repo, subset, x, ('remotebookmarks', 'remotebranches'))
293 return _revsetutil(repo, subset, x, ('remotebookmarks', 'remotebranches'))
288
294
289 @revsetpredicate('remotebranches()')
295 @revsetpredicate('remotebranches()')
290 def remotebranchesrevset(repo, subset, x):
296 def remotebranchesrevset(repo, subset, x):
291 """All changesets which are branch heads on remotes."""
297 """All changesets which are branch heads on remotes."""
292 revsetlang.getargs(x, 0, 0, _("remotebranches takes no arguments"))
298 revsetlang.getargs(x, 0, 0, _("remotebranches takes no arguments"))
293 return _revsetutil(repo, subset, x, ('remotebranches',))
299 return _revsetutil(repo, subset, x, ('remotebranches',))
294
300
295 @revsetpredicate('remotebookmarks()')
301 @revsetpredicate('remotebookmarks()')
296 def remotebmarksrevset(repo, subset, x):
302 def remotebmarksrevset(repo, subset, x):
297 """All changesets which have bookmarks on remotes."""
303 """All changesets which have bookmarks on remotes."""
298 revsetlang.getargs(x, 0, 0, _("remotebookmarks takes no arguments"))
304 revsetlang.getargs(x, 0, 0, _("remotebookmarks takes no arguments"))
299 return _revsetutil(repo, subset, x, ('remotebookmarks',))
305 return _revsetutil(repo, subset, x, ('remotebookmarks',))
General Comments 0
You need to be logged in to leave comments. Login now