##// END OF EJS Templates
keyword: improve help for kwfiles...
Christian Ebert -
r8957:7672d8e1 default
parent child Browse files
Show More
@@ -1,540 +1,543 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 of the
6 6 # GNU General Public License version 2, 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
15 15 # audience not running a version control system.
16 16 #
17 17 # For in-depth discussion refer to
18 18 # <http://mercurial.selenic.com/wiki/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 # Files to act upon/ignore are specified in the [keyword] section.
25 25 # Customized keyword template mappings in the [keywordmaps] section.
26 26 #
27 27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
28 28
29 29 '''expand keywords in tracked files
30 30
31 31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
32 32 tracked text files selected by your configuration.
33 33
34 34 Keywords are only expanded in local repositories and not stored in the
35 35 change history. The mechanism can be regarded as a convenience for the
36 36 current user or for archive distribution.
37 37
38 38 Configuration is done in the [keyword] and [keywordmaps] sections of
39 39 hgrc files.
40 40
41 41 Example:
42 42
43 43 [keyword]
44 44 # expand keywords in every python file except those matching "x*"
45 45 **.py =
46 46 x* = ignore
47 47
48 48 Note: the more specific you are in your filename patterns
49 49 the less you lose speed in huge repositories.
50 50
51 51 For [keywordmaps] template mapping and expansion demonstration and
52 52 control run "hg kwdemo".
53 53
54 54 An additional date template filter {date|utcdate} is provided.
55 55
56 56 The default template mappings (view with "hg kwdemo -d") can be
57 57 replaced with customized keywords and templates. Again, run "hg
58 58 kwdemo" to control the results of your config changes.
59 59
60 60 Before changing/disabling active keywords, run "hg kwshrink" to avoid
61 61 the risk of inadvertently storing expanded keywords in the change
62 62 history.
63 63
64 64 To force expansion after enabling it, or a configuration change, run
65 65 "hg kwexpand".
66 66
67 67 Also, when committing with the record extension or using mq's qrecord,
68 68 be aware that keywords cannot be updated. Again, run "hg kwexpand" on
69 69 the files in question to update keyword expansions after all changes
70 70 have been checked in.
71 71
72 72 Expansions spanning more than one line and incremental expansions,
73 73 like CVS' $Log$, are not supported. A keyword template map
74 74 "Log = {desc}" expands to the first line of the changeset description.
75 75 '''
76 76
77 77 from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions
78 78 from mercurial import patch, localrepo, templater, templatefilters, util, match
79 79 from mercurial.hgweb import webcommands
80 80 from mercurial.lock import release
81 81 from mercurial.node import nullid, hex
82 82 from mercurial.i18n import _
83 83 import re, shutil, tempfile, time
84 84
85 85 commands.optionalrepo += ' kwdemo'
86 86
87 87 # hg commands that do not act on keywords
88 88 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
89 89 ' log outgoing push rename rollback tip verify'
90 90 ' convert email glog')
91 91
92 92 # hg commands that trigger expansion only when writing to working dir,
93 93 # not when reading filelog, and unexpand when reading from working dir
94 94 restricted = 'merge record resolve qfold qimport qnew qpush qrefresh qrecord'
95 95
96 96 def utcdate(date):
97 97 '''Returns hgdate in cvs-like UTC format.'''
98 98 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
99 99
100 100 # make keyword tools accessible
101 101 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
102 102
103 103
104 104 class kwtemplater(object):
105 105 '''
106 106 Sets up keyword templates, corresponding keyword regex, and
107 107 provides keyword substitution functions.
108 108 '''
109 109 templates = {
110 110 'Revision': '{node|short}',
111 111 'Author': '{author|user}',
112 112 'Date': '{date|utcdate}',
113 113 'RCSFile': '{file|basename},v',
114 114 'Source': '{root}/{file},v',
115 115 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
116 116 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
117 117 }
118 118
119 119 def __init__(self, ui, repo):
120 120 self.ui = ui
121 121 self.repo = repo
122 122 self.match = match.match(repo.root, '', [],
123 123 kwtools['inc'], kwtools['exc'])
124 124 self.restrict = kwtools['hgcmd'] in restricted.split()
125 125
126 126 kwmaps = self.ui.configitems('keywordmaps')
127 127 if kwmaps: # override default templates
128 128 kwmaps = [(k, templater.parsestring(v, False))
129 129 for (k, v) in kwmaps]
130 130 self.templates = dict(kwmaps)
131 131 escaped = map(re.escape, self.templates.keys())
132 132 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
133 133 self.re_kw = re.compile(kwpat)
134 134
135 135 templatefilters.filters['utcdate'] = utcdate
136 136 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
137 137 False, None, '', False)
138 138
139 139 def substitute(self, data, path, ctx, subfunc):
140 140 '''Replaces keywords in data with expanded template.'''
141 141 def kwsub(mobj):
142 142 kw = mobj.group(1)
143 143 self.ct.use_template(self.templates[kw])
144 144 self.ui.pushbuffer()
145 145 self.ct.show(ctx, root=self.repo.root, file=path)
146 146 ekw = templatefilters.firstline(self.ui.popbuffer())
147 147 return '$%s: %s $' % (kw, ekw)
148 148 return subfunc(kwsub, data)
149 149
150 150 def expand(self, path, node, data):
151 151 '''Returns data with keywords expanded.'''
152 152 if not self.restrict and self.match(path) and not util.binary(data):
153 153 ctx = self.repo.filectx(path, fileid=node).changectx()
154 154 return self.substitute(data, path, ctx, self.re_kw.sub)
155 155 return data
156 156
157 157 def iskwfile(self, path, flagfunc):
158 158 '''Returns true if path matches [keyword] pattern
159 159 and is not a symbolic link.
160 160 Caveat: localrepository._link fails on Windows.'''
161 161 return self.match(path) and not 'l' in flagfunc(path)
162 162
163 163 def overwrite(self, node, expand, files):
164 164 '''Overwrites selected files expanding/shrinking keywords.'''
165 165 ctx = self.repo[node]
166 166 mf = ctx.manifest()
167 167 if node is not None: # commit
168 168 files = [f for f in ctx.files() if f in mf]
169 169 notify = self.ui.debug
170 170 else: # kwexpand/kwshrink
171 171 notify = self.ui.note
172 172 candidates = [f for f in files if self.iskwfile(f, ctx.flags)]
173 173 if candidates:
174 174 self.restrict = True # do not expand when reading
175 175 msg = (expand and _('overwriting %s expanding keywords\n')
176 176 or _('overwriting %s shrinking keywords\n'))
177 177 for f in candidates:
178 178 fp = self.repo.file(f)
179 179 data = fp.read(mf[f])
180 180 if util.binary(data):
181 181 continue
182 182 if expand:
183 183 if node is None:
184 184 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
185 185 data, found = self.substitute(data, f, ctx,
186 186 self.re_kw.subn)
187 187 else:
188 188 found = self.re_kw.search(data)
189 189 if found:
190 190 notify(msg % f)
191 191 self.repo.wwrite(f, data, mf.flags(f))
192 192 self.repo.dirstate.normal(f)
193 193 self.restrict = False
194 194
195 195 def shrinktext(self, text):
196 196 '''Unconditionally removes all keyword substitutions from text.'''
197 197 return self.re_kw.sub(r'$\1$', text)
198 198
199 199 def shrink(self, fname, text):
200 200 '''Returns text with all keyword substitutions removed.'''
201 201 if self.match(fname) and not util.binary(text):
202 202 return self.shrinktext(text)
203 203 return text
204 204
205 205 def shrinklines(self, fname, lines):
206 206 '''Returns lines with keyword substitutions removed.'''
207 207 if self.match(fname):
208 208 text = ''.join(lines)
209 209 if not util.binary(text):
210 210 return self.shrinktext(text).splitlines(True)
211 211 return lines
212 212
213 213 def wread(self, fname, data):
214 214 '''If in restricted mode returns data read from wdir with
215 215 keyword substitutions removed.'''
216 216 return self.restrict and self.shrink(fname, data) or data
217 217
218 218 class kwfilelog(filelog.filelog):
219 219 '''
220 220 Subclass of filelog to hook into its read, add, cmp methods.
221 221 Keywords are "stored" unexpanded, and processed on reading.
222 222 '''
223 223 def __init__(self, opener, kwt, path):
224 224 super(kwfilelog, self).__init__(opener, path)
225 225 self.kwt = kwt
226 226 self.path = path
227 227
228 228 def read(self, node):
229 229 '''Expands keywords when reading filelog.'''
230 230 data = super(kwfilelog, self).read(node)
231 231 return self.kwt.expand(self.path, node, data)
232 232
233 233 def add(self, text, meta, tr, link, p1=None, p2=None):
234 234 '''Removes keyword substitutions when adding to filelog.'''
235 235 text = self.kwt.shrink(self.path, text)
236 236 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
237 237
238 238 def cmp(self, node, text):
239 239 '''Removes keyword substitutions for comparison.'''
240 240 text = self.kwt.shrink(self.path, text)
241 241 if self.renamed(node):
242 242 t2 = super(kwfilelog, self).read(node)
243 243 return t2 != text
244 244 return revlog.revlog.cmp(self, node, text)
245 245
246 246 def _status(ui, repo, kwt, unknown, *pats, **opts):
247 247 '''Bails out if [keyword] configuration is not active.
248 248 Returns status of working directory.'''
249 249 if kwt:
250 250 match = cmdutil.match(repo, pats, opts)
251 251 return repo.status(match=match, unknown=unknown, clean=True)
252 252 if ui.configitems('keyword'):
253 253 raise util.Abort(_('[keyword] patterns cannot match'))
254 254 raise util.Abort(_('no [keyword] patterns configured'))
255 255
256 256 def _kwfwrite(ui, repo, expand, *pats, **opts):
257 257 '''Selects files and passes them to kwtemplater.overwrite.'''
258 258 if repo.dirstate.parents()[1] != nullid:
259 259 raise util.Abort(_('outstanding uncommitted merge'))
260 260 kwt = kwtools['templater']
261 261 status = _status(ui, repo, kwt, False, *pats, **opts)
262 262 modified, added, removed, deleted = status[:4]
263 263 if modified or added or removed or deleted:
264 264 raise util.Abort(_('outstanding uncommitted changes'))
265 265 wlock = lock = None
266 266 try:
267 267 wlock = repo.wlock()
268 268 lock = repo.lock()
269 269 kwt.overwrite(None, expand, status[6])
270 270 finally:
271 271 release(lock, wlock)
272 272
273 273 def demo(ui, repo, *args, **opts):
274 274 '''print [keywordmaps] configuration and an expansion example
275 275
276 276 Show current, custom, or default keyword template maps and their
277 277 expansions.
278 278
279 279 Extend current configuration by specifying maps as arguments and
280 280 optionally by reading from an additional hgrc file.
281 281
282 282 Override current keyword template maps with "default" option.
283 283 '''
284 284 def demoitems(section, items):
285 285 ui.write('[%s]\n' % section)
286 286 for k, v in items:
287 287 ui.write('%s = %s\n' % (k, v))
288 288
289 289 msg = 'hg keyword config and expansion example'
290 290 kwstatus = 'current'
291 291 fn = 'demo.txt'
292 292 branchname = 'demobranch'
293 293 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
294 294 ui.note(_('creating temporary repository at %s\n') % tmpdir)
295 295 repo = localrepo.localrepository(ui, tmpdir, True)
296 296 ui.setconfig('keyword', fn, '')
297 297 if args or opts.get('rcfile'):
298 298 kwstatus = 'custom'
299 299 if opts.get('rcfile'):
300 300 ui.readconfig(opts.get('rcfile'))
301 301 if opts.get('default'):
302 302 kwstatus = 'default'
303 303 kwmaps = kwtemplater.templates
304 304 if ui.configitems('keywordmaps'):
305 305 # override maps from optional rcfile
306 306 for k, v in kwmaps.iteritems():
307 307 ui.setconfig('keywordmaps', k, v)
308 308 elif args:
309 309 # simulate hgrc parsing
310 310 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
311 311 fp = repo.opener('hgrc', 'w')
312 312 fp.writelines(rcmaps)
313 313 fp.close()
314 314 ui.readconfig(repo.join('hgrc'))
315 315 if not opts.get('default'):
316 316 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
317 317 uisetup(ui)
318 318 reposetup(ui, repo)
319 319 for k, v in ui.configitems('extensions'):
320 320 if k.endswith('keyword'):
321 321 extension = '%s = %s' % (k, v)
322 322 break
323 323 ui.status(_('\n\tconfig using %s keyword template maps\n') % kwstatus)
324 324 ui.write('[extensions]\n%s\n' % extension)
325 325 demoitems('keyword', ui.configitems('keyword'))
326 326 demoitems('keywordmaps', kwmaps.iteritems())
327 327 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
328 328 repo.wopener(fn, 'w').write(keywords)
329 329 repo.add([fn])
330 330 path = repo.wjoin(fn)
331 331 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
332 332 ui.note(keywords)
333 333 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
334 334 # silence branch command if not verbose
335 335 quiet = ui.quiet
336 336 ui.quiet = not ui.verbose
337 337 commands.branch(ui, repo, branchname)
338 338 ui.quiet = quiet
339 339 for name, cmd in ui.configitems('hooks'):
340 340 if name.split('.', 1)[0].find('commit') > -1:
341 341 repo.ui.setconfig('hooks', name, '')
342 342 ui.note(_('unhooked all commit hooks\n'))
343 343 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
344 344 repo.commit(text=msg)
345 345 fmt = ui.verbose and ' in %s' % path or ''
346 346 ui.status(_('\n\t%s keywords expanded%s\n') % (kwstatus, fmt))
347 347 ui.write(repo.wread(fn))
348 348 ui.debug(_('\nremoving temporary repository %s\n') % tmpdir)
349 349 shutil.rmtree(tmpdir, ignore_errors=True)
350 350
351 351 def expand(ui, repo, *pats, **opts):
352 352 '''expand keywords in the working directory
353 353
354 354 Run after (re)enabling keyword expansion.
355 355
356 356 kwexpand refuses to run if given files contain local changes.
357 357 '''
358 358 # 3rd argument sets expansion to True
359 359 _kwfwrite(ui, repo, True, *pats, **opts)
360 360
361 361 def files(ui, repo, *pats, **opts):
362 '''print filenames configured for keyword expansion
362 '''show files configured for keyword expansion
363 363
364 Check which filenames in the working directory are matched by the
364 List which files in the working directory are matched by the
365 365 [keyword] configuration patterns.
366 366
367 367 Useful to prevent inadvertent keyword expansion and to speed up
368 execution by including only filenames that are actual candidates
368 execution by including only files that are actual candidates
369 369 for expansion.
370 370
371 Use -u/--untracked to display untracked filenames as well.
371 See "hg help keyword" on how to construct patterns both for
372 inclusion and exclusion of files.
373
374 Use -u/--untracked to list untracked files as well.
372 375
373 376 With -a/--all and -v/--verbose the codes used to show the status
374 377 of files are:
375 378 K = keyword expansion candidate
376 379 k = keyword expansion candidate (untracked)
377 380 I = ignored
378 381 i = ignored (untracked)
379 382 '''
380 383 kwt = kwtools['templater']
381 384 status = _status(ui, repo, kwt, opts.get('untracked'), *pats, **opts)
382 385 modified, added, removed, deleted, unknown, ignored, clean = status
383 386 files = sorted(modified + added + clean)
384 387 wctx = repo[None]
385 388 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
386 389 kwuntracked = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
387 390 cwd = pats and repo.getcwd() or ''
388 391 kwfstats = (not opts.get('ignore') and
389 392 (('K', kwfiles), ('k', kwuntracked),) or ())
390 393 if opts.get('all') or opts.get('ignore'):
391 394 kwfstats += (('I', [f for f in files if f not in kwfiles]),
392 395 ('i', [f for f in unknown if f not in kwuntracked]),)
393 396 for char, filenames in kwfstats:
394 397 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
395 398 for f in filenames:
396 399 ui.write(fmt % repo.pathto(f, cwd))
397 400
398 401 def shrink(ui, repo, *pats, **opts):
399 402 '''revert expanded keywords in the working directory
400 403
401 404 Run before changing/disabling active keywords or if you experience
402 405 problems with "hg import" or "hg merge".
403 406
404 407 kwshrink refuses to run if given files contain local changes.
405 408 '''
406 409 # 3rd argument sets expansion to False
407 410 _kwfwrite(ui, repo, False, *pats, **opts)
408 411
409 412
410 413 def uisetup(ui):
411 414 '''Collects [keyword] config in kwtools.
412 415 Monkeypatches dispatch._parse if needed.'''
413 416
414 417 for pat, opt in ui.configitems('keyword'):
415 418 if opt != 'ignore':
416 419 kwtools['inc'].append(pat)
417 420 else:
418 421 kwtools['exc'].append(pat)
419 422
420 423 if kwtools['inc']:
421 424 def kwdispatch_parse(orig, ui, args):
422 425 '''Monkeypatch dispatch._parse to obtain running hg command.'''
423 426 cmd, func, args, options, cmdoptions = orig(ui, args)
424 427 kwtools['hgcmd'] = cmd
425 428 return cmd, func, args, options, cmdoptions
426 429
427 430 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
428 431
429 432 def reposetup(ui, repo):
430 433 '''Sets up repo as kwrepo for keyword substitution.
431 434 Overrides file method to return kwfilelog instead of filelog
432 435 if file matches user configuration.
433 436 Wraps commit to overwrite configured files with updated
434 437 keyword substitutions.
435 438 Monkeypatches patch and webcommands.'''
436 439
437 440 try:
438 441 if (not repo.local() or not kwtools['inc']
439 442 or kwtools['hgcmd'] in nokwcommands.split()
440 443 or '.hg' in util.splitpath(repo.root)
441 444 or repo._url.startswith('bundle:')):
442 445 return
443 446 except AttributeError:
444 447 pass
445 448
446 449 kwtools['templater'] = kwt = kwtemplater(ui, repo)
447 450
448 451 class kwrepo(repo.__class__):
449 452 def file(self, f):
450 453 if f[0] == '/':
451 454 f = f[1:]
452 455 return kwfilelog(self.sopener, kwt, f)
453 456
454 457 def wread(self, filename):
455 458 data = super(kwrepo, self).wread(filename)
456 459 return kwt.wread(filename, data)
457 460
458 461 def commit(self, text='', user=None, date=None, match=None,
459 462 force=False, editor=None, extra={}):
460 463 wlock = lock = None
461 464 _p1 = _p2 = None
462 465 try:
463 466 wlock = self.wlock()
464 467 lock = self.lock()
465 468 # store and postpone commit hooks
466 469 commithooks = {}
467 470 for name, cmd in ui.configitems('hooks'):
468 471 if name.split('.', 1)[0] == 'commit':
469 472 commithooks[name] = cmd
470 473 ui.setconfig('hooks', name, None)
471 474 if commithooks:
472 475 # store parents for commit hook environment
473 476 _p1, _p2 = repo.dirstate.parents()
474 477 _p1 = hex(_p1)
475 478 if _p2 == nullid:
476 479 _p2 = ''
477 480 else:
478 481 _p2 = hex(_p2)
479 482
480 483 n = super(kwrepo, self).commit(text, user, date, match, force,
481 484 editor, extra)
482 485
483 486 # restore commit hooks
484 487 for name, cmd in commithooks.iteritems():
485 488 ui.setconfig('hooks', name, cmd)
486 489 if n is not None:
487 490 kwt.overwrite(n, True, None)
488 491 repo.hook('commit', node=n, parent1=_p1, parent2=_p2)
489 492 return n
490 493 finally:
491 494 release(lock, wlock)
492 495
493 496 # monkeypatches
494 497 def kwpatchfile_init(orig, self, ui, fname, opener, missing=False, eol=None):
495 498 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
496 499 rejects or conflicts due to expanded keywords in working dir.'''
497 500 orig(self, ui, fname, opener, missing, eol)
498 501 # shrink keywords read from working dir
499 502 self.lines = kwt.shrinklines(self.fname, self.lines)
500 503
501 504 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
502 505 opts=None):
503 506 '''Monkeypatch patch.diff to avoid expansion except when
504 507 comparing against working dir.'''
505 508 if node2 is not None:
506 509 kwt.match = util.never
507 510 elif node1 is not None and node1 != repo['.'].node():
508 511 kwt.restrict = True
509 512 return orig(repo, node1, node2, match, changes, opts)
510 513
511 514 def kwweb_skip(orig, web, req, tmpl):
512 515 '''Wraps webcommands.x turning off keyword expansion.'''
513 516 kwt.match = util.never
514 517 return orig(web, req, tmpl)
515 518
516 519 repo.__class__ = kwrepo
517 520
518 521 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
519 522 extensions.wrapfunction(patch, 'diff', kw_diff)
520 523 for c in 'annotate changeset rev filediff diff'.split():
521 524 extensions.wrapfunction(webcommands, c, kwweb_skip)
522 525
523 526 cmdtable = {
524 527 'kwdemo':
525 528 (demo,
526 529 [('d', 'default', None, _('show default keyword template maps')),
527 530 ('f', 'rcfile', [], _('read maps from rcfile'))],
528 531 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
529 532 'kwexpand': (expand, commands.walkopts,
530 533 _('hg kwexpand [OPTION]... [FILE]...')),
531 534 'kwfiles':
532 535 (files,
533 536 [('a', 'all', None, _('show keyword status flags of all files')),
534 537 ('i', 'ignore', None, _('show files excluded from expansion')),
535 538 ('u', 'untracked', None, _('additionally show untracked files')),
536 539 ] + commands.walkopts,
537 540 _('hg kwfiles [OPTION]... [FILE]...')),
538 541 'kwshrink': (shrink, commands.walkopts,
539 542 _('hg kwshrink [OPTION]... [FILE]...')),
540 543 }
@@ -1,502 +1,502 b''
1 1 % help
2 2 keyword extension - expand keywords in tracked files
3 3
4 4 This extension expands RCS/CVS-like or self-customized $Keywords$ in
5 5 tracked text files selected by your configuration.
6 6
7 7 Keywords are only expanded in local repositories and not stored in the
8 8 change history. The mechanism can be regarded as a convenience for the
9 9 current user or for archive distribution.
10 10
11 11 Configuration is done in the [keyword] and [keywordmaps] sections of
12 12 hgrc files.
13 13
14 14 Example:
15 15
16 16 [keyword]
17 17 # expand keywords in every python file except those matching "x*"
18 18 **.py =
19 19 x* = ignore
20 20
21 21 Note: the more specific you are in your filename patterns
22 22 the less you lose speed in huge repositories.
23 23
24 24 For [keywordmaps] template mapping and expansion demonstration and
25 25 control run "hg kwdemo".
26 26
27 27 An additional date template filter {date|utcdate} is provided.
28 28
29 29 The default template mappings (view with "hg kwdemo -d") can be
30 30 replaced with customized keywords and templates. Again, run "hg
31 31 kwdemo" to control the results of your config changes.
32 32
33 33 Before changing/disabling active keywords, run "hg kwshrink" to avoid
34 34 the risk of inadvertently storing expanded keywords in the change
35 35 history.
36 36
37 37 To force expansion after enabling it, or a configuration change, run
38 38 "hg kwexpand".
39 39
40 40 Also, when committing with the record extension or using mq's qrecord,
41 41 be aware that keywords cannot be updated. Again, run "hg kwexpand" on
42 42 the files in question to update keyword expansions after all changes
43 43 have been checked in.
44 44
45 45 Expansions spanning more than one line and incremental expansions,
46 46 like CVS' $Log$, are not supported. A keyword template map
47 47 "Log = {desc}" expands to the first line of the changeset description.
48 48
49 49 list of commands:
50 50
51 51 kwdemo print [keywordmaps] configuration and an expansion example
52 52 kwexpand expand keywords in the working directory
53 kwfiles print filenames configured for keyword expansion
53 kwfiles show files configured for keyword expansion
54 54 kwshrink revert expanded keywords in the working directory
55 55
56 56 enabled extensions:
57 57
58 58 keyword expand keywords in tracked files
59 59 mq manage a stack of patches
60 60 notify hooks for sending email notifications at commit/push time
61 61
62 62 use "hg -v help keyword" to show aliases and global options
63 63 % hg kwdemo
64 64 [extensions]
65 65 hgext.keyword =
66 66 [keyword]
67 67 * =
68 68 b = ignore
69 69 demo.txt =
70 70 [keywordmaps]
71 71 RCSFile = {file|basename},v
72 72 Author = {author|user}
73 73 Header = {root}/{file},v {node|short} {date|utcdate} {author|user}
74 74 Source = {root}/{file},v
75 75 Date = {date|utcdate}
76 76 Id = {file|basename},v {node|short} {date|utcdate} {author|user}
77 77 Revision = {node|short}
78 78 $RCSFile: demo.txt,v $
79 79 $Author: test $
80 80 $Header: /TMP/demo.txt,v xxxxxxxxxxxx 2000/00/00 00:00:00 test $
81 81 $Source: /TMP/demo.txt,v $
82 82 $Date: 2000/00/00 00:00:00 $
83 83 $Id: demo.txt,v xxxxxxxxxxxx 2000/00/00 00:00:00 test $
84 84 $Revision: xxxxxxxxxxxx $
85 85 [extensions]
86 86 hgext.keyword =
87 87 [keyword]
88 88 * =
89 89 b = ignore
90 90 demo.txt =
91 91 [keywordmaps]
92 92 Branch = {branches}
93 93 $Branch: demobranch $
94 94 % kwshrink should exit silently in empty/invalid repo
95 95 pulling from test-keyword.hg
96 96 requesting all changes
97 97 adding changesets
98 98 adding manifests
99 99 adding file changes
100 100 added 1 changesets with 1 changes to 1 files
101 101 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 102 % cat
103 103 expand $Id$
104 104 do not process $Id:
105 105 xxx $
106 106 ignore $Id$
107 107 % addremove
108 108 adding a
109 109 adding b
110 110 % status
111 111 A a
112 112 A b
113 113 % default keyword expansion including commit hook
114 114 % interrupted commit should not change state or run commit hook
115 115 abort: empty commit message
116 116 % status
117 117 A a
118 118 A b
119 119 % commit
120 120 a
121 121 b
122 122 overwriting a expanding keywords
123 123 running hook commit.test: cp a hooktest
124 124 committed changeset 1:ef63ca68695bc9495032c6fda1350c71e6d256e9
125 125 % status
126 126 ? hooktest
127 127 % identify
128 128 ef63ca68695b
129 129 % cat
130 130 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
131 131 do not process $Id:
132 132 xxx $
133 133 ignore $Id$
134 134 % hg cat
135 135 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
136 136 do not process $Id:
137 137 xxx $
138 138 ignore $Id$
139 139 a
140 140 % diff a hooktest
141 141 % removing commit hook from config
142 142 % bundle
143 143 2 changesets found
144 144 % notify on pull to check whether keywords stay as is in email
145 145 % ie. if patch.diff wrapper acts as it should
146 146 % pull from bundle
147 147 pulling from ../kw.hg
148 148 requesting all changes
149 149 adding changesets
150 150 adding manifests
151 151 adding file changes
152 152 added 2 changesets with 3 changes to 3 files
153 153
154 154 diff -r 000000000000 -r a2392c293916 sym
155 155 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
156 156 +++ b/sym Sat Feb 09 20:25:47 2008 +0100
157 157 @@ -0,0 +1,1 @@
158 158 +a
159 159 \ No newline at end of file
160 160
161 161 diff -r a2392c293916 -r ef63ca68695b a
162 162 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
163 163 +++ b/a Thu Jan 01 00:00:00 1970 +0000
164 164 @@ -0,0 +1,3 @@
165 165 +expand $Id$
166 166 +do not process $Id:
167 167 +xxx $
168 168 diff -r a2392c293916 -r ef63ca68695b b
169 169 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
170 170 +++ b/b Thu Jan 01 00:00:00 1970 +0000
171 171 @@ -0,0 +1,1 @@
172 172 +ignore $Id$
173 173 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
174 174 % remove notify config
175 175 % touch
176 176 % status
177 177 % update
178 178 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
179 179 % cat
180 180 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
181 181 do not process $Id:
182 182 xxx $
183 183 ignore $Id$
184 184 % check whether expansion is filewise
185 185 % commit c
186 186 adding c
187 187 % force expansion
188 188 overwriting a expanding keywords
189 189 overwriting c expanding keywords
190 190 % compare changenodes in a c
191 191 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
192 192 do not process $Id:
193 193 xxx $
194 194 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
195 195 tests for different changenodes
196 196 % qinit -c
197 197 % qimport
198 198 % qcommit
199 199 % keywords should not be expanded in patch
200 200 # HG changeset patch
201 201 # User User Name <user@example.com>
202 202 # Date 1 0
203 203 # Node ID 40a904bbbe4cd4ab0a1f28411e35db26341a40ad
204 204 # Parent ef63ca68695bc9495032c6fda1350c71e6d256e9
205 205 cndiff
206 206
207 207 diff -r ef63ca68695b -r 40a904bbbe4c c
208 208 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
209 209 +++ b/c Thu Jan 01 00:00:01 1970 +0000
210 210 @@ -0,0 +1,2 @@
211 211 +$Id$
212 212 +tests for different changenodes
213 213 % qpop
214 214 patch queue now empty
215 215 % qgoto - should imply qpush
216 216 applying mqtest.diff
217 217 now at: mqtest.diff
218 218 % cat
219 219 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
220 220 tests for different changenodes
221 221 % qpop and move on
222 222 patch queue now empty
223 223 % copy
224 224 % kwfiles added
225 225 a
226 226 c
227 227 % commit
228 228 c
229 229 c: copy a:0045e12f6c5791aac80ca6cbfd97709a88307292
230 230 overwriting c expanding keywords
231 231 committed changeset 2:e22d299ac0c2bd8897b3df5114374b9e4d4ca62f
232 232 % cat a c
233 233 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
234 234 do not process $Id:
235 235 xxx $
236 236 expand $Id: c,v e22d299ac0c2 1970/01/01 00:00:01 user $
237 237 do not process $Id:
238 238 xxx $
239 239 % touch copied c
240 240 % status
241 241 % kwfiles
242 242 a
243 243 c
244 244 % diff --rev
245 245 diff -r ef63ca68695b c
246 246 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
247 247 @@ -0,0 +1,3 @@
248 248 +expand $Id$
249 249 +do not process $Id:
250 250 +xxx $
251 251 % rollback
252 252 rolling back last transaction
253 253 % status
254 254 A c
255 255 % update -C
256 256 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
257 257 % custom keyword expansion
258 258 % try with kwdemo
259 259 [extensions]
260 260 hgext.keyword =
261 261 [keyword]
262 262 * =
263 263 b = ignore
264 264 demo.txt =
265 265 [keywordmaps]
266 266 Xinfo = {author}: {desc}
267 267 $Xinfo: test: hg keyword config and expansion example $
268 268 % cat
269 269 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
270 270 do not process $Id:
271 271 xxx $
272 272 ignore $Id$
273 273 % hg cat
274 274 expand $Id: a ef63ca68695b Thu, 01 Jan 1970 00:00:00 +0000 user $
275 275 do not process $Id:
276 276 xxx $
277 277 ignore $Id$
278 278 a
279 279 % interrupted commit should not change state
280 280 abort: empty commit message
281 281 % status
282 282 M a
283 283 ? c
284 284 ? log
285 285 % commit
286 286 a
287 287 overwriting a expanding keywords
288 288 committed changeset 2:bb948857c743469b22bbf51f7ec8112279ca5d83
289 289 % status
290 290 ? c
291 291 % verify
292 292 checking changesets
293 293 checking manifests
294 294 crosschecking files in changesets and manifests
295 295 checking files
296 296 3 files, 3 changesets, 4 total revisions
297 297 % cat
298 298 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
299 299 do not process $Id:
300 300 xxx $
301 301 $Xinfo: User Name <user@example.com>: firstline $
302 302 ignore $Id$
303 303 % hg cat
304 304 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
305 305 do not process $Id:
306 306 xxx $
307 307 $Xinfo: User Name <user@example.com>: firstline $
308 308 ignore $Id$
309 309 a
310 310 % annotate
311 311 1: expand $Id$
312 312 1: do not process $Id:
313 313 1: xxx $
314 314 2: $Xinfo$
315 315 % remove
316 316 committed changeset 3:d14c712653769de926994cf7fbb06c8fbd68f012
317 317 % status
318 318 ? c
319 319 % rollback
320 320 rolling back last transaction
321 321 % status
322 322 R a
323 323 ? c
324 324 % revert a
325 325 % cat a
326 326 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
327 327 do not process $Id:
328 328 xxx $
329 329 $Xinfo: User Name <user@example.com>: firstline $
330 330 % clone to test incoming
331 331 requesting all changes
332 332 adding changesets
333 333 adding manifests
334 334 adding file changes
335 335 added 2 changesets with 3 changes to 3 files
336 336 updating working directory
337 337 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
338 338 % incoming
339 339 comparing with test-keyword/Test
340 340 searching for changes
341 341 changeset: 2:bb948857c743
342 342 tag: tip
343 343 user: User Name <user@example.com>
344 344 date: Thu Jan 01 00:00:02 1970 +0000
345 345 summary: firstline
346 346
347 347 % commit rejecttest
348 348 a
349 349 overwriting a expanding keywords
350 350 committed changeset 2:85e279d709ffc28c9fdd1b868570985fc3d87082
351 351 % export
352 352 % import
353 353 applying ../rejecttest.diff
354 354 % cat
355 355 expand $Id: a 4e0994474d25 Thu, 01 Jan 1970 00:00:03 +0000 user $ rejecttest
356 356 do not process $Id: rejecttest
357 357 xxx $
358 358 $Xinfo: User Name <user@example.com>: rejects? $
359 359 ignore $Id$
360 360
361 361 % rollback
362 362 rolling back last transaction
363 363 % clean update
364 364 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
365 365 % kwexpand/kwshrink on selected files
366 366 % copy a x/a
367 367 % kwexpand a
368 368 overwriting a expanding keywords
369 369 % kwexpand x/a should abort
370 370 abort: outstanding uncommitted changes
371 371 x/a
372 372 x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e
373 373 overwriting x/a expanding keywords
374 374 committed changeset 3:cfa68229c1167443337266ebac453c73b1d5d16e
375 375 % cat a
376 376 expand $Id: x/a cfa68229c116 Thu, 01 Jan 1970 00:00:03 +0000 user $
377 377 do not process $Id:
378 378 xxx $
379 379 $Xinfo: User Name <user@example.com>: xa $
380 380 % kwshrink a inside directory x
381 381 overwriting x/a shrinking keywords
382 382 % cat a
383 383 expand $Id$
384 384 do not process $Id:
385 385 xxx $
386 386 $Xinfo$
387 387 % kwexpand nonexistent
388 388 nonexistent:
389 389 % hg serve
390 390 % expansion
391 391 % hgweb file
392 392 200 Script output follows
393 393
394 394 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
395 395 do not process $Id:
396 396 xxx $
397 397 $Xinfo: User Name <user@example.com>: firstline $
398 398 % no expansion
399 399 % hgweb annotate
400 400 200 Script output follows
401 401
402 402
403 403 user@1: expand $Id$
404 404 user@1: do not process $Id:
405 405 user@1: xxx $
406 406 user@2: $Xinfo$
407 407
408 408
409 409
410 410
411 411 % hgweb changeset
412 412 200 Script output follows
413 413
414 414
415 415 # HG changeset patch
416 416 # User User Name <user@example.com>
417 417 # Date 3 0
418 418 # Node ID cfa68229c1167443337266ebac453c73b1d5d16e
419 419 # Parent bb948857c743469b22bbf51f7ec8112279ca5d83
420 420 xa
421 421
422 422 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
423 423 +++ b/x/a Thu Jan 01 00:00:03 1970 +0000
424 424 @@ -0,0 +1,4 @@
425 425 +expand $Id$
426 426 +do not process $Id:
427 427 +xxx $
428 428 +$Xinfo$
429 429
430 430 % hgweb filediff
431 431 200 Script output follows
432 432
433 433
434 434 --- a/a Thu Jan 01 00:00:00 1970 +0000
435 435 +++ b/a Thu Jan 01 00:00:02 1970 +0000
436 436 @@ -1,3 +1,4 @@
437 437 expand $Id$
438 438 do not process $Id:
439 439 xxx $
440 440 +$Xinfo$
441 441
442 442
443 443
444 444
445 445 % errors encountered
446 446 % merge/resolve
447 447 % simplemerge
448 448 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
449 449 created new head
450 450 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
451 451 (branch merge, don't forget to commit)
452 452 $Id: m 8731e1dadc99 Thu, 01 Jan 1970 00:00:00 +0000 test $
453 453 foo
454 454 % conflict
455 455 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
456 456 created new head
457 457 merging m
458 458 warning: conflicts during merge.
459 459 merging m failed!
460 460 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
461 461 use 'hg resolve' to retry unresolved file merges or 'hg up --clean' to abandon
462 462 % keyword stays outside conflict zone
463 463 $Id$
464 464 <<<<<<< local
465 465 bar
466 466 =======
467 467 foo
468 468 >>>>>>> other
469 469 % resolve to local
470 470 $Id: m 43dfd2854b5b Thu, 01 Jan 1970 00:00:00 +0000 test $
471 471 bar
472 472 % switch off expansion
473 473 % kwshrink with unknown file u
474 474 overwriting a shrinking keywords
475 475 overwriting m shrinking keywords
476 476 overwriting x/a shrinking keywords
477 477 % cat
478 478 expand $Id$
479 479 do not process $Id:
480 480 xxx $
481 481 $Xinfo$
482 482 ignore $Id$
483 483 % hg cat
484 484 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
485 485 do not process $Id:
486 486 xxx $
487 487 $Xinfo: User Name <user@example.com>: firstline $
488 488 ignore $Id$
489 489 a
490 490 % cat
491 491 expand $Id$
492 492 do not process $Id:
493 493 xxx $
494 494 $Xinfo$
495 495 ignore $Id$
496 496 % hg cat
497 497 expand $Id$
498 498 do not process $Id:
499 499 xxx $
500 500 $Xinfo$
501 501 ignore $Id$
502 502 a
General Comments 0
You need to be logged in to leave comments. Login now