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