##// END OF EJS Templates
keyword: monkeypatch patch.diff for non-interactive diffs...
Christian Ebert -
r6092:911f5be5 default
parent child Browse files
Show More
@@ -1,547 +1,548 b''
1 1 # keyword.py - $Keyword$ expansion for Mercurial
2 2 #
3 3 # Copyright 2007, 2008 Christian Ebert <blacktrash@gmx.net>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7 #
8 8 # $Id$
9 9 #
10 10 # Keyword expansion hack against the grain of a DSCM
11 11 #
12 12 # There are many good reasons why this is not needed in a distributed
13 13 # SCM, still it may be useful in very small projects based on single
14 14 # files (like LaTeX packages), that are mostly addressed to an audience
15 15 # not running a version control system.
16 16 #
17 17 # For in-depth discussion refer to
18 18 # <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>.
19 19 #
20 20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 21 #
22 22 # Binary files are not touched.
23 23 #
24 24 # Setup in hgrc:
25 25 #
26 26 # [extensions]
27 27 # # enable extension
28 28 # hgext.keyword =
29 29 #
30 30 # Files to act upon/ignore are specified in the [keyword] section.
31 31 # Customized keyword template mappings in the [keywordmaps] section.
32 32 #
33 33 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
34 34
35 35 '''keyword expansion in local repositories
36 36
37 37 This extension expands RCS/CVS-like or self-customized $Keywords$
38 38 in tracked text files selected by your configuration.
39 39
40 40 Keywords are only expanded in local repositories and not stored in
41 41 the change history. The mechanism can be regarded as a convenience
42 42 for the current user or for archive distribution.
43 43
44 44 Configuration is done in the [keyword] and [keywordmaps] sections
45 45 of hgrc files.
46 46
47 47 Example:
48 48
49 49 [keyword]
50 50 # expand keywords in every python file except those matching "x*"
51 51 **.py =
52 52 x* = ignore
53 53
54 54 Note: the more specific you are in your filename patterns
55 55 the less you lose speed in huge repos.
56 56
57 57 For [keywordmaps] template mapping and expansion demonstration and
58 58 control run "hg kwdemo".
59 59
60 60 An additional date template filter {date|utcdate} is provided.
61 61
62 62 The default template mappings (view with "hg kwdemo -d") can be replaced
63 63 with customized keywords and templates.
64 64 Again, run "hg kwdemo" to control the results of your config changes.
65 65
66 66 Before changing/disabling active keywords, run "hg kwshrink" to avoid
67 67 the risk of inadvertedly storing expanded keywords in the change history.
68 68
69 69 To force expansion after enabling it, or a configuration change, run
70 70 "hg kwexpand".
71 71
72 72 Also, when committing with the record extension or using mq's qrecord, be aware
73 73 that keywords cannot be updated. Again, run "hg kwexpand" on the files in
74 74 question to update keyword expansions after all changes have been checked in.
75 75
76 76 Expansions spanning more than one line and incremental expansions,
77 77 like CVS' $Log$, are not supported. A keyword template map
78 78 "Log = {desc}" expands to the first line of the changeset description.
79 79 '''
80 80
81 81 from mercurial import commands, cmdutil, context, dispatch, filelog, revlog
82 82 from mercurial import patch, localrepo, templater, templatefilters, util
83 83 from mercurial.hgweb import webcommands
84 84 from mercurial.node import *
85 85 from mercurial.i18n import _
86 86 import re, shutil, tempfile, time
87 87
88 88 commands.optionalrepo += ' kwdemo'
89 89
90 90 # hg commands that do not act on keywords
91 91 nokwcommands = ('add addremove bundle copy export grep identify incoming init'
92 92 ' log outgoing push remove rename rollback tip'
93 93 ' convert email glog')
94 94
95 95 # hg commands that trigger expansion only when writing to working dir,
96 96 # not when reading filelog, and unexpand when reading from working dir
97 restricted = 'diff1 record qfold qimport qnew qpush qrefresh qrecord'
97 restricted = 'record qfold qimport qnew qpush qrefresh qrecord'
98 98
99 99 def utcdate(date):
100 100 '''Returns hgdate in cvs-like UTC format.'''
101 101 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
102 102
103 103
104 104 _kwtemplater = _cmd = _cmdoptions = None
105 105
106 106 # store originals of monkeypatches
107 107 _patchfile_init = patch.patchfile.__init__
108 _patch_diff = patch.diff
108 109 _dispatch_parse = dispatch._parse
109 110
110 111 def _kwpatchfile_init(self, ui, fname, missing=False):
111 112 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
112 113 rejects or conflicts due to expanded keywords in working dir.'''
113 114 _patchfile_init(self, ui, fname, missing=missing)
114 115 if _kwtemplater.matcher(self.fname):
115 116 # shrink keywords read from working dir
116 117 kwshrunk = _kwtemplater.shrink(''.join(self.lines))
117 118 self.lines = kwshrunk.splitlines(True)
118 119
120 def _kw_diff(repo, node1=None, node2=None, files=None, match=util.always,
121 fp=None, changes=None, opts=None):
122 # only expand if comparing against working dir
123 if node2 is not None:
124 _kwtemplater.matcher = util.never
125 if node1 is not None and node1 != repo.changectx().node():
126 _kwtemplater.restrict = True
127 _patch_diff(repo, node1=node1, node2=node2, files=files, match=match,
128 fp=fp, changes=changes, opts=opts)
129
119 130 def _kwweb_changeset(web, req, tmpl):
120 131 '''Wraps webcommands.changeset turning off keyword expansion.'''
121 132 _kwtemplater.matcher = util.never
122 133 return web.changeset(tmpl, web.changectx(req))
123 134
124 135 def _kwweb_filediff(web, req, tmpl):
125 136 '''Wraps webcommands.filediff turning off keyword expansion.'''
126 137 _kwtemplater.matcher = util.never
127 138 return web.filediff(tmpl, web.filectx(req))
128 139
129 140 def _kwdispatch_parse(ui, args):
130 '''Monkeypatch dispatch._parse to obtain
131 current command and command options (global _cmd, _cmdoptions).'''
132 global _cmd, _cmdoptions
133 _cmd, func, args, options, _cmdoptions = _dispatch_parse(ui, args)
134 return _cmd, func, args, options, _cmdoptions
141 '''Monkeypatch dispatch._parse to obtain running hg command.'''
142 global _cmd
143 _cmd, func, args, options, cmdoptions = _dispatch_parse(ui, args)
144 return _cmd, func, args, options, cmdoptions
135 145
136 146 # dispatch._parse is run before reposetup, so wrap it here
137 147 dispatch._parse = _kwdispatch_parse
138 148
139 149
140 150 class kwtemplater(object):
141 151 '''
142 152 Sets up keyword templates, corresponding keyword regex, and
143 153 provides keyword substitution functions.
144 154 '''
145 155 templates = {
146 156 'Revision': '{node|short}',
147 157 'Author': '{author|user}',
148 158 'Date': '{date|utcdate}',
149 159 'RCSFile': '{file|basename},v',
150 160 'Source': '{root}/{file},v',
151 161 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
152 162 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
153 163 }
154 164
155 165 def __init__(self, ui, repo, inc, exc, hgcmd):
156 166 self.ui = ui
157 167 self.repo = repo
158 168 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
159 169 self.restrict = hgcmd in restricted.split()
160 170 self.commitnode = None
161 171 self.path = ''
162 172
163 173 kwmaps = self.ui.configitems('keywordmaps')
164 174 if kwmaps: # override default templates
165 175 kwmaps = [(k, templater.parsestring(v, quoted=False))
166 176 for (k, v) in kwmaps]
167 177 self.templates = dict(kwmaps)
168 178 escaped = map(re.escape, self.templates.keys())
169 179 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
170 180 self.re_kw = re.compile(kwpat)
171 181
172 182 templatefilters.filters['utcdate'] = utcdate
173 183 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
174 184 False, '', False)
175 185
176 186 def substitute(self, node, data, subfunc):
177 187 '''Obtains file's changenode if commit node not given,
178 188 and calls given substitution function.'''
179 189 if self.commitnode:
180 190 fnode = self.commitnode
181 191 else:
182 192 c = context.filectx(self.repo, self.path, fileid=node)
183 193 fnode = c.node()
184 194
185 195 def kwsub(mobj):
186 196 '''Substitutes keyword using corresponding template.'''
187 197 kw = mobj.group(1)
188 198 self.ct.use_template(self.templates[kw])
189 199 self.ui.pushbuffer()
190 200 self.ct.show(changenode=fnode, root=self.repo.root, file=self.path)
191 201 ekw = templatefilters.firstline(self.ui.popbuffer())
192 202 return '$%s: %s $' % (kw, ekw)
193 203
194 204 return subfunc(kwsub, data)
195 205
196 206 def expand(self, node, data):
197 207 '''Returns data with keywords expanded.'''
198 208 if self.restrict or util.binary(data):
199 209 return data
200 210 return self.substitute(node, data, self.re_kw.sub)
201 211
202 212 def process(self, node, data, expand):
203 213 '''Returns a tuple: data, count.
204 214 Count is number of keywords/keyword substitutions,
205 215 telling caller whether to act on file containing data.'''
206 216 if util.binary(data):
207 217 return data, None
208 218 if expand:
209 219 return self.substitute(node, data, self.re_kw.subn)
210 220 return data, self.re_kw.search(data)
211 221
212 222 def shrink(self, text):
213 223 '''Returns text with all keyword substitutions removed.'''
214 224 if util.binary(text):
215 225 return text
216 226 return self.re_kw.sub(r'$\1$', text)
217 227
218 228 class kwfilelog(filelog.filelog):
219 229 '''
220 230 Subclass of filelog to hook into its read, add, cmp methods.
221 231 Keywords are "stored" unexpanded, and processed on reading.
222 232 '''
223 233 def __init__(self, opener, path):
224 234 super(kwfilelog, self).__init__(opener, path)
225 235 _kwtemplater.path = path
226 236
227 237 def kwctread(self, node, expand):
228 238 '''Reads expanding and counting keywords, called from _overwrite.'''
229 239 data = super(kwfilelog, self).read(node)
230 240 return _kwtemplater.process(node, data, expand)
231 241
232 242 def read(self, node):
233 243 '''Expands keywords when reading filelog.'''
234 244 data = super(kwfilelog, self).read(node)
235 245 return _kwtemplater.expand(node, data)
236 246
237 247 def add(self, text, meta, tr, link, p1=None, p2=None):
238 248 '''Removes keyword substitutions when adding to filelog.'''
239 249 text = _kwtemplater.shrink(text)
240 250 return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
241 251
242 252 def cmp(self, node, text):
243 253 '''Removes keyword substitutions for comparison.'''
244 254 text = _kwtemplater.shrink(text)
245 255 if self.renamed(node):
246 256 t2 = super(kwfilelog, self).read(node)
247 257 return t2 != text
248 258 return revlog.revlog.cmp(self, node, text)
249 259
250 260 def _iskwfile(f, link):
251 261 return not link(f) and _kwtemplater.matcher(f)
252 262
253 263 def _status(ui, repo, *pats, **opts):
254 264 '''Bails out if [keyword] configuration is not active.
255 265 Returns status of working directory.'''
256 266 if _kwtemplater:
257 267 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
258 268 return repo.status(files=files, match=match, list_clean=True)
259 269 if ui.configitems('keyword'):
260 270 raise util.Abort(_('[keyword] patterns cannot match'))
261 271 raise util.Abort(_('no [keyword] patterns configured'))
262 272
263 273 def _overwrite(ui, repo, node=None, expand=True, files=None):
264 274 '''Overwrites selected files expanding/shrinking keywords.'''
265 275 ctx = repo.changectx(node)
266 276 mf = ctx.manifest()
267 277 if node is not None: # commit
268 278 _kwtemplater.commitnode = node
269 279 files = [f for f in ctx.files() if f in mf]
270 280 notify = ui.debug
271 281 else: # kwexpand/kwshrink
272 282 notify = ui.note
273 283 candidates = [f for f in files if _iskwfile(f, mf.linkf)]
274 284 if candidates:
275 285 candidates.sort()
276 286 action = expand and 'expanding' or 'shrinking'
277 287 for f in candidates:
278 288 fp = repo.file(f, kwmatch=True)
279 289 data, kwfound = fp.kwctread(mf[f], expand)
280 290 if kwfound:
281 291 notify(_('overwriting %s %s keywords\n') % (f, action))
282 292 repo.wwrite(f, data, mf.flags(f))
283 293 repo.dirstate.normal(f)
284 294
285 295 def _kwfwrite(ui, repo, expand, *pats, **opts):
286 296 '''Selects files and passes them to _overwrite.'''
287 297 status = _status(ui, repo, *pats, **opts)
288 298 modified, added, removed, deleted, unknown, ignored, clean = status
289 299 if modified or added or removed or deleted:
290 300 raise util.Abort(_('outstanding uncommitted changes in given files'))
291 301 wlock = lock = None
292 302 try:
293 303 wlock = repo.wlock()
294 304 lock = repo.lock()
295 305 _overwrite(ui, repo, expand=expand, files=clean)
296 306 finally:
297 307 del wlock, lock
298 308
299 309
300 310 def demo(ui, repo, *args, **opts):
301 311 '''print [keywordmaps] configuration and an expansion example
302 312
303 313 Show current, custom, or default keyword template maps
304 314 and their expansion.
305 315
306 316 Extend current configuration by specifying maps as arguments
307 317 and optionally by reading from an additional hgrc file.
308 318
309 319 Override current keyword template maps with "default" option.
310 320 '''
311 321 def demostatus(stat):
312 322 ui.status(_('\n\t%s\n') % stat)
313 323
314 324 def demoitems(section, items):
315 325 ui.write('[%s]\n' % section)
316 326 for k, v in items:
317 327 ui.write('%s = %s\n' % (k, v))
318 328
319 329 msg = 'hg keyword config and expansion example'
320 330 kwstatus = 'current'
321 331 fn = 'demo.txt'
322 332 branchname = 'demobranch'
323 333 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
324 334 ui.note(_('creating temporary repo at %s\n') % tmpdir)
325 335 repo = localrepo.localrepository(ui, path=tmpdir, create=True)
326 336 ui.setconfig('keyword', fn, '')
327 337 if args or opts.get('rcfile'):
328 338 kwstatus = 'custom'
329 339 if opts.get('rcfile'):
330 340 ui.readconfig(opts.get('rcfile'))
331 341 if opts.get('default'):
332 342 kwstatus = 'default'
333 343 kwmaps = kwtemplater.templates
334 344 if ui.configitems('keywordmaps'):
335 345 # override maps from optional rcfile
336 346 for k, v in kwmaps.iteritems():
337 347 ui.setconfig('keywordmaps', k, v)
338 348 elif args:
339 349 # simulate hgrc parsing
340 350 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
341 351 fp = repo.opener('hgrc', 'w')
342 352 fp.writelines(rcmaps)
343 353 fp.close()
344 354 ui.readconfig(repo.join('hgrc'))
345 355 if not opts.get('default'):
346 356 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
347 357 reposetup(ui, repo)
348 358 for k, v in ui.configitems('extensions'):
349 359 if k.endswith('keyword'):
350 360 extension = '%s = %s' % (k, v)
351 361 break
352 362 demostatus('config using %s keyword template maps' % kwstatus)
353 363 ui.write('[extensions]\n%s\n' % extension)
354 364 demoitems('keyword', ui.configitems('keyword'))
355 365 demoitems('keywordmaps', kwmaps.iteritems())
356 366 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
357 367 repo.wopener(fn, 'w').write(keywords)
358 368 repo.add([fn])
359 369 path = repo.wjoin(fn)
360 370 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
361 371 ui.note(keywords)
362 372 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
363 373 # silence branch command if not verbose
364 374 quiet = ui.quiet
365 375 ui.quiet = not ui.verbose
366 376 commands.branch(ui, repo, branchname)
367 377 ui.quiet = quiet
368 378 for name, cmd in ui.configitems('hooks'):
369 379 if name.split('.', 1)[0].find('commit') > -1:
370 380 repo.ui.setconfig('hooks', name, '')
371 381 ui.note(_('unhooked all commit hooks\n'))
372 382 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
373 383 repo.commit(text=msg)
374 384 format = ui.verbose and ' in %s' % path or ''
375 385 demostatus('%s keywords expanded%s' % (kwstatus, format))
376 386 ui.write(repo.wread(fn))
377 387 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
378 388 shutil.rmtree(tmpdir, ignore_errors=True)
379 389
380 390 def expand(ui, repo, *pats, **opts):
381 391 '''expand keywords in working directory
382 392
383 393 Run after (re)enabling keyword expansion.
384 394
385 395 kwexpand refuses to run if given files contain local changes.
386 396 '''
387 397 # 3rd argument sets expansion to True
388 398 _kwfwrite(ui, repo, True, *pats, **opts)
389 399
390 400 def files(ui, repo, *pats, **opts):
391 401 '''print files currently configured for keyword expansion
392 402
393 403 Crosscheck which files in working directory are potential targets for
394 404 keyword expansion.
395 405 That is, files matched by [keyword] config patterns but not symlinks.
396 406 '''
397 407 status = _status(ui, repo, *pats, **opts)
398 408 modified, added, removed, deleted, unknown, ignored, clean = status
399 409 files = modified + added + clean
400 410 if opts.get('untracked'):
401 411 files += unknown
402 412 files.sort()
403 413 wctx = repo.workingctx()
404 414 islink = lambda p: 'l' in wctx.fileflags(p)
405 415 kwfiles = [f for f in files if _iskwfile(f, islink)]
406 416 cwd = pats and repo.getcwd() or ''
407 417 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
408 418 if opts.get('all') or opts.get('ignore'):
409 419 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
410 420 for char, filenames in kwfstats:
411 421 format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
412 422 for f in filenames:
413 423 ui.write(format % repo.pathto(f, cwd))
414 424
415 425 def shrink(ui, repo, *pats, **opts):
416 426 '''revert expanded keywords in working directory
417 427
418 428 Run before changing/disabling active keywords
419 429 or if you experience problems with "hg import" or "hg merge".
420 430
421 431 kwshrink refuses to run if given files contain local changes.
422 432 '''
423 433 # 3rd argument sets expansion to False
424 434 _kwfwrite(ui, repo, False, *pats, **opts)
425 435
426 436
427 437 def reposetup(ui, repo):
428 438 '''Sets up repo as kwrepo for keyword substitution.
429 439 Overrides file method to return kwfilelog instead of filelog
430 440 if file matches user configuration.
431 441 Wraps commit to overwrite configured files with updated
432 442 keyword substitutions.
433 443 This is done for local repos only, and only if there are
434 444 files configured at all for keyword substitution.'''
435 445
436 446 global _kwtemplater
437 hgcmd, hgcmdopts = _cmd, _cmdoptions
438 447
439 448 try:
440 if (not repo.local() or hgcmd in nokwcommands.split()
449 if (not repo.local() or _cmd in nokwcommands.split()
441 450 or '.hg' in util.splitpath(repo.root)
442 451 or repo._url.startswith('bundle:')):
443 452 return
444 453 except AttributeError:
445 454 pass
446 455
447 456 inc, exc = [], ['.hg*']
448 457 for pat, opt in ui.configitems('keyword'):
449 458 if opt != 'ignore':
450 459 inc.append(pat)
451 460 else:
452 461 exc.append(pat)
453 462 if not inc:
454 463 return
455 464
456 if hgcmd == 'diff':
457 # only expand if comparing against working dir
458 node1, node2 = cmdutil.revpair(repo, hgcmdopts.get('rev'))
459 if node2 is not None:
460 return
461 # shrink if rev is not current node
462 if node1 is not None and node1 != repo.changectx().node():
463 hgcmd = 'diff1'
464
465 _kwtemplater = kwtemplater(ui, repo, inc, exc, hgcmd)
465 _kwtemplater = kwtemplater(ui, repo, inc, exc, _cmd)
466 466
467 467 class kwrepo(repo.__class__):
468 468 def file(self, f, kwmatch=False):
469 469 if f[0] == '/':
470 470 f = f[1:]
471 471 if kwmatch or _kwtemplater.matcher(f):
472 472 return kwfilelog(self.sopener, f)
473 473 return filelog.filelog(self.sopener, f)
474 474
475 475 def wread(self, filename):
476 476 data = super(kwrepo, self).wread(filename)
477 477 if _kwtemplater.restrict and _kwtemplater.matcher(filename):
478 478 return _kwtemplater.shrink(data)
479 479 return data
480 480
481 481 def commit(self, files=None, text='', user=None, date=None,
482 482 match=util.always, force=False, force_editor=False,
483 483 p1=None, p2=None, extra={}, empty_ok=False):
484 484 wlock = lock = None
485 485 _p1 = _p2 = None
486 486 try:
487 487 wlock = self.wlock()
488 488 lock = self.lock()
489 489 # store and postpone commit hooks
490 490 commithooks = {}
491 491 for name, cmd in ui.configitems('hooks'):
492 492 if name.split('.', 1)[0] == 'commit':
493 493 commithooks[name] = cmd
494 494 ui.setconfig('hooks', name, None)
495 495 if commithooks:
496 496 # store parents for commit hook environment
497 497 if p1 is None:
498 498 _p1, _p2 = repo.dirstate.parents()
499 499 else:
500 500 _p1, _p2 = p1, p2 or nullid
501 501 _p1 = hex(_p1)
502 502 if _p2 == nullid:
503 503 _p2 = ''
504 504 else:
505 505 _p2 = hex(_p2)
506 506
507 507 node = super(kwrepo,
508 508 self).commit(files=files, text=text, user=user,
509 509 date=date, match=match, force=force,
510 510 force_editor=force_editor,
511 511 p1=p1, p2=p2, extra=extra,
512 512 empty_ok=empty_ok)
513 513
514 514 # restore commit hooks
515 515 for name, cmd in commithooks.iteritems():
516 516 ui.setconfig('hooks', name, cmd)
517 517 if node is not None:
518 518 _overwrite(ui, self, node=node)
519 519 repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
520 520 return node
521 521 finally:
522 522 del wlock, lock
523 523
524 524 repo.__class__ = kwrepo
525 525 patch.patchfile.__init__ = _kwpatchfile_init
526 patch.diff = _kw_diff
526 527 webcommands.changeset = webcommands.rev = _kwweb_changeset
527 528 webcommands.filediff = webcommands.diff = _kwweb_filediff
528 529
529 530
530 531 cmdtable = {
531 532 'kwdemo':
532 533 (demo,
533 534 [('d', 'default', None, _('show default keyword template maps')),
534 535 ('f', 'rcfile', [], _('read maps from rcfile'))],
535 536 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
536 537 'kwexpand': (expand, commands.walkopts,
537 538 _('hg kwexpand [OPTION]... [FILE]...')),
538 539 'kwfiles':
539 540 (files,
540 541 [('a', 'all', None, _('show keyword status flags of all files')),
541 542 ('i', 'ignore', None, _('show files excluded from expansion')),
542 543 ('u', 'untracked', None, _('additionally show untracked files')),
543 544 ] + commands.walkopts,
544 545 _('hg kwfiles [OPTION]... [FILE]...')),
545 546 'kwshrink': (shrink, commands.walkopts,
546 547 _('hg kwshrink [OPTION]... [FILE]...')),
547 548 }
General Comments 0
You need to be logged in to leave comments. Login now