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