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