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