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