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