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