##// END OF EJS Templates
py3: store _origdoc as str...
Martin von Zweigbergk -
r42799:83666f01 default
parent child Browse files
Show More
@@ -1,470 +1,470
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 (
31 from mercurial.node import (
32 nullrev,
32 nullrev,
33 )
33 )
34 from mercurial import (
34 from mercurial import (
35 cmdutil,
35 cmdutil,
36 commands,
36 commands,
37 destutil,
37 destutil,
38 error,
38 error,
39 formatter,
39 formatter,
40 graphmod,
40 graphmod,
41 logcmdutil,
41 logcmdutil,
42 phases,
42 phases,
43 pycompat,
43 pycompat,
44 registrar,
44 registrar,
45 revset,
45 revset,
46 revsetlang,
46 revsetlang,
47 scmutil,
47 scmutil,
48 )
48 )
49
49
50 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
50 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
51 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
51 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
52 # be specifying the version(s) of Mercurial they are tested with, or
52 # be specifying the version(s) of Mercurial they are tested with, or
53 # leave the attribute unspecified.
53 # leave the attribute unspecified.
54 testedwith = 'ships-with-hg-core'
54 testedwith = 'ships-with-hg-core'
55
55
56 cmdtable = {}
56 cmdtable = {}
57 command = registrar.command(cmdtable)
57 command = registrar.command(cmdtable)
58
58
59 revsetpredicate = registrar.revsetpredicate()
59 revsetpredicate = registrar.revsetpredicate()
60
60
61 class showcmdfunc(registrar._funcregistrarbase):
61 class showcmdfunc(registrar._funcregistrarbase):
62 """Register a function to be invoked for an `hg show <thing>`."""
62 """Register a function to be invoked for an `hg show <thing>`."""
63
63
64 # Used by _formatdoc().
64 # Used by _formatdoc().
65 _docformat = '%s -- %s'
65 _docformat = '%s -- %s'
66
66
67 def _extrasetup(self, name, func, fmtopic=None, csettopic=None):
67 def _extrasetup(self, name, func, fmtopic=None, csettopic=None):
68 """Called with decorator arguments to register a show view.
68 """Called with decorator arguments to register a show view.
69
69
70 ``name`` is the sub-command name.
70 ``name`` is the sub-command name.
71
71
72 ``func`` is the function being decorated.
72 ``func`` is the function being decorated.
73
73
74 ``fmtopic`` is the topic in the style that will be rendered for
74 ``fmtopic`` is the topic in the style that will be rendered for
75 this view.
75 this view.
76
76
77 ``csettopic`` is the topic in the style to be used for a changeset
77 ``csettopic`` is the topic in the style to be used for a changeset
78 printer.
78 printer.
79
79
80 If ``fmtopic`` is specified, the view function will receive a
80 If ``fmtopic`` is specified, the view function will receive a
81 formatter instance. If ``csettopic`` is specified, the view
81 formatter instance. If ``csettopic`` is specified, the view
82 function will receive a changeset printer.
82 function will receive a changeset printer.
83 """
83 """
84 func._fmtopic = fmtopic
84 func._fmtopic = fmtopic
85 func._csettopic = csettopic
85 func._csettopic = csettopic
86
86
87 showview = showcmdfunc()
87 showview = showcmdfunc()
88
88
89 @command('show', [
89 @command('show', [
90 # TODO: Switch this template flag to use cmdutil.formatteropts if
90 # TODO: Switch this template flag to use cmdutil.formatteropts if
91 # 'hg show' becomes stable before --template/-T is stable. For now,
91 # 'hg show' becomes stable before --template/-T is stable. For now,
92 # we are putting it here without the '(EXPERIMENTAL)' flag because it
92 # we are putting it here without the '(EXPERIMENTAL)' flag because it
93 # is an important part of the 'hg show' user experience and the entire
93 # is an important part of the 'hg show' user experience and the entire
94 # 'hg show' experience is experimental.
94 # 'hg show' experience is experimental.
95 ('T', 'template', '', ('display with template'), _('TEMPLATE')),
95 ('T', 'template', '', ('display with template'), _('TEMPLATE')),
96 ], _('VIEW'),
96 ], _('VIEW'),
97 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
97 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
98 def show(ui, repo, view=None, template=None):
98 def show(ui, repo, view=None, template=None):
99 """show various repository information
99 """show various repository information
100
100
101 A requested view of repository data is displayed.
101 A requested view of repository data is displayed.
102
102
103 If no view is requested, the list of available views is shown and the
103 If no view is requested, the list of available views is shown and the
104 command aborts.
104 command aborts.
105
105
106 .. note::
106 .. note::
107
107
108 There are no backwards compatibility guarantees for the output of this
108 There are no backwards compatibility guarantees for the output of this
109 command. Output may change in any future Mercurial release.
109 command. Output may change in any future Mercurial release.
110
110
111 Consumers wanting stable command output should specify a template via
111 Consumers wanting stable command output should specify a template via
112 ``-T/--template``.
112 ``-T/--template``.
113
113
114 List of available views:
114 List of available views:
115 """
115 """
116 if ui.plain() and not template:
116 if ui.plain() and not template:
117 hint = _('invoke with -T/--template to control output format')
117 hint = _('invoke with -T/--template to control output format')
118 raise error.Abort(_('must specify a template in plain mode'), hint=hint)
118 raise error.Abort(_('must specify a template in plain mode'), hint=hint)
119
119
120 views = showview._table
120 views = showview._table
121
121
122 if not view:
122 if not view:
123 ui.pager('show')
123 ui.pager('show')
124 # TODO consider using formatter here so available views can be
124 # TODO consider using formatter here so available views can be
125 # rendered to custom format.
125 # rendered to custom format.
126 ui.write(_('available views:\n'))
126 ui.write(_('available views:\n'))
127 ui.write('\n')
127 ui.write('\n')
128
128
129 for name, func in sorted(views.items()):
129 for name, func in sorted(views.items()):
130 ui.write(('%s\n') % pycompat.sysbytes(func.__doc__))
130 ui.write(('%s\n') % pycompat.sysbytes(func.__doc__))
131
131
132 ui.write('\n')
132 ui.write('\n')
133 raise error.Abort(_('no view requested'),
133 raise error.Abort(_('no view requested'),
134 hint=_('use "hg show VIEW" to choose a view'))
134 hint=_('use "hg show VIEW" to choose a view'))
135
135
136 # TODO use same logic as dispatch to perform prefix matching.
136 # TODO use same logic as dispatch to perform prefix matching.
137 if view not in views:
137 if view not in views:
138 raise error.Abort(_('unknown view: %s') % view,
138 raise error.Abort(_('unknown view: %s') % view,
139 hint=_('run "hg show" to see available views'))
139 hint=_('run "hg show" to see available views'))
140
140
141 template = template or 'show'
141 template = template or 'show'
142
142
143 fn = views[view]
143 fn = views[view]
144 ui.pager('show')
144 ui.pager('show')
145
145
146 if fn._fmtopic:
146 if fn._fmtopic:
147 fmtopic = 'show%s' % fn._fmtopic
147 fmtopic = 'show%s' % fn._fmtopic
148 with ui.formatter(fmtopic, {'template': template}) as fm:
148 with ui.formatter(fmtopic, {'template': template}) as fm:
149 return fn(ui, repo, fm)
149 return fn(ui, repo, fm)
150 elif fn._csettopic:
150 elif fn._csettopic:
151 ref = 'show%s' % fn._csettopic
151 ref = 'show%s' % fn._csettopic
152 spec = formatter.lookuptemplate(ui, ref, template)
152 spec = formatter.lookuptemplate(ui, ref, template)
153 displayer = logcmdutil.changesettemplater(ui, repo, spec, buffered=True)
153 displayer = logcmdutil.changesettemplater(ui, repo, spec, buffered=True)
154 return fn(ui, repo, displayer)
154 return fn(ui, repo, displayer)
155 else:
155 else:
156 return fn(ui, repo)
156 return fn(ui, repo)
157
157
158 @showview('bookmarks', fmtopic='bookmarks')
158 @showview('bookmarks', fmtopic='bookmarks')
159 def showbookmarks(ui, repo, fm):
159 def showbookmarks(ui, repo, fm):
160 """bookmarks and their associated changeset"""
160 """bookmarks and their associated changeset"""
161 marks = repo._bookmarks
161 marks = repo._bookmarks
162 if not len(marks):
162 if not len(marks):
163 # This is a bit hacky. Ideally, templates would have a way to
163 # This is a bit hacky. Ideally, templates would have a way to
164 # specify an empty output, but we shouldn't corrupt JSON while
164 # specify an empty output, but we shouldn't corrupt JSON while
165 # waiting for this functionality.
165 # waiting for this functionality.
166 if not isinstance(fm, formatter.jsonformatter):
166 if not isinstance(fm, formatter.jsonformatter):
167 ui.write(_('(no bookmarks set)\n'))
167 ui.write(_('(no bookmarks set)\n'))
168 return
168 return
169
169
170 revs = [repo[node].rev() for node in marks.values()]
170 revs = [repo[node].rev() for node in marks.values()]
171 active = repo._activebookmark
171 active = repo._activebookmark
172 longestname = max(len(b) for b in marks)
172 longestname = max(len(b) for b in marks)
173 nodelen = longestshortest(repo, revs)
173 nodelen = longestshortest(repo, revs)
174
174
175 for bm, node in sorted(marks.items()):
175 for bm, node in sorted(marks.items()):
176 fm.startitem()
176 fm.startitem()
177 fm.context(ctx=repo[node])
177 fm.context(ctx=repo[node])
178 fm.write('bookmark', '%s', bm)
178 fm.write('bookmark', '%s', bm)
179 fm.write('node', fm.hexfunc(node), fm.hexfunc(node))
179 fm.write('node', fm.hexfunc(node), fm.hexfunc(node))
180 fm.data(active=bm == active,
180 fm.data(active=bm == active,
181 longestbookmarklen=longestname,
181 longestbookmarklen=longestname,
182 nodelen=nodelen)
182 nodelen=nodelen)
183
183
184 @showview('stack', csettopic='stack')
184 @showview('stack', csettopic='stack')
185 def showstack(ui, repo, displayer):
185 def showstack(ui, repo, displayer):
186 """current line of work"""
186 """current line of work"""
187 wdirctx = repo['.']
187 wdirctx = repo['.']
188 if wdirctx.rev() == nullrev:
188 if wdirctx.rev() == nullrev:
189 raise error.Abort(_('stack view only available when there is a '
189 raise error.Abort(_('stack view only available when there is a '
190 'working directory'))
190 'working directory'))
191
191
192 if wdirctx.phase() == phases.public:
192 if wdirctx.phase() == phases.public:
193 ui.write(_('(empty stack; working directory parent is a published '
193 ui.write(_('(empty stack; working directory parent is a published '
194 'changeset)\n'))
194 'changeset)\n'))
195 return
195 return
196
196
197 # TODO extract "find stack" into a function to facilitate
197 # TODO extract "find stack" into a function to facilitate
198 # customization and reuse.
198 # customization and reuse.
199
199
200 baserev = destutil.stackbase(ui, repo)
200 baserev = destutil.stackbase(ui, repo)
201 basectx = None
201 basectx = None
202
202
203 if baserev is None:
203 if baserev is None:
204 baserev = wdirctx.rev()
204 baserev = wdirctx.rev()
205 stackrevs = {wdirctx.rev()}
205 stackrevs = {wdirctx.rev()}
206 else:
206 else:
207 stackrevs = set(repo.revs('%d::.', baserev))
207 stackrevs = set(repo.revs('%d::.', baserev))
208
208
209 ctx = repo[baserev]
209 ctx = repo[baserev]
210 if ctx.p1().rev() != nullrev:
210 if ctx.p1().rev() != nullrev:
211 basectx = ctx.p1()
211 basectx = ctx.p1()
212
212
213 # And relevant descendants.
213 # And relevant descendants.
214 branchpointattip = False
214 branchpointattip = False
215 cl = repo.changelog
215 cl = repo.changelog
216
216
217 for rev in cl.descendants([wdirctx.rev()]):
217 for rev in cl.descendants([wdirctx.rev()]):
218 ctx = repo[rev]
218 ctx = repo[rev]
219
219
220 # Will only happen if . is public.
220 # Will only happen if . is public.
221 if ctx.phase() == phases.public:
221 if ctx.phase() == phases.public:
222 break
222 break
223
223
224 stackrevs.add(ctx.rev())
224 stackrevs.add(ctx.rev())
225
225
226 # ctx.children() within a function iterating on descandants
226 # ctx.children() within a function iterating on descandants
227 # potentially has severe performance concerns because revlog.children()
227 # potentially has severe performance concerns because revlog.children()
228 # iterates over all revisions after ctx's node. However, the number of
228 # iterates over all revisions after ctx's node. However, the number of
229 # draft changesets should be a reasonably small number. So even if
229 # draft changesets should be a reasonably small number. So even if
230 # this is quadratic, the perf impact should be minimal.
230 # this is quadratic, the perf impact should be minimal.
231 if len(ctx.children()) > 1:
231 if len(ctx.children()) > 1:
232 branchpointattip = True
232 branchpointattip = True
233 break
233 break
234
234
235 stackrevs = list(sorted(stackrevs, reverse=True))
235 stackrevs = list(sorted(stackrevs, reverse=True))
236
236
237 # Find likely target heads for the current stack. These are likely
237 # Find likely target heads for the current stack. These are likely
238 # merge or rebase targets.
238 # merge or rebase targets.
239 if basectx:
239 if basectx:
240 # TODO make this customizable?
240 # TODO make this customizable?
241 newheads = set(repo.revs('heads(%d::) - %ld - not public()',
241 newheads = set(repo.revs('heads(%d::) - %ld - not public()',
242 basectx.rev(), stackrevs))
242 basectx.rev(), stackrevs))
243 else:
243 else:
244 newheads = set()
244 newheads = set()
245
245
246 allrevs = set(stackrevs) | newheads | {baserev}
246 allrevs = set(stackrevs) | newheads | {baserev}
247 nodelen = longestshortest(repo, allrevs)
247 nodelen = longestshortest(repo, allrevs)
248
248
249 try:
249 try:
250 cmdutil.findcmd('rebase', commands.table)
250 cmdutil.findcmd('rebase', commands.table)
251 haverebase = True
251 haverebase = True
252 except (error.AmbiguousCommand, error.UnknownCommand):
252 except (error.AmbiguousCommand, error.UnknownCommand):
253 haverebase = False
253 haverebase = False
254
254
255 # TODO use templating.
255 # TODO use templating.
256 # TODO consider using graphmod. But it may not be necessary given
256 # TODO consider using graphmod. But it may not be necessary given
257 # our simplicity and the customizations required.
257 # our simplicity and the customizations required.
258 # TODO use proper graph symbols from graphmod
258 # TODO use proper graph symbols from graphmod
259
259
260 tres = formatter.templateresources(ui, repo)
260 tres = formatter.templateresources(ui, repo)
261 shortesttmpl = formatter.maketemplater(ui, '{shortest(node, %d)}' % nodelen,
261 shortesttmpl = formatter.maketemplater(ui, '{shortest(node, %d)}' % nodelen,
262 resources=tres)
262 resources=tres)
263 def shortest(ctx):
263 def shortest(ctx):
264 return shortesttmpl.renderdefault({'ctx': ctx, 'node': ctx.hex()})
264 return shortesttmpl.renderdefault({'ctx': ctx, 'node': ctx.hex()})
265
265
266 # We write out new heads to aid in DAG awareness and to help with decision
266 # We write out new heads to aid in DAG awareness and to help with decision
267 # making on how the stack should be reconciled with commits made since the
267 # making on how the stack should be reconciled with commits made since the
268 # branch point.
268 # branch point.
269 if newheads:
269 if newheads:
270 # Calculate distance from base so we can render the count and so we can
270 # Calculate distance from base so we can render the count and so we can
271 # sort display order by commit distance.
271 # sort display order by commit distance.
272 revdistance = {}
272 revdistance = {}
273 for head in newheads:
273 for head in newheads:
274 # There is some redundancy in DAG traversal here and therefore
274 # There is some redundancy in DAG traversal here and therefore
275 # room to optimize.
275 # room to optimize.
276 ancestors = cl.ancestors([head], stoprev=basectx.rev())
276 ancestors = cl.ancestors([head], stoprev=basectx.rev())
277 revdistance[head] = len(list(ancestors))
277 revdistance[head] = len(list(ancestors))
278
278
279 sourcectx = repo[stackrevs[-1]]
279 sourcectx = repo[stackrevs[-1]]
280
280
281 sortedheads = sorted(newheads, key=lambda x: revdistance[x],
281 sortedheads = sorted(newheads, key=lambda x: revdistance[x],
282 reverse=True)
282 reverse=True)
283
283
284 for i, rev in enumerate(sortedheads):
284 for i, rev in enumerate(sortedheads):
285 ctx = repo[rev]
285 ctx = repo[rev]
286
286
287 if i:
287 if i:
288 ui.write(': ')
288 ui.write(': ')
289 else:
289 else:
290 ui.write(' ')
290 ui.write(' ')
291
291
292 ui.write(('o '))
292 ui.write(('o '))
293 displayer.show(ctx, nodelen=nodelen)
293 displayer.show(ctx, nodelen=nodelen)
294 displayer.flush(ctx)
294 displayer.flush(ctx)
295 ui.write('\n')
295 ui.write('\n')
296
296
297 if i:
297 if i:
298 ui.write(':/')
298 ui.write(':/')
299 else:
299 else:
300 ui.write(' /')
300 ui.write(' /')
301
301
302 ui.write(' (')
302 ui.write(' (')
303 ui.write(_('%d commits ahead') % revdistance[rev],
303 ui.write(_('%d commits ahead') % revdistance[rev],
304 label='stack.commitdistance')
304 label='stack.commitdistance')
305
305
306 if haverebase:
306 if haverebase:
307 # TODO may be able to omit --source in some scenarios
307 # TODO may be able to omit --source in some scenarios
308 ui.write('; ')
308 ui.write('; ')
309 ui.write(('hg rebase --source %s --dest %s' % (
309 ui.write(('hg rebase --source %s --dest %s' % (
310 shortest(sourcectx), shortest(ctx))),
310 shortest(sourcectx), shortest(ctx))),
311 label='stack.rebasehint')
311 label='stack.rebasehint')
312
312
313 ui.write(')\n')
313 ui.write(')\n')
314
314
315 ui.write(':\n: ')
315 ui.write(':\n: ')
316 ui.write(_('(stack head)\n'), label='stack.label')
316 ui.write(_('(stack head)\n'), label='stack.label')
317
317
318 if branchpointattip:
318 if branchpointattip:
319 ui.write(' \\ / ')
319 ui.write(' \\ / ')
320 ui.write(_('(multiple children)\n'), label='stack.label')
320 ui.write(_('(multiple children)\n'), label='stack.label')
321 ui.write(' |\n')
321 ui.write(' |\n')
322
322
323 for rev in stackrevs:
323 for rev in stackrevs:
324 ctx = repo[rev]
324 ctx = repo[rev]
325 symbol = '@' if rev == wdirctx.rev() else 'o'
325 symbol = '@' if rev == wdirctx.rev() else 'o'
326
326
327 if newheads:
327 if newheads:
328 ui.write(': ')
328 ui.write(': ')
329 else:
329 else:
330 ui.write(' ')
330 ui.write(' ')
331
331
332 ui.write(symbol, ' ')
332 ui.write(symbol, ' ')
333 displayer.show(ctx, nodelen=nodelen)
333 displayer.show(ctx, nodelen=nodelen)
334 displayer.flush(ctx)
334 displayer.flush(ctx)
335 ui.write('\n')
335 ui.write('\n')
336
336
337 # TODO display histedit hint?
337 # TODO display histedit hint?
338
338
339 if basectx:
339 if basectx:
340 # Vertically and horizontally separate stack base from parent
340 # Vertically and horizontally separate stack base from parent
341 # to reinforce stack boundary.
341 # to reinforce stack boundary.
342 if newheads:
342 if newheads:
343 ui.write(':/ ')
343 ui.write(':/ ')
344 else:
344 else:
345 ui.write(' / ')
345 ui.write(' / ')
346
346
347 ui.write(_('(stack base)'), '\n', label='stack.label')
347 ui.write(_('(stack base)'), '\n', label='stack.label')
348 ui.write(('o '))
348 ui.write(('o '))
349
349
350 displayer.show(basectx, nodelen=nodelen)
350 displayer.show(basectx, nodelen=nodelen)
351 displayer.flush(basectx)
351 displayer.flush(basectx)
352 ui.write('\n')
352 ui.write('\n')
353
353
354 @revsetpredicate('_underway([commitage[, headage]])')
354 @revsetpredicate('_underway([commitage[, headage]])')
355 def underwayrevset(repo, subset, x):
355 def underwayrevset(repo, subset, x):
356 args = revset.getargsdict(x, 'underway', 'commitage headage')
356 args = revset.getargsdict(x, 'underway', 'commitage headage')
357 if 'commitage' not in args:
357 if 'commitage' not in args:
358 args['commitage'] = None
358 args['commitage'] = None
359 if 'headage' not in args:
359 if 'headage' not in args:
360 args['headage'] = None
360 args['headage'] = None
361
361
362 # We assume callers of this revset add a topographical sort on the
362 # We assume callers of this revset add a topographical sort on the
363 # result. This means there is no benefit to making the revset lazy
363 # result. This means there is no benefit to making the revset lazy
364 # since the topographical sort needs to consume all revs.
364 # since the topographical sort needs to consume all revs.
365 #
365 #
366 # With this in mind, we build up the set manually instead of constructing
366 # With this in mind, we build up the set manually instead of constructing
367 # a complex revset. This enables faster execution.
367 # a complex revset. This enables faster execution.
368
368
369 # Mutable changesets (non-public) are the most important changesets
369 # Mutable changesets (non-public) are the most important changesets
370 # to return. ``not public()`` will also pull in obsolete changesets if
370 # to return. ``not public()`` will also pull in obsolete changesets if
371 # there is a non-obsolete changeset with obsolete ancestors. This is
371 # there is a non-obsolete changeset with obsolete ancestors. This is
372 # why we exclude obsolete changesets from this query.
372 # why we exclude obsolete changesets from this query.
373 rs = 'not public() and not obsolete()'
373 rs = 'not public() and not obsolete()'
374 rsargs = []
374 rsargs = []
375 if args['commitage']:
375 if args['commitage']:
376 rs += ' and date(%s)'
376 rs += ' and date(%s)'
377 rsargs.append(revsetlang.getstring(args['commitage'],
377 rsargs.append(revsetlang.getstring(args['commitage'],
378 _('commitage requires a string')))
378 _('commitage requires a string')))
379
379
380 mutable = repo.revs(rs, *rsargs)
380 mutable = repo.revs(rs, *rsargs)
381 relevant = revset.baseset(mutable)
381 relevant = revset.baseset(mutable)
382
382
383 # Add parents of mutable changesets to provide context.
383 # Add parents of mutable changesets to provide context.
384 relevant += repo.revs('parents(%ld)', mutable)
384 relevant += repo.revs('parents(%ld)', mutable)
385
385
386 # We also pull in (public) heads if they a) aren't closing a branch
386 # We also pull in (public) heads if they a) aren't closing a branch
387 # b) are recent.
387 # b) are recent.
388 rs = 'head() and not closed()'
388 rs = 'head() and not closed()'
389 rsargs = []
389 rsargs = []
390 if args['headage']:
390 if args['headage']:
391 rs += ' and date(%s)'
391 rs += ' and date(%s)'
392 rsargs.append(revsetlang.getstring(args['headage'],
392 rsargs.append(revsetlang.getstring(args['headage'],
393 _('headage requires a string')))
393 _('headage requires a string')))
394
394
395 relevant += repo.revs(rs, *rsargs)
395 relevant += repo.revs(rs, *rsargs)
396
396
397 # Add working directory parent.
397 # Add working directory parent.
398 wdirrev = repo['.'].rev()
398 wdirrev = repo['.'].rev()
399 if wdirrev != nullrev:
399 if wdirrev != nullrev:
400 relevant += revset.baseset({wdirrev})
400 relevant += revset.baseset({wdirrev})
401
401
402 return subset & relevant
402 return subset & relevant
403
403
404 @showview('work', csettopic='work')
404 @showview('work', csettopic='work')
405 def showwork(ui, repo, displayer):
405 def showwork(ui, repo, displayer):
406 """changesets that aren't finished"""
406 """changesets that aren't finished"""
407 # TODO support date-based limiting when calling revset.
407 # TODO support date-based limiting when calling revset.
408 revs = repo.revs('sort(_underway(), topo)')
408 revs = repo.revs('sort(_underway(), topo)')
409 nodelen = longestshortest(repo, revs)
409 nodelen = longestshortest(repo, revs)
410
410
411 revdag = graphmod.dagwalker(repo, revs)
411 revdag = graphmod.dagwalker(repo, revs)
412
412
413 ui.setconfig('experimental', 'graphshorten', True)
413 ui.setconfig('experimental', 'graphshorten', True)
414 logcmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges,
414 logcmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges,
415 props={'nodelen': nodelen})
415 props={'nodelen': nodelen})
416
416
417 def extsetup(ui):
417 def extsetup(ui):
418 # Alias `hg <prefix><view>` to `hg show <view>`.
418 # Alias `hg <prefix><view>` to `hg show <view>`.
419 for prefix in ui.configlist('commands', 'show.aliasprefix'):
419 for prefix in ui.configlist('commands', 'show.aliasprefix'):
420 for view in showview._table:
420 for view in showview._table:
421 name = '%s%s' % (prefix, view)
421 name = '%s%s' % (prefix, view)
422
422
423 choice, allcommands = cmdutil.findpossible(name, commands.table,
423 choice, allcommands = cmdutil.findpossible(name, commands.table,
424 strict=True)
424 strict=True)
425
425
426 # This alias is already a command name. Don't set it.
426 # This alias is already a command name. Don't set it.
427 if name in choice:
427 if name in choice:
428 continue
428 continue
429
429
430 # Same for aliases.
430 # Same for aliases.
431 if ui.config('alias', name, None):
431 if ui.config('alias', name, None):
432 continue
432 continue
433
433
434 ui.setconfig('alias', name, 'show %s' % view, source='show')
434 ui.setconfig('alias', name, 'show %s' % view, source='show')
435
435
436 def longestshortest(repo, revs, minlen=4):
436 def longestshortest(repo, revs, minlen=4):
437 """Return the length of the longest shortest node to identify revisions.
437 """Return the length of the longest shortest node to identify revisions.
438
438
439 The result of this function can be used with the ``shortest()`` template
439 The result of this function can be used with the ``shortest()`` template
440 function to ensure that a value is unique and unambiguous for a given
440 function to ensure that a value is unique and unambiguous for a given
441 set of nodes.
441 set of nodes.
442
442
443 The number of revisions in the repo is taken into account to prevent
443 The number of revisions in the repo is taken into account to prevent
444 a numeric node prefix from conflicting with an integer revision number.
444 a numeric node prefix from conflicting with an integer revision number.
445 If we fail to do this, a value of e.g. ``10023`` could mean either
445 If we fail to do this, a value of e.g. ``10023`` could mean either
446 revision 10023 or node ``10023abc...``.
446 revision 10023 or node ``10023abc...``.
447 """
447 """
448 if not revs:
448 if not revs:
449 return minlen
449 return minlen
450 cl = repo.changelog
450 cl = repo.changelog
451 return max(len(scmutil.shortesthexnodeidprefix(repo, cl.node(r), minlen))
451 return max(len(scmutil.shortesthexnodeidprefix(repo, cl.node(r), minlen))
452 for r in revs)
452 for r in revs)
453
453
454 # Adjust the docstring of the show command so it shows all registered views.
454 # Adjust the docstring of the show command so it shows all registered views.
455 # This is a bit hacky because it runs at the end of module load. When moved
455 # This is a bit hacky because it runs at the end of module load. When moved
456 # into core or when another extension wants to provide a view, we'll need
456 # into core or when another extension wants to provide a view, we'll need
457 # to do this more robustly.
457 # to do this more robustly.
458 # TODO make this more robust.
458 # TODO make this more robust.
459 def _updatedocstring():
459 def _updatedocstring():
460 longest = max(map(len, showview._table.keys()))
460 longest = max(map(len, showview._table.keys()))
461 entries = []
461 entries = []
462 for key in sorted(showview._table.keys()):
462 for key in sorted(showview._table.keys()):
463 entries.append(pycompat.sysstr(' %s %s' % (
463 entries.append(r' %s %s' % (
464 key.ljust(longest), showview._table[key]._origdoc)))
464 pycompat.sysstr(key.ljust(longest)), showview._table[key]._origdoc))
465
465
466 cmdtable['show'][0].__doc__ = pycompat.sysstr('%s\n\n%s\n ') % (
466 cmdtable['show'][0].__doc__ = pycompat.sysstr('%s\n\n%s\n ') % (
467 cmdtable['show'][0].__doc__.rstrip(),
467 cmdtable['show'][0].__doc__.rstrip(),
468 pycompat.sysstr('\n\n').join(entries))
468 pycompat.sysstr('\n\n').join(entries))
469
469
470 _updatedocstring()
470 _updatedocstring()
@@ -1,501 +1,501
1 # registrar.py - utilities to register function for specific purpose
1 # registrar.py - utilities to register function for specific purpose
2 #
2 #
3 # Copyright FUJIWARA Katsunori <foozy@lares.dti.ne.jp> and others
3 # Copyright FUJIWARA Katsunori <foozy@lares.dti.ne.jp> and others
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 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from . import (
10 from . import (
11 configitems,
11 configitems,
12 error,
12 error,
13 pycompat,
13 pycompat,
14 util,
14 util,
15 )
15 )
16
16
17 # unlike the other registered items, config options are neither functions or
17 # unlike the other registered items, config options are neither functions or
18 # classes. Registering the option is just small function call.
18 # classes. Registering the option is just small function call.
19 #
19 #
20 # We still add the official API to the registrar module for consistency with
20 # We still add the official API to the registrar module for consistency with
21 # the other items extensions want might to register.
21 # the other items extensions want might to register.
22 configitem = configitems.getitemregister
22 configitem = configitems.getitemregister
23
23
24 class _funcregistrarbase(object):
24 class _funcregistrarbase(object):
25 """Base of decorator to register a function for specific purpose
25 """Base of decorator to register a function for specific purpose
26
26
27 This decorator stores decorated functions into own dict 'table'.
27 This decorator stores decorated functions into own dict 'table'.
28
28
29 The least derived class can be defined by overriding 'formatdoc',
29 The least derived class can be defined by overriding 'formatdoc',
30 for example::
30 for example::
31
31
32 class keyword(_funcregistrarbase):
32 class keyword(_funcregistrarbase):
33 _docformat = ":%s: %s"
33 _docformat = ":%s: %s"
34
34
35 This should be used as below:
35 This should be used as below:
36
36
37 keyword = registrar.keyword()
37 keyword = registrar.keyword()
38
38
39 @keyword('bar')
39 @keyword('bar')
40 def barfunc(*args, **kwargs):
40 def barfunc(*args, **kwargs):
41 '''Explanation of bar keyword ....
41 '''Explanation of bar keyword ....
42 '''
42 '''
43 pass
43 pass
44
44
45 In this case:
45 In this case:
46
46
47 - 'barfunc' is stored as 'bar' in '_table' of an instance 'keyword' above
47 - 'barfunc' is stored as 'bar' in '_table' of an instance 'keyword' above
48 - 'barfunc.__doc__' becomes ":bar: Explanation of bar keyword"
48 - 'barfunc.__doc__' becomes ":bar: Explanation of bar keyword"
49 """
49 """
50 def __init__(self, table=None):
50 def __init__(self, table=None):
51 if table is None:
51 if table is None:
52 self._table = {}
52 self._table = {}
53 else:
53 else:
54 self._table = table
54 self._table = table
55
55
56 def __call__(self, decl, *args, **kwargs):
56 def __call__(self, decl, *args, **kwargs):
57 return lambda func: self._doregister(func, decl, *args, **kwargs)
57 return lambda func: self._doregister(func, decl, *args, **kwargs)
58
58
59 def _doregister(self, func, decl, *args, **kwargs):
59 def _doregister(self, func, decl, *args, **kwargs):
60 name = self._getname(decl)
60 name = self._getname(decl)
61
61
62 if name in self._table:
62 if name in self._table:
63 msg = 'duplicate registration for name: "%s"' % name
63 msg = 'duplicate registration for name: "%s"' % name
64 raise error.ProgrammingError(msg)
64 raise error.ProgrammingError(msg)
65
65
66 if func.__doc__ and not util.safehasattr(func, '_origdoc'):
66 if func.__doc__ and not util.safehasattr(func, '_origdoc'):
67 doc = pycompat.sysbytes(func.__doc__).strip()
67 func._origdoc = func.__doc__.strip()
68 func._origdoc = doc
68 doc = pycompat.sysbytes(func._origdoc)
69 func.__doc__ = pycompat.sysstr(self._formatdoc(decl, doc))
69 func.__doc__ = pycompat.sysstr(self._formatdoc(decl, doc))
70
70
71 self._table[name] = func
71 self._table[name] = func
72 self._extrasetup(name, func, *args, **kwargs)
72 self._extrasetup(name, func, *args, **kwargs)
73
73
74 return func
74 return func
75
75
76 def _merge(self, registrarbase):
76 def _merge(self, registrarbase):
77 """Merge the entries of the given registrar object into this one.
77 """Merge the entries of the given registrar object into this one.
78
78
79 The other registrar object must not contain any entries already in the
79 The other registrar object must not contain any entries already in the
80 current one, or a ProgrammmingError is raised. Additionally, the types
80 current one, or a ProgrammmingError is raised. Additionally, the types
81 of the two registrars must match.
81 of the two registrars must match.
82 """
82 """
83 if not isinstance(registrarbase, type(self)):
83 if not isinstance(registrarbase, type(self)):
84 msg = "cannot merge different types of registrar"
84 msg = "cannot merge different types of registrar"
85 raise error.ProgrammingError(msg)
85 raise error.ProgrammingError(msg)
86
86
87 dups = set(registrarbase._table).intersection(self._table)
87 dups = set(registrarbase._table).intersection(self._table)
88
88
89 if dups:
89 if dups:
90 msg = 'duplicate registration for names: "%s"' % '", "'.join(dups)
90 msg = 'duplicate registration for names: "%s"' % '", "'.join(dups)
91 raise error.ProgrammingError(msg)
91 raise error.ProgrammingError(msg)
92
92
93 self._table.update(registrarbase._table)
93 self._table.update(registrarbase._table)
94
94
95 def _parsefuncdecl(self, decl):
95 def _parsefuncdecl(self, decl):
96 """Parse function declaration and return the name of function in it
96 """Parse function declaration and return the name of function in it
97 """
97 """
98 i = decl.find('(')
98 i = decl.find('(')
99 if i >= 0:
99 if i >= 0:
100 return decl[:i]
100 return decl[:i]
101 else:
101 else:
102 return decl
102 return decl
103
103
104 def _getname(self, decl):
104 def _getname(self, decl):
105 """Return the name of the registered function from decl
105 """Return the name of the registered function from decl
106
106
107 Derived class should override this, if it allows more
107 Derived class should override this, if it allows more
108 descriptive 'decl' string than just a name.
108 descriptive 'decl' string than just a name.
109 """
109 """
110 return decl
110 return decl
111
111
112 _docformat = None
112 _docformat = None
113
113
114 def _formatdoc(self, decl, doc):
114 def _formatdoc(self, decl, doc):
115 """Return formatted document of the registered function for help
115 """Return formatted document of the registered function for help
116
116
117 'doc' is '__doc__.strip()' of the registered function.
117 'doc' is '__doc__.strip()' of the registered function.
118 """
118 """
119 return self._docformat % (decl, doc)
119 return self._docformat % (decl, doc)
120
120
121 def _extrasetup(self, name, func):
121 def _extrasetup(self, name, func):
122 """Execute exra setup for registered function, if needed
122 """Execute exra setup for registered function, if needed
123 """
123 """
124
124
125 class command(_funcregistrarbase):
125 class command(_funcregistrarbase):
126 """Decorator to register a command function to table
126 """Decorator to register a command function to table
127
127
128 This class receives a command table as its argument. The table should
128 This class receives a command table as its argument. The table should
129 be a dict.
129 be a dict.
130
130
131 The created object can be used as a decorator for adding commands to
131 The created object can be used as a decorator for adding commands to
132 that command table. This accepts multiple arguments to define a command.
132 that command table. This accepts multiple arguments to define a command.
133
133
134 The first argument is the command name (as bytes).
134 The first argument is the command name (as bytes).
135
135
136 The `options` keyword argument is an iterable of tuples defining command
136 The `options` keyword argument is an iterable of tuples defining command
137 arguments. See ``mercurial.fancyopts.fancyopts()`` for the format of each
137 arguments. See ``mercurial.fancyopts.fancyopts()`` for the format of each
138 tuple.
138 tuple.
139
139
140 The `synopsis` argument defines a short, one line summary of how to use the
140 The `synopsis` argument defines a short, one line summary of how to use the
141 command. This shows up in the help output.
141 command. This shows up in the help output.
142
142
143 There are three arguments that control what repository (if any) is found
143 There are three arguments that control what repository (if any) is found
144 and passed to the decorated function: `norepo`, `optionalrepo`, and
144 and passed to the decorated function: `norepo`, `optionalrepo`, and
145 `inferrepo`.
145 `inferrepo`.
146
146
147 The `norepo` argument defines whether the command does not require a
147 The `norepo` argument defines whether the command does not require a
148 local repository. Most commands operate against a repository, thus the
148 local repository. Most commands operate against a repository, thus the
149 default is False. When True, no repository will be passed.
149 default is False. When True, no repository will be passed.
150
150
151 The `optionalrepo` argument defines whether the command optionally requires
151 The `optionalrepo` argument defines whether the command optionally requires
152 a local repository. If no repository can be found, None will be passed
152 a local repository. If no repository can be found, None will be passed
153 to the decorated function.
153 to the decorated function.
154
154
155 The `inferrepo` argument defines whether to try to find a repository from
155 The `inferrepo` argument defines whether to try to find a repository from
156 the command line arguments. If True, arguments will be examined for
156 the command line arguments. If True, arguments will be examined for
157 potential repository locations. See ``findrepo()``. If a repository is
157 potential repository locations. See ``findrepo()``. If a repository is
158 found, it will be used and passed to the decorated function.
158 found, it will be used and passed to the decorated function.
159
159
160 The `intents` argument defines a set of intended actions or capabilities
160 The `intents` argument defines a set of intended actions or capabilities
161 the command is taking. These intents can be used to affect the construction
161 the command is taking. These intents can be used to affect the construction
162 of the repository object passed to the command. For example, commands
162 of the repository object passed to the command. For example, commands
163 declaring that they are read-only could receive a repository that doesn't
163 declaring that they are read-only could receive a repository that doesn't
164 have any methods allowing repository mutation. Other intents could be used
164 have any methods allowing repository mutation. Other intents could be used
165 to prevent the command from running if the requested intent could not be
165 to prevent the command from running if the requested intent could not be
166 fulfilled.
166 fulfilled.
167
167
168 If `helpcategory` is set (usually to one of the constants in the help
168 If `helpcategory` is set (usually to one of the constants in the help
169 module), the command will be displayed under that category in the help's
169 module), the command will be displayed under that category in the help's
170 list of commands.
170 list of commands.
171
171
172 The following intents are defined:
172 The following intents are defined:
173
173
174 readonly
174 readonly
175 The command is read-only
175 The command is read-only
176
176
177 The signature of the decorated function looks like this:
177 The signature of the decorated function looks like this:
178 def cmd(ui[, repo] [, <args>] [, <options>])
178 def cmd(ui[, repo] [, <args>] [, <options>])
179
179
180 `repo` is required if `norepo` is False.
180 `repo` is required if `norepo` is False.
181 `<args>` are positional args (or `*args`) arguments, of non-option
181 `<args>` are positional args (or `*args`) arguments, of non-option
182 arguments from the command line.
182 arguments from the command line.
183 `<options>` are keyword arguments (or `**options`) of option arguments
183 `<options>` are keyword arguments (or `**options`) of option arguments
184 from the command line.
184 from the command line.
185
185
186 See the WritingExtensions and MercurialApi documentation for more exhaustive
186 See the WritingExtensions and MercurialApi documentation for more exhaustive
187 descriptions and examples.
187 descriptions and examples.
188 """
188 """
189
189
190 # Command categories for grouping them in help output.
190 # Command categories for grouping them in help output.
191 # These can also be specified for aliases, like:
191 # These can also be specified for aliases, like:
192 # [alias]
192 # [alias]
193 # myalias = something
193 # myalias = something
194 # myalias:category = repo
194 # myalias:category = repo
195 CATEGORY_REPO_CREATION = 'repo'
195 CATEGORY_REPO_CREATION = 'repo'
196 CATEGORY_REMOTE_REPO_MANAGEMENT = 'remote'
196 CATEGORY_REMOTE_REPO_MANAGEMENT = 'remote'
197 CATEGORY_COMMITTING = 'commit'
197 CATEGORY_COMMITTING = 'commit'
198 CATEGORY_CHANGE_MANAGEMENT = 'management'
198 CATEGORY_CHANGE_MANAGEMENT = 'management'
199 CATEGORY_CHANGE_ORGANIZATION = 'organization'
199 CATEGORY_CHANGE_ORGANIZATION = 'organization'
200 CATEGORY_FILE_CONTENTS = 'files'
200 CATEGORY_FILE_CONTENTS = 'files'
201 CATEGORY_CHANGE_NAVIGATION = 'navigation'
201 CATEGORY_CHANGE_NAVIGATION = 'navigation'
202 CATEGORY_WORKING_DIRECTORY = 'wdir'
202 CATEGORY_WORKING_DIRECTORY = 'wdir'
203 CATEGORY_IMPORT_EXPORT = 'import'
203 CATEGORY_IMPORT_EXPORT = 'import'
204 CATEGORY_MAINTENANCE = 'maintenance'
204 CATEGORY_MAINTENANCE = 'maintenance'
205 CATEGORY_HELP = 'help'
205 CATEGORY_HELP = 'help'
206 CATEGORY_MISC = 'misc'
206 CATEGORY_MISC = 'misc'
207 CATEGORY_NONE = 'none'
207 CATEGORY_NONE = 'none'
208
208
209 def _doregister(self, func, name, options=(), synopsis=None,
209 def _doregister(self, func, name, options=(), synopsis=None,
210 norepo=False, optionalrepo=False, inferrepo=False,
210 norepo=False, optionalrepo=False, inferrepo=False,
211 intents=None, helpcategory=None, helpbasic=False):
211 intents=None, helpcategory=None, helpbasic=False):
212 func.norepo = norepo
212 func.norepo = norepo
213 func.optionalrepo = optionalrepo
213 func.optionalrepo = optionalrepo
214 func.inferrepo = inferrepo
214 func.inferrepo = inferrepo
215 func.intents = intents or set()
215 func.intents = intents or set()
216 func.helpcategory = helpcategory
216 func.helpcategory = helpcategory
217 func.helpbasic = helpbasic
217 func.helpbasic = helpbasic
218 if synopsis:
218 if synopsis:
219 self._table[name] = func, list(options), synopsis
219 self._table[name] = func, list(options), synopsis
220 else:
220 else:
221 self._table[name] = func, list(options)
221 self._table[name] = func, list(options)
222 return func
222 return func
223
223
224 INTENT_READONLY = b'readonly'
224 INTENT_READONLY = b'readonly'
225
225
226 class revsetpredicate(_funcregistrarbase):
226 class revsetpredicate(_funcregistrarbase):
227 """Decorator to register revset predicate
227 """Decorator to register revset predicate
228
228
229 Usage::
229 Usage::
230
230
231 revsetpredicate = registrar.revsetpredicate()
231 revsetpredicate = registrar.revsetpredicate()
232
232
233 @revsetpredicate('mypredicate(arg1, arg2[, arg3])')
233 @revsetpredicate('mypredicate(arg1, arg2[, arg3])')
234 def mypredicatefunc(repo, subset, x):
234 def mypredicatefunc(repo, subset, x):
235 '''Explanation of this revset predicate ....
235 '''Explanation of this revset predicate ....
236 '''
236 '''
237 pass
237 pass
238
238
239 The first string argument is used also in online help.
239 The first string argument is used also in online help.
240
240
241 Optional argument 'safe' indicates whether a predicate is safe for
241 Optional argument 'safe' indicates whether a predicate is safe for
242 DoS attack (False by default).
242 DoS attack (False by default).
243
243
244 Optional argument 'takeorder' indicates whether a predicate function
244 Optional argument 'takeorder' indicates whether a predicate function
245 takes ordering policy as the last argument.
245 takes ordering policy as the last argument.
246
246
247 Optional argument 'weight' indicates the estimated run-time cost, useful
247 Optional argument 'weight' indicates the estimated run-time cost, useful
248 for static optimization, default is 1. Higher weight means more expensive.
248 for static optimization, default is 1. Higher weight means more expensive.
249 Usually, revsets that are fast and return only one revision has a weight of
249 Usually, revsets that are fast and return only one revision has a weight of
250 0.5 (ex. a symbol); revsets with O(changelog) complexity and read only the
250 0.5 (ex. a symbol); revsets with O(changelog) complexity and read only the
251 changelog have weight 10 (ex. author); revsets reading manifest deltas have
251 changelog have weight 10 (ex. author); revsets reading manifest deltas have
252 weight 30 (ex. adds); revset reading manifest contents have weight 100
252 weight 30 (ex. adds); revset reading manifest contents have weight 100
253 (ex. contains). Note: those values are flexible. If the revset has a
253 (ex. contains). Note: those values are flexible. If the revset has a
254 same big-O time complexity as 'contains', but with a smaller constant, it
254 same big-O time complexity as 'contains', but with a smaller constant, it
255 might have a weight of 90.
255 might have a weight of 90.
256
256
257 'revsetpredicate' instance in example above can be used to
257 'revsetpredicate' instance in example above can be used to
258 decorate multiple functions.
258 decorate multiple functions.
259
259
260 Decorated functions are registered automatically at loading
260 Decorated functions are registered automatically at loading
261 extension, if an instance named as 'revsetpredicate' is used for
261 extension, if an instance named as 'revsetpredicate' is used for
262 decorating in extension.
262 decorating in extension.
263
263
264 Otherwise, explicit 'revset.loadpredicate()' is needed.
264 Otherwise, explicit 'revset.loadpredicate()' is needed.
265 """
265 """
266 _getname = _funcregistrarbase._parsefuncdecl
266 _getname = _funcregistrarbase._parsefuncdecl
267 _docformat = "``%s``\n %s"
267 _docformat = "``%s``\n %s"
268
268
269 def _extrasetup(self, name, func, safe=False, takeorder=False, weight=1):
269 def _extrasetup(self, name, func, safe=False, takeorder=False, weight=1):
270 func._safe = safe
270 func._safe = safe
271 func._takeorder = takeorder
271 func._takeorder = takeorder
272 func._weight = weight
272 func._weight = weight
273
273
274 class filesetpredicate(_funcregistrarbase):
274 class filesetpredicate(_funcregistrarbase):
275 """Decorator to register fileset predicate
275 """Decorator to register fileset predicate
276
276
277 Usage::
277 Usage::
278
278
279 filesetpredicate = registrar.filesetpredicate()
279 filesetpredicate = registrar.filesetpredicate()
280
280
281 @filesetpredicate('mypredicate()')
281 @filesetpredicate('mypredicate()')
282 def mypredicatefunc(mctx, x):
282 def mypredicatefunc(mctx, x):
283 '''Explanation of this fileset predicate ....
283 '''Explanation of this fileset predicate ....
284 '''
284 '''
285 pass
285 pass
286
286
287 The first string argument is used also in online help.
287 The first string argument is used also in online help.
288
288
289 Optional argument 'callstatus' indicates whether a predicate
289 Optional argument 'callstatus' indicates whether a predicate
290 implies 'matchctx.status()' at runtime or not (False, by
290 implies 'matchctx.status()' at runtime or not (False, by
291 default).
291 default).
292
292
293 Optional argument 'weight' indicates the estimated run-time cost, useful
293 Optional argument 'weight' indicates the estimated run-time cost, useful
294 for static optimization, default is 1. Higher weight means more expensive.
294 for static optimization, default is 1. Higher weight means more expensive.
295 There are predefined weights in the 'filesetlang' module.
295 There are predefined weights in the 'filesetlang' module.
296
296
297 ====== =============================================================
297 ====== =============================================================
298 Weight Description and examples
298 Weight Description and examples
299 ====== =============================================================
299 ====== =============================================================
300 0.5 basic match patterns (e.g. a symbol)
300 0.5 basic match patterns (e.g. a symbol)
301 10 computing status (e.g. added()) or accessing a few files
301 10 computing status (e.g. added()) or accessing a few files
302 30 reading file content for each (e.g. grep())
302 30 reading file content for each (e.g. grep())
303 50 scanning working directory (ignored())
303 50 scanning working directory (ignored())
304 ====== =============================================================
304 ====== =============================================================
305
305
306 'filesetpredicate' instance in example above can be used to
306 'filesetpredicate' instance in example above can be used to
307 decorate multiple functions.
307 decorate multiple functions.
308
308
309 Decorated functions are registered automatically at loading
309 Decorated functions are registered automatically at loading
310 extension, if an instance named as 'filesetpredicate' is used for
310 extension, if an instance named as 'filesetpredicate' is used for
311 decorating in extension.
311 decorating in extension.
312
312
313 Otherwise, explicit 'fileset.loadpredicate()' is needed.
313 Otherwise, explicit 'fileset.loadpredicate()' is needed.
314 """
314 """
315 _getname = _funcregistrarbase._parsefuncdecl
315 _getname = _funcregistrarbase._parsefuncdecl
316 _docformat = "``%s``\n %s"
316 _docformat = "``%s``\n %s"
317
317
318 def _extrasetup(self, name, func, callstatus=False, weight=1):
318 def _extrasetup(self, name, func, callstatus=False, weight=1):
319 func._callstatus = callstatus
319 func._callstatus = callstatus
320 func._weight = weight
320 func._weight = weight
321
321
322 class _templateregistrarbase(_funcregistrarbase):
322 class _templateregistrarbase(_funcregistrarbase):
323 """Base of decorator to register functions as template specific one
323 """Base of decorator to register functions as template specific one
324 """
324 """
325 _docformat = ":%s: %s"
325 _docformat = ":%s: %s"
326
326
327 class templatekeyword(_templateregistrarbase):
327 class templatekeyword(_templateregistrarbase):
328 """Decorator to register template keyword
328 """Decorator to register template keyword
329
329
330 Usage::
330 Usage::
331
331
332 templatekeyword = registrar.templatekeyword()
332 templatekeyword = registrar.templatekeyword()
333
333
334 # new API (since Mercurial 4.6)
334 # new API (since Mercurial 4.6)
335 @templatekeyword('mykeyword', requires={'repo', 'ctx'})
335 @templatekeyword('mykeyword', requires={'repo', 'ctx'})
336 def mykeywordfunc(context, mapping):
336 def mykeywordfunc(context, mapping):
337 '''Explanation of this template keyword ....
337 '''Explanation of this template keyword ....
338 '''
338 '''
339 pass
339 pass
340
340
341 The first string argument is used also in online help.
341 The first string argument is used also in online help.
342
342
343 Optional argument 'requires' should be a collection of resource names
343 Optional argument 'requires' should be a collection of resource names
344 which the template keyword depends on.
344 which the template keyword depends on.
345
345
346 'templatekeyword' instance in example above can be used to
346 'templatekeyword' instance in example above can be used to
347 decorate multiple functions.
347 decorate multiple functions.
348
348
349 Decorated functions are registered automatically at loading
349 Decorated functions are registered automatically at loading
350 extension, if an instance named as 'templatekeyword' is used for
350 extension, if an instance named as 'templatekeyword' is used for
351 decorating in extension.
351 decorating in extension.
352
352
353 Otherwise, explicit 'templatekw.loadkeyword()' is needed.
353 Otherwise, explicit 'templatekw.loadkeyword()' is needed.
354 """
354 """
355
355
356 def _extrasetup(self, name, func, requires=()):
356 def _extrasetup(self, name, func, requires=()):
357 func._requires = requires
357 func._requires = requires
358
358
359 class templatefilter(_templateregistrarbase):
359 class templatefilter(_templateregistrarbase):
360 """Decorator to register template filer
360 """Decorator to register template filer
361
361
362 Usage::
362 Usage::
363
363
364 templatefilter = registrar.templatefilter()
364 templatefilter = registrar.templatefilter()
365
365
366 @templatefilter('myfilter', intype=bytes)
366 @templatefilter('myfilter', intype=bytes)
367 def myfilterfunc(text):
367 def myfilterfunc(text):
368 '''Explanation of this template filter ....
368 '''Explanation of this template filter ....
369 '''
369 '''
370 pass
370 pass
371
371
372 The first string argument is used also in online help.
372 The first string argument is used also in online help.
373
373
374 Optional argument 'intype' defines the type of the input argument,
374 Optional argument 'intype' defines the type of the input argument,
375 which should be (bytes, int, templateutil.date, or None for any.)
375 which should be (bytes, int, templateutil.date, or None for any.)
376
376
377 'templatefilter' instance in example above can be used to
377 'templatefilter' instance in example above can be used to
378 decorate multiple functions.
378 decorate multiple functions.
379
379
380 Decorated functions are registered automatically at loading
380 Decorated functions are registered automatically at loading
381 extension, if an instance named as 'templatefilter' is used for
381 extension, if an instance named as 'templatefilter' is used for
382 decorating in extension.
382 decorating in extension.
383
383
384 Otherwise, explicit 'templatefilters.loadkeyword()' is needed.
384 Otherwise, explicit 'templatefilters.loadkeyword()' is needed.
385 """
385 """
386
386
387 def _extrasetup(self, name, func, intype=None):
387 def _extrasetup(self, name, func, intype=None):
388 func._intype = intype
388 func._intype = intype
389
389
390 class templatefunc(_templateregistrarbase):
390 class templatefunc(_templateregistrarbase):
391 """Decorator to register template function
391 """Decorator to register template function
392
392
393 Usage::
393 Usage::
394
394
395 templatefunc = registrar.templatefunc()
395 templatefunc = registrar.templatefunc()
396
396
397 @templatefunc('myfunc(arg1, arg2[, arg3])', argspec='arg1 arg2 arg3',
397 @templatefunc('myfunc(arg1, arg2[, arg3])', argspec='arg1 arg2 arg3',
398 requires={'ctx'})
398 requires={'ctx'})
399 def myfuncfunc(context, mapping, args):
399 def myfuncfunc(context, mapping, args):
400 '''Explanation of this template function ....
400 '''Explanation of this template function ....
401 '''
401 '''
402 pass
402 pass
403
403
404 The first string argument is used also in online help.
404 The first string argument is used also in online help.
405
405
406 If optional 'argspec' is defined, the function will receive 'args' as
406 If optional 'argspec' is defined, the function will receive 'args' as
407 a dict of named arguments. Otherwise 'args' is a list of positional
407 a dict of named arguments. Otherwise 'args' is a list of positional
408 arguments.
408 arguments.
409
409
410 Optional argument 'requires' should be a collection of resource names
410 Optional argument 'requires' should be a collection of resource names
411 which the template function depends on.
411 which the template function depends on.
412
412
413 'templatefunc' instance in example above can be used to
413 'templatefunc' instance in example above can be used to
414 decorate multiple functions.
414 decorate multiple functions.
415
415
416 Decorated functions are registered automatically at loading
416 Decorated functions are registered automatically at loading
417 extension, if an instance named as 'templatefunc' is used for
417 extension, if an instance named as 'templatefunc' is used for
418 decorating in extension.
418 decorating in extension.
419
419
420 Otherwise, explicit 'templatefuncs.loadfunction()' is needed.
420 Otherwise, explicit 'templatefuncs.loadfunction()' is needed.
421 """
421 """
422 _getname = _funcregistrarbase._parsefuncdecl
422 _getname = _funcregistrarbase._parsefuncdecl
423
423
424 def _extrasetup(self, name, func, argspec=None, requires=()):
424 def _extrasetup(self, name, func, argspec=None, requires=()):
425 func._argspec = argspec
425 func._argspec = argspec
426 func._requires = requires
426 func._requires = requires
427
427
428 class internalmerge(_funcregistrarbase):
428 class internalmerge(_funcregistrarbase):
429 """Decorator to register in-process merge tool
429 """Decorator to register in-process merge tool
430
430
431 Usage::
431 Usage::
432
432
433 internalmerge = registrar.internalmerge()
433 internalmerge = registrar.internalmerge()
434
434
435 @internalmerge('mymerge', internalmerge.mergeonly,
435 @internalmerge('mymerge', internalmerge.mergeonly,
436 onfailure=None, precheck=None,
436 onfailure=None, precheck=None,
437 binary=False, symlink=False):
437 binary=False, symlink=False):
438 def mymergefunc(repo, mynode, orig, fcd, fco, fca,
438 def mymergefunc(repo, mynode, orig, fcd, fco, fca,
439 toolconf, files, labels=None):
439 toolconf, files, labels=None):
440 '''Explanation of this internal merge tool ....
440 '''Explanation of this internal merge tool ....
441 '''
441 '''
442 return 1, False # means "conflicted", "no deletion needed"
442 return 1, False # means "conflicted", "no deletion needed"
443
443
444 The first string argument is used to compose actual merge tool name,
444 The first string argument is used to compose actual merge tool name,
445 ":name" and "internal:name" (the latter is historical one).
445 ":name" and "internal:name" (the latter is historical one).
446
446
447 The second argument is one of merge types below:
447 The second argument is one of merge types below:
448
448
449 ========== ======== ======== =========
449 ========== ======== ======== =========
450 merge type precheck premerge fullmerge
450 merge type precheck premerge fullmerge
451 ========== ======== ======== =========
451 ========== ======== ======== =========
452 nomerge x x x
452 nomerge x x x
453 mergeonly o x o
453 mergeonly o x o
454 fullmerge o o o
454 fullmerge o o o
455 ========== ======== ======== =========
455 ========== ======== ======== =========
456
456
457 Optional argument 'onfailure' is the format of warning message
457 Optional argument 'onfailure' is the format of warning message
458 to be used at failure of merging (target filename is specified
458 to be used at failure of merging (target filename is specified
459 at formatting). Or, None or so, if warning message should be
459 at formatting). Or, None or so, if warning message should be
460 suppressed.
460 suppressed.
461
461
462 Optional argument 'precheck' is the function to be used
462 Optional argument 'precheck' is the function to be used
463 before actual invocation of internal merge tool itself.
463 before actual invocation of internal merge tool itself.
464 It takes as same arguments as internal merge tool does, other than
464 It takes as same arguments as internal merge tool does, other than
465 'files' and 'labels'. If it returns false value, merging is aborted
465 'files' and 'labels'. If it returns false value, merging is aborted
466 immediately (and file is marked as "unresolved").
466 immediately (and file is marked as "unresolved").
467
467
468 Optional argument 'binary' is a binary files capability of internal
468 Optional argument 'binary' is a binary files capability of internal
469 merge tool. 'nomerge' merge type implies binary=True.
469 merge tool. 'nomerge' merge type implies binary=True.
470
470
471 Optional argument 'symlink' is a symlinks capability of inetrnal
471 Optional argument 'symlink' is a symlinks capability of inetrnal
472 merge function. 'nomerge' merge type implies symlink=True.
472 merge function. 'nomerge' merge type implies symlink=True.
473
473
474 'internalmerge' instance in example above can be used to
474 'internalmerge' instance in example above can be used to
475 decorate multiple functions.
475 decorate multiple functions.
476
476
477 Decorated functions are registered automatically at loading
477 Decorated functions are registered automatically at loading
478 extension, if an instance named as 'internalmerge' is used for
478 extension, if an instance named as 'internalmerge' is used for
479 decorating in extension.
479 decorating in extension.
480
480
481 Otherwise, explicit 'filemerge.loadinternalmerge()' is needed.
481 Otherwise, explicit 'filemerge.loadinternalmerge()' is needed.
482 """
482 """
483 _docformat = "``:%s``\n %s"
483 _docformat = "``:%s``\n %s"
484
484
485 # merge type definitions:
485 # merge type definitions:
486 nomerge = None
486 nomerge = None
487 mergeonly = 'mergeonly' # just the full merge, no premerge
487 mergeonly = 'mergeonly' # just the full merge, no premerge
488 fullmerge = 'fullmerge' # both premerge and merge
488 fullmerge = 'fullmerge' # both premerge and merge
489
489
490 def _extrasetup(self, name, func, mergetype,
490 def _extrasetup(self, name, func, mergetype,
491 onfailure=None, precheck=None,
491 onfailure=None, precheck=None,
492 binary=False, symlink=False):
492 binary=False, symlink=False):
493 func.mergetype = mergetype
493 func.mergetype = mergetype
494 func.onfailure = onfailure
494 func.onfailure = onfailure
495 func.precheck = precheck
495 func.precheck = precheck
496
496
497 binarycap = binary or mergetype == self.nomerge
497 binarycap = binary or mergetype == self.nomerge
498 symlinkcap = symlink or mergetype == self.nomerge
498 symlinkcap = symlink or mergetype == self.nomerge
499
499
500 # actual capabilities, which this internal merge tool has
500 # actual capabilities, which this internal merge tool has
501 func.capabilities = {"binary": binarycap, "symlink": symlinkcap}
501 func.capabilities = {"binary": binarycap, "symlink": symlinkcap}
General Comments 0
You need to be logged in to leave comments. Login now