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