##// END OF EJS Templates
show: also catch AmbiguousCommand...
Gregory Szorc -
r33207:b93df142 default
parent child Browse files
Show More
@@ -1,432 +1,432 b''
1 1 # show.py - Extension implementing `hg show`
2 2 #
3 3 # Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """unified command to show various repository information (EXPERIMENTAL)
9 9
10 10 This extension provides the :hg:`show` command, which provides a central
11 11 command for displaying commonly-accessed repository data and views of that
12 12 data.
13 13
14 14 The following config options can influence operation.
15 15
16 16 ``commands``
17 17 ------------
18 18
19 19 ``show.aliasprefix``
20 20 List of strings that will register aliases for views. e.g. ``s`` will
21 21 effectively set config options ``alias.s<view> = show <view>`` for all
22 22 views. i.e. `hg swork` would execute `hg show work`.
23 23
24 24 Aliases that would conflict with existing registrations will not be
25 25 performed.
26 26 """
27 27
28 28 from __future__ import absolute_import
29 29
30 30 from mercurial.i18n import _
31 31 from mercurial.node import nullrev
32 32 from mercurial import (
33 33 cmdutil,
34 34 commands,
35 35 destutil,
36 36 error,
37 37 formatter,
38 38 graphmod,
39 39 phases,
40 40 pycompat,
41 41 registrar,
42 42 revset,
43 43 revsetlang,
44 44 )
45 45
46 46 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
47 47 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
48 48 # be specifying the version(s) of Mercurial they are tested with, or
49 49 # leave the attribute unspecified.
50 50 testedwith = 'ships-with-hg-core'
51 51
52 52 cmdtable = {}
53 53 command = registrar.command(cmdtable)
54 54 revsetpredicate = registrar.revsetpredicate()
55 55
56 56 class showcmdfunc(registrar._funcregistrarbase):
57 57 """Register a function to be invoked for an `hg show <thing>`."""
58 58
59 59 # Used by _formatdoc().
60 60 _docformat = '%s -- %s'
61 61
62 62 def _extrasetup(self, name, func, fmtopic=None, csettopic=None):
63 63 """Called with decorator arguments to register a show view.
64 64
65 65 ``name`` is the sub-command name.
66 66
67 67 ``func`` is the function being decorated.
68 68
69 69 ``fmtopic`` is the topic in the style that will be rendered for
70 70 this view.
71 71
72 72 ``csettopic`` is the topic in the style to be used for a changeset
73 73 printer.
74 74
75 75 If ``fmtopic`` is specified, the view function will receive a
76 76 formatter instance. If ``csettopic`` is specified, the view
77 77 function will receive a changeset printer.
78 78 """
79 79 func._fmtopic = fmtopic
80 80 func._csettopic = csettopic
81 81
82 82 showview = showcmdfunc()
83 83
84 84 @command('show', [
85 85 # TODO: Switch this template flag to use cmdutil.formatteropts if
86 86 # 'hg show' becomes stable before --template/-T is stable. For now,
87 87 # we are putting it here without the '(EXPERIMENTAL)' flag because it
88 88 # is an important part of the 'hg show' user experience and the entire
89 89 # 'hg show' experience is experimental.
90 90 ('T', 'template', '', ('display with template'), _('TEMPLATE')),
91 91 ], _('VIEW'))
92 92 def show(ui, repo, view=None, template=None):
93 93 """show various repository information
94 94
95 95 A requested view of repository data is displayed.
96 96
97 97 If no view is requested, the list of available views is shown and the
98 98 command aborts.
99 99
100 100 .. note::
101 101
102 102 There are no backwards compatibility guarantees for the output of this
103 103 command. Output may change in any future Mercurial release.
104 104
105 105 Consumers wanting stable command output should specify a template via
106 106 ``-T/--template``.
107 107
108 108 List of available views:
109 109 """
110 110 if ui.plain() and not template:
111 111 hint = _('invoke with -T/--template to control output format')
112 112 raise error.Abort(_('must specify a template in plain mode'), hint=hint)
113 113
114 114 views = showview._table
115 115
116 116 if not view:
117 117 ui.pager('show')
118 118 # TODO consider using formatter here so available views can be
119 119 # rendered to custom format.
120 120 ui.write(_('available views:\n'))
121 121 ui.write('\n')
122 122
123 123 for name, func in sorted(views.items()):
124 124 ui.write(('%s\n') % func.__doc__)
125 125
126 126 ui.write('\n')
127 127 raise error.Abort(_('no view requested'),
128 128 hint=_('use "hg show VIEW" to choose a view'))
129 129
130 130 # TODO use same logic as dispatch to perform prefix matching.
131 131 if view not in views:
132 132 raise error.Abort(_('unknown view: %s') % view,
133 133 hint=_('run "hg show" to see available views'))
134 134
135 135 template = template or 'show'
136 136
137 137 fn = views[view]
138 138 ui.pager('show')
139 139
140 140 if fn._fmtopic:
141 141 fmtopic = 'show%s' % fn._fmtopic
142 142 with ui.formatter(fmtopic, {'template': template}) as fm:
143 143 return fn(ui, repo, fm)
144 144 elif fn._csettopic:
145 145 ref = 'show%s' % fn._csettopic
146 146 spec = formatter.lookuptemplate(ui, ref, template)
147 147 displayer = cmdutil.changeset_templater(ui, repo, spec, buffered=True)
148 148 return fn(ui, repo, displayer)
149 149 else:
150 150 return fn(ui, repo)
151 151
152 152 @showview('bookmarks', fmtopic='bookmarks')
153 153 def showbookmarks(ui, repo, fm):
154 154 """bookmarks and their associated changeset"""
155 155 marks = repo._bookmarks
156 156 if not len(marks):
157 157 # This is a bit hacky. Ideally, templates would have a way to
158 158 # specify an empty output, but we shouldn't corrupt JSON while
159 159 # waiting for this functionality.
160 160 if not isinstance(fm, formatter.jsonformatter):
161 161 ui.write(_('(no bookmarks set)\n'))
162 162 return
163 163
164 164 active = repo._activebookmark
165 165 longestname = max(len(b) for b in marks)
166 166 # TODO consider exposing longest shortest(node).
167 167
168 168 for bm, node in sorted(marks.items()):
169 169 fm.startitem()
170 170 fm.context(ctx=repo[node])
171 171 fm.write('bookmark', '%s', bm)
172 172 fm.write('node', fm.hexfunc(node), fm.hexfunc(node))
173 173 fm.data(active=bm == active,
174 174 longestbookmarklen=longestname)
175 175
176 176 @showview('stack', csettopic='stack')
177 177 def showstack(ui, repo, displayer):
178 178 """current line of work"""
179 179 wdirctx = repo['.']
180 180 if wdirctx.rev() == nullrev:
181 181 raise error.Abort(_('stack view only available when there is a '
182 182 'working directory'))
183 183
184 184 if wdirctx.phase() == phases.public:
185 185 ui.write(_('(empty stack; working directory parent is a published '
186 186 'changeset)\n'))
187 187 return
188 188
189 189 # TODO extract "find stack" into a function to facilitate
190 190 # customization and reuse.
191 191
192 192 baserev = destutil.stackbase(ui, repo)
193 193 basectx = None
194 194
195 195 if baserev is None:
196 196 baserev = wdirctx.rev()
197 197 stackrevs = {wdirctx.rev()}
198 198 else:
199 199 stackrevs = set(repo.revs('%d::.', baserev))
200 200
201 201 ctx = repo[baserev]
202 202 if ctx.p1().rev() != nullrev:
203 203 basectx = ctx.p1()
204 204
205 205 # And relevant descendants.
206 206 branchpointattip = False
207 207 cl = repo.changelog
208 208
209 209 for rev in cl.descendants([wdirctx.rev()]):
210 210 ctx = repo[rev]
211 211
212 212 # Will only happen if . is public.
213 213 if ctx.phase() == phases.public:
214 214 break
215 215
216 216 stackrevs.add(ctx.rev())
217 217
218 218 if len(ctx.children()) > 1:
219 219 branchpointattip = True
220 220 break
221 221
222 222 stackrevs = list(sorted(stackrevs, reverse=True))
223 223
224 224 # Find likely target heads for the current stack. These are likely
225 225 # merge or rebase targets.
226 226 if basectx:
227 227 # TODO make this customizable?
228 228 newheads = set(repo.revs('heads(%d::) - %ld - not public()',
229 229 basectx.rev(), stackrevs))
230 230 else:
231 231 newheads = set()
232 232
233 233 try:
234 234 cmdutil.findcmd('rebase', commands.table)
235 235 haverebase = True
236 except error.UnknownCommand:
236 except (error.AmbiguousCommand, error.UnknownCommand):
237 237 haverebase = False
238 238
239 239 # TODO use templating.
240 240 # TODO consider using graphmod. But it may not be necessary given
241 241 # our simplicity and the customizations required.
242 242 # TODO use proper graph symbols from graphmod
243 243
244 244 shortesttmpl = formatter.maketemplater(ui, '{shortest(node, 5)}')
245 245 def shortest(ctx):
246 246 return shortesttmpl.render({'ctx': ctx, 'node': ctx.hex()})
247 247
248 248 # We write out new heads to aid in DAG awareness and to help with decision
249 249 # making on how the stack should be reconciled with commits made since the
250 250 # branch point.
251 251 if newheads:
252 252 # Calculate distance from base so we can render the count and so we can
253 253 # sort display order by commit distance.
254 254 revdistance = {}
255 255 for head in newheads:
256 256 # There is some redundancy in DAG traversal here and therefore
257 257 # room to optimize.
258 258 ancestors = cl.ancestors([head], stoprev=basectx.rev())
259 259 revdistance[head] = len(list(ancestors))
260 260
261 261 sourcectx = repo[stackrevs[-1]]
262 262
263 263 sortedheads = sorted(newheads, key=lambda x: revdistance[x],
264 264 reverse=True)
265 265
266 266 for i, rev in enumerate(sortedheads):
267 267 ctx = repo[rev]
268 268
269 269 if i:
270 270 ui.write(': ')
271 271 else:
272 272 ui.write(' ')
273 273
274 274 ui.write(('o '))
275 275 displayer.show(ctx)
276 276 displayer.flush(ctx)
277 277 ui.write('\n')
278 278
279 279 if i:
280 280 ui.write(':/')
281 281 else:
282 282 ui.write(' /')
283 283
284 284 ui.write(' (')
285 285 ui.write(_('%d commits ahead') % revdistance[rev],
286 286 label='stack.commitdistance')
287 287
288 288 if haverebase:
289 289 # TODO may be able to omit --source in some scenarios
290 290 ui.write('; ')
291 291 ui.write(('hg rebase --source %s --dest %s' % (
292 292 shortest(sourcectx), shortest(ctx))),
293 293 label='stack.rebasehint')
294 294
295 295 ui.write(')\n')
296 296
297 297 ui.write(':\n: ')
298 298 ui.write(_('(stack head)\n'), label='stack.label')
299 299
300 300 if branchpointattip:
301 301 ui.write(' \\ / ')
302 302 ui.write(_('(multiple children)\n'), label='stack.label')
303 303 ui.write(' |\n')
304 304
305 305 for rev in stackrevs:
306 306 ctx = repo[rev]
307 307 symbol = '@' if rev == wdirctx.rev() else 'o'
308 308
309 309 if newheads:
310 310 ui.write(': ')
311 311 else:
312 312 ui.write(' ')
313 313
314 314 ui.write(symbol, ' ')
315 315 displayer.show(ctx)
316 316 displayer.flush(ctx)
317 317 ui.write('\n')
318 318
319 319 # TODO display histedit hint?
320 320
321 321 if basectx:
322 322 # Vertically and horizontally separate stack base from parent
323 323 # to reinforce stack boundary.
324 324 if newheads:
325 325 ui.write(':/ ')
326 326 else:
327 327 ui.write(' / ')
328 328
329 329 ui.write(_('(stack base)'), '\n', label='stack.label')
330 330 ui.write(('o '))
331 331
332 332 displayer.show(basectx)
333 333 displayer.flush(basectx)
334 334 ui.write('\n')
335 335
336 336 @revsetpredicate('_underway([commitage[, headage]])')
337 337 def underwayrevset(repo, subset, x):
338 338 args = revset.getargsdict(x, 'underway', 'commitage headage')
339 339 if 'commitage' not in args:
340 340 args['commitage'] = None
341 341 if 'headage' not in args:
342 342 args['headage'] = None
343 343
344 344 # We assume callers of this revset add a topographical sort on the
345 345 # result. This means there is no benefit to making the revset lazy
346 346 # since the topographical sort needs to consume all revs.
347 347 #
348 348 # With this in mind, we build up the set manually instead of constructing
349 349 # a complex revset. This enables faster execution.
350 350
351 351 # Mutable changesets (non-public) are the most important changesets
352 352 # to return. ``not public()`` will also pull in obsolete changesets if
353 353 # there is a non-obsolete changeset with obsolete ancestors. This is
354 354 # why we exclude obsolete changesets from this query.
355 355 rs = 'not public() and not obsolete()'
356 356 rsargs = []
357 357 if args['commitage']:
358 358 rs += ' and date(%s)'
359 359 rsargs.append(revsetlang.getstring(args['commitage'],
360 360 _('commitage requires a string')))
361 361
362 362 mutable = repo.revs(rs, *rsargs)
363 363 relevant = revset.baseset(mutable)
364 364
365 365 # Add parents of mutable changesets to provide context.
366 366 relevant += repo.revs('parents(%ld)', mutable)
367 367
368 368 # We also pull in (public) heads if they a) aren't closing a branch
369 369 # b) are recent.
370 370 rs = 'head() and not closed()'
371 371 rsargs = []
372 372 if args['headage']:
373 373 rs += ' and date(%s)'
374 374 rsargs.append(revsetlang.getstring(args['headage'],
375 375 _('headage requires a string')))
376 376
377 377 relevant += repo.revs(rs, *rsargs)
378 378
379 379 # Add working directory parent.
380 380 wdirrev = repo['.'].rev()
381 381 if wdirrev != nullrev:
382 382 relevant += revset.baseset({wdirrev})
383 383
384 384 return subset & relevant
385 385
386 386 @showview('work', csettopic='work')
387 387 def showwork(ui, repo, displayer):
388 388 """changesets that aren't finished"""
389 389 # TODO support date-based limiting when calling revset.
390 390 revs = repo.revs('sort(_underway(), topo)')
391 391
392 392 revdag = graphmod.dagwalker(repo, revs)
393 393
394 394 ui.setconfig('experimental', 'graphshorten', True)
395 395 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
396 396
397 397 def extsetup(ui):
398 398 # Alias `hg <prefix><view>` to `hg show <view>`.
399 399 for prefix in ui.configlist('commands', 'show.aliasprefix'):
400 400 for view in showview._table:
401 401 name = '%s%s' % (prefix, view)
402 402
403 403 choice, allcommands = cmdutil.findpossible(name, commands.table,
404 404 strict=True)
405 405
406 406 # This alias is already a command name. Don't set it.
407 407 if name in choice:
408 408 continue
409 409
410 410 # Same for aliases.
411 411 if ui.config('alias', name):
412 412 continue
413 413
414 414 ui.setconfig('alias', name, 'show %s' % view, source='show')
415 415
416 416 # Adjust the docstring of the show command so it shows all registered views.
417 417 # This is a bit hacky because it runs at the end of module load. When moved
418 418 # into core or when another extension wants to provide a view, we'll need
419 419 # to do this more robustly.
420 420 # TODO make this more robust.
421 421 def _updatedocstring():
422 422 longest = max(map(len, showview._table.keys()))
423 423 entries = []
424 424 for key in sorted(showview._table.keys()):
425 425 entries.append(pycompat.sysstr(' %s %s' % (
426 426 key.ljust(longest), showview._table[key]._origdoc)))
427 427
428 428 cmdtable['show'][0].__doc__ = pycompat.sysstr('%s\n\n%s\n ') % (
429 429 cmdtable['show'][0].__doc__.rstrip(),
430 430 pycompat.sysstr('\n\n').join(entries))
431 431
432 432 _updatedocstring()
General Comments 0
You need to be logged in to leave comments. Login now