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