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