##// END OF EJS Templates
keyword: preserve file mode when overwriting
Christian Ebert -
r15070:e4c65158 stable
parent child Browse files
Show More
@@ -1,690 +1,693 b''
1 1 # keyword.py - $Keyword$ expansion for Mercurial
2 2 #
3 3 # Copyright 2007-2010 Christian Ebert <blacktrash@gmx.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 #
8 8 # $Id$
9 9 #
10 10 # Keyword expansion hack against the grain of a DSCM
11 11 #
12 12 # There are many good reasons why this is not needed in a distributed
13 13 # SCM, still it may be useful in very small projects based on single
14 14 # files (like LaTeX packages), that are mostly addressed to an
15 15 # audience not running a version control system.
16 16 #
17 17 # For in-depth discussion refer to
18 18 # <http://mercurial.selenic.com/wiki/KeywordPlan>.
19 19 #
20 20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 21 #
22 22 # Binary files are not touched.
23 23 #
24 24 # Files to act upon/ignore are specified in the [keyword] section.
25 25 # Customized keyword template mappings in the [keywordmaps] section.
26 26 #
27 27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
28 28
29 29 '''expand keywords in tracked files
30 30
31 31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
32 32 tracked text files selected by your configuration.
33 33
34 34 Keywords are only expanded in local repositories and not stored in the
35 35 change history. The mechanism can be regarded as a convenience for the
36 36 current user or for archive distribution.
37 37
38 38 Keywords expand to the changeset data pertaining to the latest change
39 39 relative to the working directory parent of each file.
40 40
41 41 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
42 42 sections of hgrc files.
43 43
44 44 Example::
45 45
46 46 [keyword]
47 47 # expand keywords in every python file except those matching "x*"
48 48 **.py =
49 49 x* = ignore
50 50
51 51 [keywordset]
52 52 # prefer svn- over cvs-like default keywordmaps
53 53 svn = True
54 54
55 55 .. note::
56 56 The more specific you are in your filename patterns the less you
57 57 lose speed in huge repositories.
58 58
59 59 For [keywordmaps] template mapping and expansion demonstration and
60 60 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
61 61 available templates and filters.
62 62
63 63 Three additional date template filters are provided:
64 64
65 65 :``utcdate``: "2006/09/18 15:13:13"
66 66 :``svnutcdate``: "2006-09-18 15:13:13Z"
67 67 :``svnisodate``: "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
68 68
69 69 The default template mappings (view with :hg:`kwdemo -d`) can be
70 70 replaced with customized keywords and templates. Again, run
71 71 :hg:`kwdemo` to control the results of your configuration changes.
72 72
73 73 Before changing/disabling active keywords, you must run :hg:`kwshrink`
74 74 to avoid storing expanded keywords in the change history.
75 75
76 76 To force expansion after enabling it, or a configuration change, run
77 77 :hg:`kwexpand`.
78 78
79 79 Expansions spanning more than one line and incremental expansions,
80 80 like CVS' $Log$, are not supported. A keyword template map "Log =
81 81 {desc}" expands to the first line of the changeset description.
82 82 '''
83 83
84 84 from mercurial import commands, context, cmdutil, dispatch, filelog, extensions
85 85 from mercurial import localrepo, match, patch, templatefilters, templater, util
86 86 from mercurial import scmutil
87 87 from mercurial.hgweb import webcommands
88 88 from mercurial.i18n import _
89 89 import os, re, shutil, tempfile
90 90
91 91 commands.optionalrepo += ' kwdemo'
92 92
93 93 cmdtable = {}
94 94 command = cmdutil.command(cmdtable)
95 95
96 96 # hg commands that do not act on keywords
97 97 nokwcommands = ('add addremove annotate bundle export grep incoming init log'
98 98 ' outgoing push tip verify convert email glog')
99 99
100 100 # hg commands that trigger expansion only when writing to working dir,
101 101 # not when reading filelog, and unexpand when reading from working dir
102 102 restricted = 'merge kwexpand kwshrink record qrecord resolve transplant'
103 103
104 104 # names of extensions using dorecord
105 105 recordextensions = 'record'
106 106
107 107 colortable = {
108 108 'kwfiles.enabled': 'green bold',
109 109 'kwfiles.deleted': 'cyan bold underline',
110 110 'kwfiles.enabledunknown': 'green',
111 111 'kwfiles.ignored': 'bold',
112 112 'kwfiles.ignoredunknown': 'none'
113 113 }
114 114
115 115 # date like in cvs' $Date
116 116 def utcdate(text):
117 117 ''':utcdate: Date. Returns a UTC-date in this format: "2009/08/18 11:00:13".
118 118 '''
119 119 return util.datestr((text[0], 0), '%Y/%m/%d %H:%M:%S')
120 120 # date like in svn's $Date
121 121 def svnisodate(text):
122 122 ''':svnisodate: Date. Returns a date in this format: "2009-08-18 13:00:13
123 123 +0200 (Tue, 18 Aug 2009)".
124 124 '''
125 125 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
126 126 # date like in svn's $Id
127 127 def svnutcdate(text):
128 128 ''':svnutcdate: Date. Returns a UTC-date in this format: "2009-08-18
129 129 11:00:13Z".
130 130 '''
131 131 return util.datestr((text[0], 0), '%Y-%m-%d %H:%M:%SZ')
132 132
133 133 templatefilters.filters.update({'utcdate': utcdate,
134 134 'svnisodate': svnisodate,
135 135 'svnutcdate': svnutcdate})
136 136
137 137 # make keyword tools accessible
138 138 kwtools = {'templater': None, 'hgcmd': ''}
139 139
140 140 def _defaultkwmaps(ui):
141 141 '''Returns default keywordmaps according to keywordset configuration.'''
142 142 templates = {
143 143 'Revision': '{node|short}',
144 144 'Author': '{author|user}',
145 145 }
146 146 kwsets = ({
147 147 'Date': '{date|utcdate}',
148 148 'RCSfile': '{file|basename},v',
149 149 'RCSFile': '{file|basename},v', # kept for backwards compatibility
150 150 # with hg-keyword
151 151 'Source': '{root}/{file},v',
152 152 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
153 153 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
154 154 }, {
155 155 'Date': '{date|svnisodate}',
156 156 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
157 157 'LastChangedRevision': '{node|short}',
158 158 'LastChangedBy': '{author|user}',
159 159 'LastChangedDate': '{date|svnisodate}',
160 160 })
161 161 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
162 162 return templates
163 163
164 164 def _shrinktext(text, subfunc):
165 165 '''Helper for keyword expansion removal in text.
166 166 Depending on subfunc also returns number of substitutions.'''
167 167 return subfunc(r'$\1$', text)
168 168
169 169 def _preselect(wstatus, changed):
170 170 '''Retrieves modfied and added files from a working directory state
171 171 and returns the subset of each contained in given changed files
172 172 retrieved from a change context.'''
173 173 modified, added = wstatus[:2]
174 174 modified = [f for f in modified if f in changed]
175 175 added = [f for f in added if f in changed]
176 176 return modified, added
177 177
178 178
179 179 class kwtemplater(object):
180 180 '''
181 181 Sets up keyword templates, corresponding keyword regex, and
182 182 provides keyword substitution functions.
183 183 '''
184 184
185 185 def __init__(self, ui, repo, inc, exc):
186 186 self.ui = ui
187 187 self.repo = repo
188 188 self.match = match.match(repo.root, '', [], inc, exc)
189 189 self.restrict = kwtools['hgcmd'] in restricted.split()
190 190 self.record = False
191 191
192 192 kwmaps = self.ui.configitems('keywordmaps')
193 193 if kwmaps: # override default templates
194 194 self.templates = dict((k, templater.parsestring(v, False))
195 195 for k, v in kwmaps)
196 196 else:
197 197 self.templates = _defaultkwmaps(self.ui)
198 198
199 199 @util.propertycache
200 200 def escape(self):
201 201 '''Returns bar-separated and escaped keywords.'''
202 202 return '|'.join(map(re.escape, self.templates.keys()))
203 203
204 204 @util.propertycache
205 205 def rekw(self):
206 206 '''Returns regex for unexpanded keywords.'''
207 207 return re.compile(r'\$(%s)\$' % self.escape)
208 208
209 209 @util.propertycache
210 210 def rekwexp(self):
211 211 '''Returns regex for expanded keywords.'''
212 212 return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
213 213
214 214 def substitute(self, data, path, ctx, subfunc):
215 215 '''Replaces keywords in data with expanded template.'''
216 216 def kwsub(mobj):
217 217 kw = mobj.group(1)
218 218 ct = cmdutil.changeset_templater(self.ui, self.repo,
219 219 False, None, '', False)
220 220 ct.use_template(self.templates[kw])
221 221 self.ui.pushbuffer()
222 222 ct.show(ctx, root=self.repo.root, file=path)
223 223 ekw = templatefilters.firstline(self.ui.popbuffer())
224 224 return '$%s: %s $' % (kw, ekw)
225 225 return subfunc(kwsub, data)
226 226
227 227 def linkctx(self, path, fileid):
228 228 '''Similar to filelog.linkrev, but returns a changectx.'''
229 229 return self.repo.filectx(path, fileid=fileid).changectx()
230 230
231 231 def expand(self, path, node, data):
232 232 '''Returns data with keywords expanded.'''
233 233 if not self.restrict and self.match(path) and not util.binary(data):
234 234 ctx = self.linkctx(path, node)
235 235 return self.substitute(data, path, ctx, self.rekw.sub)
236 236 return data
237 237
238 238 def iskwfile(self, cand, ctx):
239 239 '''Returns subset of candidates which are configured for keyword
240 240 expansion are not symbolic links.'''
241 241 return [f for f in cand if self.match(f) and not 'l' in ctx.flags(f)]
242 242
243 243 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
244 244 '''Overwrites selected files expanding/shrinking keywords.'''
245 245 if self.restrict or lookup or self.record: # exclude kw_copy
246 246 candidates = self.iskwfile(candidates, ctx)
247 247 if not candidates:
248 248 return
249 249 kwcmd = self.restrict and lookup # kwexpand/kwshrink
250 250 if self.restrict or expand and lookup:
251 251 mf = ctx.manifest()
252 252 lctx = ctx
253 253 re_kw = (self.restrict or rekw) and self.rekw or self.rekwexp
254 254 msg = (expand and _('overwriting %s expanding keywords\n')
255 255 or _('overwriting %s shrinking keywords\n'))
256 256 for f in candidates:
257 257 if self.restrict:
258 258 data = self.repo.file(f).read(mf[f])
259 259 else:
260 260 data = self.repo.wread(f)
261 261 if util.binary(data):
262 262 continue
263 263 if expand:
264 264 if lookup:
265 265 lctx = self.linkctx(f, mf[f])
266 266 data, found = self.substitute(data, f, lctx, re_kw.subn)
267 267 elif self.restrict:
268 268 found = re_kw.search(data)
269 269 else:
270 270 data, found = _shrinktext(data, re_kw.subn)
271 271 if found:
272 272 self.ui.note(msg % f)
273 fpath = self.repo.wjoin(f)
274 mode = os.lstat(fpath).st_mode
273 275 self.repo.wwrite(f, data, ctx.flags(f))
276 os.chmod(fpath, mode)
274 277 if kwcmd:
275 278 self.repo.dirstate.normal(f)
276 279 elif self.record:
277 280 self.repo.dirstate.normallookup(f)
278 281
279 282 def shrink(self, fname, text):
280 283 '''Returns text with all keyword substitutions removed.'''
281 284 if self.match(fname) and not util.binary(text):
282 285 return _shrinktext(text, self.rekwexp.sub)
283 286 return text
284 287
285 288 def shrinklines(self, fname, lines):
286 289 '''Returns lines with keyword substitutions removed.'''
287 290 if self.match(fname):
288 291 text = ''.join(lines)
289 292 if not util.binary(text):
290 293 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
291 294 return lines
292 295
293 296 def wread(self, fname, data):
294 297 '''If in restricted mode returns data read from wdir with
295 298 keyword substitutions removed.'''
296 299 return self.restrict and self.shrink(fname, data) or data
297 300
298 301 class kwfilelog(filelog.filelog):
299 302 '''
300 303 Subclass of filelog to hook into its read, add, cmp methods.
301 304 Keywords are "stored" unexpanded, and processed on reading.
302 305 '''
303 306 def __init__(self, opener, kwt, path):
304 307 super(kwfilelog, self).__init__(opener, path)
305 308 self.kwt = kwt
306 309 self.path = path
307 310
308 311 def read(self, node):
309 312 '''Expands keywords when reading filelog.'''
310 313 data = super(kwfilelog, self).read(node)
311 314 if self.renamed(node):
312 315 return data
313 316 return self.kwt.expand(self.path, node, data)
314 317
315 318 def add(self, text, meta, tr, link, p1=None, p2=None):
316 319 '''Removes keyword substitutions when adding to filelog.'''
317 320 text = self.kwt.shrink(self.path, text)
318 321 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
319 322
320 323 def cmp(self, node, text):
321 324 '''Removes keyword substitutions for comparison.'''
322 325 text = self.kwt.shrink(self.path, text)
323 326 return super(kwfilelog, self).cmp(node, text)
324 327
325 328 def _status(ui, repo, kwt, *pats, **opts):
326 329 '''Bails out if [keyword] configuration is not active.
327 330 Returns status of working directory.'''
328 331 if kwt:
329 332 return repo.status(match=scmutil.match(repo[None], pats, opts), clean=True,
330 333 unknown=opts.get('unknown') or opts.get('all'))
331 334 if ui.configitems('keyword'):
332 335 raise util.Abort(_('[keyword] patterns cannot match'))
333 336 raise util.Abort(_('no [keyword] patterns configured'))
334 337
335 338 def _kwfwrite(ui, repo, expand, *pats, **opts):
336 339 '''Selects files and passes them to kwtemplater.overwrite.'''
337 340 wctx = repo[None]
338 341 if len(wctx.parents()) > 1:
339 342 raise util.Abort(_('outstanding uncommitted merge'))
340 343 kwt = kwtools['templater']
341 344 wlock = repo.wlock()
342 345 try:
343 346 status = _status(ui, repo, kwt, *pats, **opts)
344 347 modified, added, removed, deleted, unknown, ignored, clean = status
345 348 if modified or added or removed or deleted:
346 349 raise util.Abort(_('outstanding uncommitted changes'))
347 350 kwt.overwrite(wctx, clean, True, expand)
348 351 finally:
349 352 wlock.release()
350 353
351 354 @command('kwdemo',
352 355 [('d', 'default', None, _('show default keyword template maps')),
353 356 ('f', 'rcfile', '',
354 357 _('read maps from rcfile'), _('FILE'))],
355 358 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'))
356 359 def demo(ui, repo, *args, **opts):
357 360 '''print [keywordmaps] configuration and an expansion example
358 361
359 362 Show current, custom, or default keyword template maps and their
360 363 expansions.
361 364
362 365 Extend the current configuration by specifying maps as arguments
363 366 and using -f/--rcfile to source an external hgrc file.
364 367
365 368 Use -d/--default to disable current configuration.
366 369
367 370 See :hg:`help templates` for information on templates and filters.
368 371 '''
369 372 def demoitems(section, items):
370 373 ui.write('[%s]\n' % section)
371 374 for k, v in sorted(items):
372 375 ui.write('%s = %s\n' % (k, v))
373 376
374 377 fn = 'demo.txt'
375 378 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
376 379 ui.note(_('creating temporary repository at %s\n') % tmpdir)
377 380 repo = localrepo.localrepository(ui, tmpdir, True)
378 381 ui.setconfig('keyword', fn, '')
379 382 svn = ui.configbool('keywordset', 'svn')
380 383 # explicitly set keywordset for demo output
381 384 ui.setconfig('keywordset', 'svn', svn)
382 385
383 386 uikwmaps = ui.configitems('keywordmaps')
384 387 if args or opts.get('rcfile'):
385 388 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
386 389 if uikwmaps:
387 390 ui.status(_('\textending current template maps\n'))
388 391 if opts.get('default') or not uikwmaps:
389 392 if svn:
390 393 ui.status(_('\toverriding default svn keywordset\n'))
391 394 else:
392 395 ui.status(_('\toverriding default cvs keywordset\n'))
393 396 if opts.get('rcfile'):
394 397 ui.readconfig(opts.get('rcfile'))
395 398 if args:
396 399 # simulate hgrc parsing
397 400 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
398 401 fp = repo.opener('hgrc', 'w')
399 402 fp.writelines(rcmaps)
400 403 fp.close()
401 404 ui.readconfig(repo.join('hgrc'))
402 405 kwmaps = dict(ui.configitems('keywordmaps'))
403 406 elif opts.get('default'):
404 407 if svn:
405 408 ui.status(_('\n\tconfiguration using default svn keywordset\n'))
406 409 else:
407 410 ui.status(_('\n\tconfiguration using default cvs keywordset\n'))
408 411 kwmaps = _defaultkwmaps(ui)
409 412 if uikwmaps:
410 413 ui.status(_('\tdisabling current template maps\n'))
411 414 for k, v in kwmaps.iteritems():
412 415 ui.setconfig('keywordmaps', k, v)
413 416 else:
414 417 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
415 418 kwmaps = dict(uikwmaps) or _defaultkwmaps(ui)
416 419
417 420 uisetup(ui)
418 421 reposetup(ui, repo)
419 422 ui.write('[extensions]\nkeyword =\n')
420 423 demoitems('keyword', ui.configitems('keyword'))
421 424 demoitems('keywordset', ui.configitems('keywordset'))
422 425 demoitems('keywordmaps', kwmaps.iteritems())
423 426 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
424 427 repo.wopener.write(fn, keywords)
425 428 repo[None].add([fn])
426 429 ui.note(_('\nkeywords written to %s:\n') % fn)
427 430 ui.note(keywords)
428 431 repo.dirstate.setbranch('demobranch')
429 432 for name, cmd in ui.configitems('hooks'):
430 433 if name.split('.', 1)[0].find('commit') > -1:
431 434 repo.ui.setconfig('hooks', name, '')
432 435 msg = _('hg keyword configuration and expansion example')
433 436 ui.note("hg ci -m '%s'\n" % msg)
434 437 repo.commit(text=msg)
435 438 ui.status(_('\n\tkeywords expanded\n'))
436 439 ui.write(repo.wread(fn))
437 440 shutil.rmtree(tmpdir, ignore_errors=True)
438 441
439 442 @command('kwexpand', commands.walkopts, _('hg kwexpand [OPTION]... [FILE]...'))
440 443 def expand(ui, repo, *pats, **opts):
441 444 '''expand keywords in the working directory
442 445
443 446 Run after (re)enabling keyword expansion.
444 447
445 448 kwexpand refuses to run if given files contain local changes.
446 449 '''
447 450 # 3rd argument sets expansion to True
448 451 _kwfwrite(ui, repo, True, *pats, **opts)
449 452
450 453 @command('kwfiles',
451 454 [('A', 'all', None, _('show keyword status flags of all files')),
452 455 ('i', 'ignore', None, _('show files excluded from expansion')),
453 456 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
454 457 ] + commands.walkopts,
455 458 _('hg kwfiles [OPTION]... [FILE]...'))
456 459 def files(ui, repo, *pats, **opts):
457 460 '''show files configured for keyword expansion
458 461
459 462 List which files in the working directory are matched by the
460 463 [keyword] configuration patterns.
461 464
462 465 Useful to prevent inadvertent keyword expansion and to speed up
463 466 execution by including only files that are actual candidates for
464 467 expansion.
465 468
466 469 See :hg:`help keyword` on how to construct patterns both for
467 470 inclusion and exclusion of files.
468 471
469 472 With -A/--all and -v/--verbose the codes used to show the status
470 473 of files are::
471 474
472 475 K = keyword expansion candidate
473 476 k = keyword expansion candidate (not tracked)
474 477 I = ignored
475 478 i = ignored (not tracked)
476 479 '''
477 480 kwt = kwtools['templater']
478 481 status = _status(ui, repo, kwt, *pats, **opts)
479 482 cwd = pats and repo.getcwd() or ''
480 483 modified, added, removed, deleted, unknown, ignored, clean = status
481 484 files = []
482 485 if not opts.get('unknown') or opts.get('all'):
483 486 files = sorted(modified + added + clean)
484 487 wctx = repo[None]
485 488 kwfiles = kwt.iskwfile(files, wctx)
486 489 kwdeleted = kwt.iskwfile(deleted, wctx)
487 490 kwunknown = kwt.iskwfile(unknown, wctx)
488 491 if not opts.get('ignore') or opts.get('all'):
489 492 showfiles = kwfiles, kwdeleted, kwunknown
490 493 else:
491 494 showfiles = [], [], []
492 495 if opts.get('all') or opts.get('ignore'):
493 496 showfiles += ([f for f in files if f not in kwfiles],
494 497 [f for f in unknown if f not in kwunknown])
495 498 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
496 499 kwstates = zip('K!kIi', showfiles, kwlabels)
497 500 for char, filenames, kwstate in kwstates:
498 501 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
499 502 for f in filenames:
500 503 ui.write(fmt % repo.pathto(f, cwd), label='kwfiles.' + kwstate)
501 504
502 505 @command('kwshrink', commands.walkopts, _('hg kwshrink [OPTION]... [FILE]...'))
503 506 def shrink(ui, repo, *pats, **opts):
504 507 '''revert expanded keywords in the working directory
505 508
506 509 Must be run before changing/disabling active keywords.
507 510
508 511 kwshrink refuses to run if given files contain local changes.
509 512 '''
510 513 # 3rd argument sets expansion to False
511 514 _kwfwrite(ui, repo, False, *pats, **opts)
512 515
513 516
514 517 def uisetup(ui):
515 518 ''' Monkeypatches dispatch._parse to retrieve user command.'''
516 519
517 520 def kwdispatch_parse(orig, ui, args):
518 521 '''Monkeypatch dispatch._parse to obtain running hg command.'''
519 522 cmd, func, args, options, cmdoptions = orig(ui, args)
520 523 kwtools['hgcmd'] = cmd
521 524 return cmd, func, args, options, cmdoptions
522 525
523 526 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
524 527
525 528 def reposetup(ui, repo):
526 529 '''Sets up repo as kwrepo for keyword substitution.
527 530 Overrides file method to return kwfilelog instead of filelog
528 531 if file matches user configuration.
529 532 Wraps commit to overwrite configured files with updated
530 533 keyword substitutions.
531 534 Monkeypatches patch and webcommands.'''
532 535
533 536 try:
534 537 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
535 538 or '.hg' in util.splitpath(repo.root)
536 539 or repo._url.startswith('bundle:')):
537 540 return
538 541 except AttributeError:
539 542 pass
540 543
541 544 inc, exc = [], ['.hg*']
542 545 for pat, opt in ui.configitems('keyword'):
543 546 if opt != 'ignore':
544 547 inc.append(pat)
545 548 else:
546 549 exc.append(pat)
547 550 if not inc:
548 551 return
549 552
550 553 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
551 554
552 555 class kwrepo(repo.__class__):
553 556 def file(self, f):
554 557 if f[0] == '/':
555 558 f = f[1:]
556 559 return kwfilelog(self.sopener, kwt, f)
557 560
558 561 def wread(self, filename):
559 562 data = super(kwrepo, self).wread(filename)
560 563 return kwt.wread(filename, data)
561 564
562 565 def commit(self, *args, **opts):
563 566 # use custom commitctx for user commands
564 567 # other extensions can still wrap repo.commitctx directly
565 568 self.commitctx = self.kwcommitctx
566 569 try:
567 570 return super(kwrepo, self).commit(*args, **opts)
568 571 finally:
569 572 del self.commitctx
570 573
571 574 def kwcommitctx(self, ctx, error=False):
572 575 n = super(kwrepo, self).commitctx(ctx, error)
573 576 # no lock needed, only called from repo.commit() which already locks
574 577 if not kwt.record:
575 578 restrict = kwt.restrict
576 579 kwt.restrict = True
577 580 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
578 581 False, True)
579 582 kwt.restrict = restrict
580 583 return n
581 584
582 585 def rollback(self, dryrun=False):
583 586 wlock = self.wlock()
584 587 try:
585 588 if not dryrun:
586 589 changed = self['.'].files()
587 590 ret = super(kwrepo, self).rollback(dryrun)
588 591 if not dryrun:
589 592 ctx = self['.']
590 593 modified, added = _preselect(self[None].status(), changed)
591 594 kwt.overwrite(ctx, modified, True, True)
592 595 kwt.overwrite(ctx, added, True, False)
593 596 return ret
594 597 finally:
595 598 wlock.release()
596 599
597 600 # monkeypatches
598 601 def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None):
599 602 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
600 603 rejects or conflicts due to expanded keywords in working dir.'''
601 604 orig(self, ui, gp, backend, store, eolmode)
602 605 # shrink keywords read from working dir
603 606 self.lines = kwt.shrinklines(self.fname, self.lines)
604 607
605 608 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
606 609 opts=None, prefix=''):
607 610 '''Monkeypatch patch.diff to avoid expansion.'''
608 611 kwt.restrict = True
609 612 return orig(repo, node1, node2, match, changes, opts, prefix)
610 613
611 614 def kwweb_skip(orig, web, req, tmpl):
612 615 '''Wraps webcommands.x turning off keyword expansion.'''
613 616 kwt.match = util.never
614 617 return orig(web, req, tmpl)
615 618
616 619 def kw_copy(orig, ui, repo, pats, opts, rename=False):
617 620 '''Wraps cmdutil.copy so that copy/rename destinations do not
618 621 contain expanded keywords.
619 622 Note that the source of a regular file destination may also be a
620 623 symlink:
621 624 hg cp sym x -> x is symlink
622 625 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
623 626 For the latter we have to follow the symlink to find out whether its
624 627 target is configured for expansion and we therefore must unexpand the
625 628 keywords in the destination.'''
626 629 orig(ui, repo, pats, opts, rename)
627 630 if opts.get('dry_run'):
628 631 return
629 632 wctx = repo[None]
630 633 cwd = repo.getcwd()
631 634
632 635 def haskwsource(dest):
633 636 '''Returns true if dest is a regular file and configured for
634 637 expansion or a symlink which points to a file configured for
635 638 expansion. '''
636 639 source = repo.dirstate.copied(dest)
637 640 if 'l' in wctx.flags(source):
638 641 source = scmutil.canonpath(repo.root, cwd,
639 642 os.path.realpath(source))
640 643 return kwt.match(source)
641 644
642 645 candidates = [f for f in repo.dirstate.copies() if
643 646 not 'l' in wctx.flags(f) and haskwsource(f)]
644 647 kwt.overwrite(wctx, candidates, False, False)
645 648
646 649 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
647 650 '''Wraps record.dorecord expanding keywords after recording.'''
648 651 wlock = repo.wlock()
649 652 try:
650 653 # record returns 0 even when nothing has changed
651 654 # therefore compare nodes before and after
652 655 kwt.record = True
653 656 ctx = repo['.']
654 657 wstatus = repo[None].status()
655 658 ret = orig(ui, repo, commitfunc, *pats, **opts)
656 659 recctx = repo['.']
657 660 if ctx != recctx:
658 661 modified, added = _preselect(wstatus, recctx.files())
659 662 kwt.restrict = False
660 663 kwt.overwrite(recctx, modified, False, True)
661 664 kwt.overwrite(recctx, added, False, True, True)
662 665 kwt.restrict = True
663 666 return ret
664 667 finally:
665 668 wlock.release()
666 669
667 670 def kwfilectx_cmp(orig, self, fctx):
668 671 # keyword affects data size, comparing wdir and filelog size does
669 672 # not make sense
670 673 if (fctx._filerev is None and
671 674 (self._repo._encodefilterpats or
672 675 kwt.match(fctx.path()) and not 'l' in fctx.flags()) or
673 676 self.size() == fctx.size()):
674 677 return self._filelog.cmp(self._filenode, fctx.data())
675 678 return True
676 679
677 680 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
678 681 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
679 682 extensions.wrapfunction(patch, 'diff', kw_diff)
680 683 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
681 684 for c in 'annotate changeset rev filediff diff'.split():
682 685 extensions.wrapfunction(webcommands, c, kwweb_skip)
683 686 for name in recordextensions.split():
684 687 try:
685 688 record = extensions.find(name)
686 689 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
687 690 except KeyError:
688 691 pass
689 692
690 693 repo.__class__ = kwrepo
@@ -1,1074 +1,1079 b''
1 1 $ cat <<EOF >> $HGRCPATH
2 2 > [extensions]
3 3 > keyword =
4 4 > mq =
5 5 > notify =
6 6 > record =
7 7 > transplant =
8 8 > [ui]
9 9 > interactive = true
10 10 > EOF
11 11
12 12 Run kwdemo before [keyword] files are set up
13 13 as it would succeed without uisetup otherwise
14 14
15 15 $ hg --quiet kwdemo
16 16 [extensions]
17 17 keyword =
18 18 [keyword]
19 19 demo.txt =
20 20 [keywordset]
21 21 svn = False
22 22 [keywordmaps]
23 23 Author = {author|user}
24 24 Date = {date|utcdate}
25 25 Header = {root}/{file},v {node|short} {date|utcdate} {author|user}
26 26 Id = {file|basename},v {node|short} {date|utcdate} {author|user}
27 27 RCSFile = {file|basename},v
28 28 RCSfile = {file|basename},v
29 29 Revision = {node|short}
30 30 Source = {root}/{file},v
31 31 $Author: test $
32 32 $Date: ????/??/?? ??:??:?? $ (glob)
33 33 $Header: */demo.txt,v ???????????? ????/??/?? ??:??:?? test $ (glob)
34 34 $Id: demo.txt,v ???????????? ????/??/?? ??:??:?? test $ (glob)
35 35 $RCSFile: demo.txt,v $
36 36 $RCSfile: demo.txt,v $
37 37 $Revision: ???????????? $ (glob)
38 38 $Source: */demo.txt,v $ (glob)
39 39
40 40 $ hg --quiet kwdemo "Branch = {branches}"
41 41 [extensions]
42 42 keyword =
43 43 [keyword]
44 44 demo.txt =
45 45 [keywordset]
46 46 svn = False
47 47 [keywordmaps]
48 48 Branch = {branches}
49 49 $Branch: demobranch $
50 50
51 51 $ cat <<EOF >> $HGRCPATH
52 52 > [keyword]
53 53 > ** =
54 54 > b = ignore
55 55 > i = ignore
56 56 > [hooks]
57 57 > EOF
58 58 $ cp $HGRCPATH $HGRCPATH.nohooks
59 59 > cat <<EOF >> $HGRCPATH
60 60 > commit=
61 61 > commit.test=cp a hooktest
62 62 > EOF
63 63
64 64 $ hg init Test-bndl
65 65 $ cd Test-bndl
66 66
67 67 kwshrink should exit silently in empty/invalid repo
68 68
69 69 $ hg kwshrink
70 70
71 71 Symlinks cannot be created on Windows.
72 72 A bundle to test this was made with:
73 73 hg init t
74 74 cd t
75 75 echo a > a
76 76 ln -s a sym
77 77 hg add sym
78 78 hg ci -m addsym -u mercurial
79 79 hg bundle --base null ../test-keyword.hg
80 80
81 81 $ hg pull -u "$TESTDIR"/bundles/test-keyword.hg
82 82 pulling from *test-keyword.hg (glob)
83 83 requesting all changes
84 84 adding changesets
85 85 adding manifests
86 86 adding file changes
87 87 added 1 changesets with 1 changes to 1 files
88 88 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
89 89
90 90 $ echo 'expand $Id$' > a
91 91 $ echo 'do not process $Id:' >> a
92 92 $ echo 'xxx $' >> a
93 93 $ echo 'ignore $Id$' > b
94 94
95 95 Output files as they were created
96 96
97 97 $ cat a b
98 98 expand $Id$
99 99 do not process $Id:
100 100 xxx $
101 101 ignore $Id$
102 102
103 103 no kwfiles
104 104
105 105 $ hg kwfiles
106 106
107 107 untracked candidates
108 108
109 109 $ hg -v kwfiles --unknown
110 110 k a
111 111
112 112 Add files and check status
113 113
114 114 $ hg addremove
115 115 adding a
116 116 adding b
117 117 $ hg status
118 118 A a
119 119 A b
120 120
121 121
122 122 Default keyword expansion including commit hook
123 123 Interrupted commit should not change state or run commit hook
124 124
125 125 $ hg --debug commit
126 126 abort: empty commit message
127 127 [255]
128 128 $ hg status
129 129 A a
130 130 A b
131 131
132 132 Commit with several checks
133 133
134 134 $ hg --debug commit -mabsym -u 'User Name <user@example.com>'
135 135 a
136 136 b
137 137 overwriting a expanding keywords
138 138 running hook commit.test: cp a hooktest
139 139 committed changeset 1:ef63ca68695bc9495032c6fda1350c71e6d256e9
140 140 $ hg status
141 141 ? hooktest
142 142 $ hg debugrebuildstate
143 143 $ hg --quiet identify
144 144 ef63ca68695b
145 145
146 146 cat files in working directory with keywords expanded
147 147
148 148 $ cat a b
149 149 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
150 150 do not process $Id:
151 151 xxx $
152 152 ignore $Id$
153 153
154 154 hg cat files and symlink, no expansion
155 155
156 156 $ hg cat sym a b && echo
157 157 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
158 158 do not process $Id:
159 159 xxx $
160 160 ignore $Id$
161 161 a
162 162
163 163 Test hook execution
164 164
165 165 $ diff a hooktest
166 166
167 167 $ cp $HGRCPATH.nohooks $HGRCPATH
168 168 $ rm hooktest
169 169
170 170 bundle
171 171
172 172 $ hg bundle --base null ../kw.hg
173 173 2 changesets found
174 174 $ cd ..
175 175 $ hg init Test
176 176 $ cd Test
177 177
178 178 Notify on pull to check whether keywords stay as is in email
179 179 ie. if patch.diff wrapper acts as it should
180 180
181 181 $ cat <<EOF >> $HGRCPATH
182 182 > [hooks]
183 183 > incoming.notify = python:hgext.notify.hook
184 184 > [notify]
185 185 > sources = pull
186 186 > diffstat = False
187 187 > maxsubject = 15
188 188 > [reposubs]
189 189 > * = Test
190 190 > EOF
191 191
192 192 Pull from bundle and trigger notify
193 193
194 194 $ hg pull -u ../kw.hg
195 195 pulling from ../kw.hg
196 196 requesting all changes
197 197 adding changesets
198 198 adding manifests
199 199 adding file changes
200 200 added 2 changesets with 3 changes to 3 files
201 201 Content-Type: text/plain; charset="us-ascii"
202 202 MIME-Version: 1.0
203 203 Content-Transfer-Encoding: 7bit
204 204 Date: * (glob)
205 205 Subject: changeset in...
206 206 From: mercurial
207 207 X-Hg-Notification: changeset a2392c293916
208 208 Message-Id: <hg.a2392c293916*> (glob)
209 209 To: Test
210 210
211 211 changeset a2392c293916 in $TESTTMP/Test
212 212 details: $TESTTMP/Test?cmd=changeset;node=a2392c293916
213 213 description:
214 214 addsym
215 215
216 216 diffs (6 lines):
217 217
218 218 diff -r 000000000000 -r a2392c293916 sym
219 219 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
220 220 +++ b/sym Sat Feb 09 20:25:47 2008 +0100
221 221 @@ -0,0 +1,1 @@
222 222 +a
223 223 \ No newline at end of file
224 224 Content-Type: text/plain; charset="us-ascii"
225 225 MIME-Version: 1.0
226 226 Content-Transfer-Encoding: 7bit
227 227 Date:* (glob)
228 228 Subject: changeset in...
229 229 From: User Name <user@example.com>
230 230 X-Hg-Notification: changeset ef63ca68695b
231 231 Message-Id: <hg.ef63ca68695b*> (glob)
232 232 To: Test
233 233
234 234 changeset ef63ca68695b in $TESTTMP/Test
235 235 details: $TESTTMP/Test?cmd=changeset;node=ef63ca68695b
236 236 description:
237 237 absym
238 238
239 239 diffs (12 lines):
240 240
241 241 diff -r a2392c293916 -r ef63ca68695b a
242 242 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
243 243 +++ b/a Thu Jan 01 00:00:00 1970 +0000
244 244 @@ -0,0 +1,3 @@
245 245 +expand $Id$
246 246 +do not process $Id:
247 247 +xxx $
248 248 diff -r a2392c293916 -r ef63ca68695b b
249 249 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
250 250 +++ b/b Thu Jan 01 00:00:00 1970 +0000
251 251 @@ -0,0 +1,1 @@
252 252 +ignore $Id$
253 253 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
254 254
255 255 $ cp $HGRCPATH.nohooks $HGRCPATH
256 256
257 257 Touch files and check with status
258 258
259 259 $ touch a b
260 260 $ hg status
261 261
262 262 Update and expand
263 263
264 264 $ rm sym a b
265 265 $ hg update -C
266 266 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
267 267 $ cat a b
268 268 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
269 269 do not process $Id:
270 270 xxx $
271 271 ignore $Id$
272 272
273 Check whether expansion is filewise
273 Check whether expansion is filewise and file mode is preserved
274 274
275 275 $ echo '$Id$' > c
276 276 $ echo 'tests for different changenodes' >> c
277 $ chmod 600 c
278 $ ls -l c | cut -b 1-10
279 -rw-------
277 280
278 281 commit file c
279 282
280 283 $ hg commit -A -mcndiff -d '1 0' -u 'User Name <user@example.com>'
281 284 adding c
285 $ ls -l c | cut -b 1-10
286 -rw-------
282 287
283 288 force expansion
284 289
285 290 $ hg -v kwexpand
286 291 overwriting a expanding keywords
287 292 overwriting c expanding keywords
288 293
289 294 compare changenodes in a and c
290 295
291 296 $ cat a c
292 297 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
293 298 do not process $Id:
294 299 xxx $
295 300 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
296 301 tests for different changenodes
297 302
298 303 record
299 304
300 305 $ echo '$Id$' > r
301 306 $ hg add r
302 307
303 308 record chunk
304 309
305 310 $ python -c \
306 311 > 'l=open("a").readlines();l.insert(1,"foo\n");l.append("bar\n");open("a","w").writelines(l);'
307 312 $ hg record -d '1 10' -m rectest a<<EOF
308 313 > y
309 314 > y
310 315 > n
311 316 > EOF
312 317 diff --git a/a b/a
313 318 2 hunks, 2 lines changed
314 319 examine changes to 'a'? [Ynsfdaq?]
315 320 @@ -1,3 +1,4 @@
316 321 expand $Id$
317 322 +foo
318 323 do not process $Id:
319 324 xxx $
320 325 record change 1/2 to 'a'? [Ynsfdaq?]
321 326 @@ -2,2 +3,3 @@
322 327 do not process $Id:
323 328 xxx $
324 329 +bar
325 330 record change 2/2 to 'a'? [Ynsfdaq?]
326 331
327 332 $ hg identify
328 333 d17e03c92c97+ tip
329 334 $ hg status
330 335 M a
331 336 A r
332 337
333 338 Cat modified file a
334 339
335 340 $ cat a
336 341 expand $Id: a,v d17e03c92c97 1970/01/01 00:00:01 test $
337 342 foo
338 343 do not process $Id:
339 344 xxx $
340 345 bar
341 346
342 347 Diff remaining chunk
343 348
344 349 $ hg diff a
345 350 diff -r d17e03c92c97 a
346 351 --- a/a Wed Dec 31 23:59:51 1969 -0000
347 352 +++ b/a * (glob)
348 353 @@ -2,3 +2,4 @@
349 354 foo
350 355 do not process $Id:
351 356 xxx $
352 357 +bar
353 358
354 359 $ hg rollback
355 360 repository tip rolled back to revision 2 (undo commit)
356 361 working directory now based on revision 2
357 362
358 363 Record all chunks in file a
359 364
360 365 $ echo foo > msg
361 366
362 367 - do not use "hg record -m" here!
363 368
364 369 $ hg record -l msg -d '1 11' a<<EOF
365 370 > y
366 371 > y
367 372 > y
368 373 > EOF
369 374 diff --git a/a b/a
370 375 2 hunks, 2 lines changed
371 376 examine changes to 'a'? [Ynsfdaq?]
372 377 @@ -1,3 +1,4 @@
373 378 expand $Id$
374 379 +foo
375 380 do not process $Id:
376 381 xxx $
377 382 record change 1/2 to 'a'? [Ynsfdaq?]
378 383 @@ -2,2 +3,3 @@
379 384 do not process $Id:
380 385 xxx $
381 386 +bar
382 387 record change 2/2 to 'a'? [Ynsfdaq?]
383 388
384 389 File a should be clean
385 390
386 391 $ hg status -A a
387 392 C a
388 393
389 394 rollback and revert expansion
390 395
391 396 $ cat a
392 397 expand $Id: a,v 59f969a3b52c 1970/01/01 00:00:01 test $
393 398 foo
394 399 do not process $Id:
395 400 xxx $
396 401 bar
397 402 $ hg --verbose rollback
398 403 repository tip rolled back to revision 2 (undo commit)
399 404 working directory now based on revision 2
400 405 overwriting a expanding keywords
401 406 $ hg status a
402 407 M a
403 408 $ cat a
404 409 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
405 410 foo
406 411 do not process $Id:
407 412 xxx $
408 413 bar
409 414 $ echo '$Id$' > y
410 415 $ echo '$Id$' > z
411 416 $ hg add y
412 417 $ hg commit -Am "rollback only" z
413 418 $ cat z
414 419 $Id: z,v 45a5d3adce53 1970/01/01 00:00:00 test $
415 420 $ hg --verbose rollback
416 421 repository tip rolled back to revision 2 (undo commit)
417 422 working directory now based on revision 2
418 423 overwriting z shrinking keywords
419 424
420 425 Only z should be overwritten
421 426
422 427 $ hg status a y z
423 428 M a
424 429 A y
425 430 A z
426 431 $ cat z
427 432 $Id$
428 433 $ hg forget y z
429 434 $ rm y z
430 435
431 436 record added file alone
432 437
433 438 $ hg -v record -l msg -d '1 12' r<<EOF
434 439 > y
435 440 > EOF
436 441 diff --git a/r b/r
437 442 new file mode 100644
438 443 examine changes to 'r'? [Ynsfdaq?]
439 444 r
440 445 committed changeset 3:899491280810
441 446 overwriting r expanding keywords
442 447 $ hg --verbose rollback
443 448 repository tip rolled back to revision 2 (undo commit)
444 449 working directory now based on revision 2
445 450 overwriting r shrinking keywords
446 451 $ hg forget r
447 452 $ rm msg r
448 453 $ hg update -C
449 454 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
450 455
451 456 record added keyword ignored file
452 457
453 458 $ echo '$Id$' > i
454 459 $ hg add i
455 460 $ hg --verbose record -d '1 13' -m recignored<<EOF
456 461 > y
457 462 > EOF
458 463 diff --git a/i b/i
459 464 new file mode 100644
460 465 examine changes to 'i'? [Ynsfdaq?]
461 466 i
462 467 committed changeset 3:5f40fe93bbdc
463 468 $ cat i
464 469 $Id$
465 470 $ hg -q rollback
466 471 $ hg forget i
467 472 $ rm i
468 473
469 474 Test patch queue repo
470 475
471 476 $ hg init --mq
472 477 $ hg qimport -r tip -n mqtest.diff
473 478 $ hg commit --mq -m mqtest
474 479
475 480 Keywords should not be expanded in patch
476 481
477 482 $ cat .hg/patches/mqtest.diff
478 483 # HG changeset patch
479 484 # User User Name <user@example.com>
480 485 # Date 1 0
481 486 # Node ID 40a904bbbe4cd4ab0a1f28411e35db26341a40ad
482 487 # Parent ef63ca68695bc9495032c6fda1350c71e6d256e9
483 488 cndiff
484 489
485 490 diff -r ef63ca68695b -r 40a904bbbe4c c
486 491 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
487 492 +++ b/c Thu Jan 01 00:00:01 1970 +0000
488 493 @@ -0,0 +1,2 @@
489 494 +$Id$
490 495 +tests for different changenodes
491 496
492 497 $ hg qpop
493 498 popping mqtest.diff
494 499 patch queue now empty
495 500
496 501 qgoto, implying qpush, should expand
497 502
498 503 $ hg qgoto mqtest.diff
499 504 applying mqtest.diff
500 505 now at: mqtest.diff
501 506 $ cat c
502 507 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
503 508 tests for different changenodes
504 509 $ hg cat c
505 510 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
506 511 tests for different changenodes
507 512
508 513 Keywords should not be expanded in filelog
509 514
510 515 $ hg --config 'extensions.keyword=!' cat c
511 516 $Id$
512 517 tests for different changenodes
513 518
514 519 qpop and move on
515 520
516 521 $ hg qpop
517 522 popping mqtest.diff
518 523 patch queue now empty
519 524
520 525 Copy and show added kwfiles
521 526
522 527 $ hg cp a c
523 528 $ hg kwfiles
524 529 a
525 530 c
526 531
527 532 Commit and show expansion in original and copy
528 533
529 534 $ hg --debug commit -ma2c -d '1 0' -u 'User Name <user@example.com>'
530 535 c
531 536 c: copy a:0045e12f6c5791aac80ca6cbfd97709a88307292
532 537 overwriting c expanding keywords
533 538 committed changeset 2:25736cf2f5cbe41f6be4e6784ef6ecf9f3bbcc7d
534 539 $ cat a c
535 540 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
536 541 do not process $Id:
537 542 xxx $
538 543 expand $Id: c,v 25736cf2f5cb 1970/01/01 00:00:01 user $
539 544 do not process $Id:
540 545 xxx $
541 546
542 547 Touch copied c and check its status
543 548
544 549 $ touch c
545 550 $ hg status
546 551
547 552 Copy kwfile to keyword ignored file unexpanding keywords
548 553
549 554 $ hg --verbose copy a i
550 555 copying a to i
551 556 overwriting i shrinking keywords
552 557 $ head -n 1 i
553 558 expand $Id$
554 559 $ hg forget i
555 560 $ rm i
556 561
557 562 Copy ignored file to ignored file: no overwriting
558 563
559 564 $ hg --verbose copy b i
560 565 copying b to i
561 566 $ hg forget i
562 567 $ rm i
563 568
564 569 cp symlink file; hg cp -A symlink file (part1)
565 570 - copied symlink points to kwfile: overwrite
566 571
567 572 $ cp sym i
568 573 $ ls -l i
569 574 -rw-r--r--* (glob)
570 575 $ head -1 i
571 576 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
572 577 $ hg copy --after --verbose sym i
573 578 copying sym to i
574 579 overwriting i shrinking keywords
575 580 $ head -1 i
576 581 expand $Id$
577 582 $ hg forget i
578 583 $ rm i
579 584
580 585 Test different options of hg kwfiles
581 586
582 587 $ hg kwfiles
583 588 a
584 589 c
585 590 $ hg -v kwfiles --ignore
586 591 I b
587 592 I sym
588 593 $ hg kwfiles --all
589 594 K a
590 595 K c
591 596 I b
592 597 I sym
593 598
594 599 Diff specific revision
595 600
596 601 $ hg diff --rev 1
597 602 diff -r ef63ca68695b c
598 603 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
599 604 +++ b/c * (glob)
600 605 @@ -0,0 +1,3 @@
601 606 +expand $Id$
602 607 +do not process $Id:
603 608 +xxx $
604 609
605 610 Status after rollback:
606 611
607 612 $ hg rollback
608 613 repository tip rolled back to revision 1 (undo commit)
609 614 working directory now based on revision 1
610 615 $ hg status
611 616 A c
612 617 $ hg update --clean
613 618 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
614 619
615 620 cp symlink file; hg cp -A symlink file (part2)
616 621 - copied symlink points to kw ignored file: do not overwrite
617 622
618 623 $ cat a > i
619 624 $ ln -s i symignored
620 625 $ hg commit -Am 'fake expansion in ignored and symlink' i symignored
621 626 $ cp symignored x
622 627 $ hg copy --after --verbose symignored x
623 628 copying symignored to x
624 629 $ head -n 1 x
625 630 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
626 631 $ hg forget x
627 632 $ rm x
628 633
629 634 $ hg rollback
630 635 repository tip rolled back to revision 1 (undo commit)
631 636 working directory now based on revision 1
632 637 $ hg update --clean
633 638 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
634 639 $ rm i symignored
635 640
636 641 Custom keywordmaps as argument to kwdemo
637 642
638 643 $ hg --quiet kwdemo "Xinfo = {author}: {desc}"
639 644 [extensions]
640 645 keyword =
641 646 [keyword]
642 647 ** =
643 648 b = ignore
644 649 demo.txt =
645 650 i = ignore
646 651 [keywordset]
647 652 svn = False
648 653 [keywordmaps]
649 654 Xinfo = {author}: {desc}
650 655 $Xinfo: test: hg keyword configuration and expansion example $
651 656
652 657 Configure custom keywordmaps
653 658
654 659 $ cat <<EOF >>$HGRCPATH
655 660 > [keywordmaps]
656 661 > Id = {file} {node|short} {date|rfc822date} {author|user}
657 662 > Xinfo = {author}: {desc}
658 663 > EOF
659 664
660 665 Cat and hg cat files before custom expansion
661 666
662 667 $ cat a b
663 668 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
664 669 do not process $Id:
665 670 xxx $
666 671 ignore $Id$
667 672 $ hg cat sym a b && echo
668 673 expand $Id: a ef63ca68695b Thu, 01 Jan 1970 00:00:00 +0000 user $
669 674 do not process $Id:
670 675 xxx $
671 676 ignore $Id$
672 677 a
673 678
674 679 Write custom keyword and prepare multiline commit message
675 680
676 681 $ echo '$Xinfo$' >> a
677 682 $ cat <<EOF >> log
678 683 > firstline
679 684 > secondline
680 685 > EOF
681 686
682 687 Interrupted commit should not change state
683 688
684 689 $ hg commit
685 690 abort: empty commit message
686 691 [255]
687 692 $ hg status
688 693 M a
689 694 ? c
690 695 ? log
691 696
692 697 Commit with multiline message and custom expansion
693 698
694 699 $ hg --debug commit -l log -d '2 0' -u 'User Name <user@example.com>'
695 700 a
696 701 overwriting a expanding keywords
697 702 committed changeset 2:bb948857c743469b22bbf51f7ec8112279ca5d83
698 703 $ rm log
699 704
700 705 Stat, verify and show custom expansion (firstline)
701 706
702 707 $ hg status
703 708 ? c
704 709 $ hg verify
705 710 checking changesets
706 711 checking manifests
707 712 crosschecking files in changesets and manifests
708 713 checking files
709 714 3 files, 3 changesets, 4 total revisions
710 715 $ cat a b
711 716 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
712 717 do not process $Id:
713 718 xxx $
714 719 $Xinfo: User Name <user@example.com>: firstline $
715 720 ignore $Id$
716 721 $ hg cat sym a b && echo
717 722 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
718 723 do not process $Id:
719 724 xxx $
720 725 $Xinfo: User Name <user@example.com>: firstline $
721 726 ignore $Id$
722 727 a
723 728
724 729 annotate
725 730
726 731 $ hg annotate a
727 732 1: expand $Id$
728 733 1: do not process $Id:
729 734 1: xxx $
730 735 2: $Xinfo$
731 736
732 737 remove with status checks
733 738
734 739 $ hg debugrebuildstate
735 740 $ hg remove a
736 741 $ hg --debug commit -m rma
737 742 committed changeset 3:d14c712653769de926994cf7fbb06c8fbd68f012
738 743 $ hg status
739 744 ? c
740 745
741 746 Rollback, revert, and check expansion
742 747
743 748 $ hg rollback
744 749 repository tip rolled back to revision 2 (undo commit)
745 750 working directory now based on revision 2
746 751 $ hg status
747 752 R a
748 753 ? c
749 754 $ hg revert --no-backup --rev tip a
750 755 $ cat a
751 756 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
752 757 do not process $Id:
753 758 xxx $
754 759 $Xinfo: User Name <user@example.com>: firstline $
755 760
756 761 Clone to test global and local configurations
757 762
758 763 $ cd ..
759 764
760 765 Expansion in destinaton with global configuration
761 766
762 767 $ hg --quiet clone Test globalconf
763 768 $ cat globalconf/a
764 769 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
765 770 do not process $Id:
766 771 xxx $
767 772 $Xinfo: User Name <user@example.com>: firstline $
768 773
769 774 No expansion in destination with local configuration in origin only
770 775
771 776 $ hg --quiet --config 'keyword.**=ignore' clone Test localconf
772 777 $ cat localconf/a
773 778 expand $Id$
774 779 do not process $Id:
775 780 xxx $
776 781 $Xinfo$
777 782
778 783 Clone to test incoming
779 784
780 785 $ hg clone -r1 Test Test-a
781 786 adding changesets
782 787 adding manifests
783 788 adding file changes
784 789 added 2 changesets with 3 changes to 3 files
785 790 updating to branch default
786 791 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
787 792 $ cd Test-a
788 793 $ cat <<EOF >> .hg/hgrc
789 794 > [paths]
790 795 > default = ../Test
791 796 > EOF
792 797 $ hg incoming
793 798 comparing with $TESTTMP/Test
794 799 searching for changes
795 800 changeset: 2:bb948857c743
796 801 tag: tip
797 802 user: User Name <user@example.com>
798 803 date: Thu Jan 01 00:00:02 1970 +0000
799 804 summary: firstline
800 805
801 806 Imported patch should not be rejected
802 807
803 808 $ python -c \
804 809 > 'import re; s=re.sub("(Id.*)","\\1 rejecttest",open("a").read()); open("a","wb").write(s);'
805 810 $ hg --debug commit -m'rejects?' -d '3 0' -u 'User Name <user@example.com>'
806 811 a
807 812 overwriting a expanding keywords
808 813 committed changeset 2:85e279d709ffc28c9fdd1b868570985fc3d87082
809 814 $ hg export -o ../rejecttest.diff tip
810 815 $ cd ../Test
811 816 $ hg import ../rejecttest.diff
812 817 applying ../rejecttest.diff
813 818 $ cat a b
814 819 expand $Id: a 4e0994474d25 Thu, 01 Jan 1970 00:00:03 +0000 user $ rejecttest
815 820 do not process $Id: rejecttest
816 821 xxx $
817 822 $Xinfo: User Name <user@example.com>: rejects? $
818 823 ignore $Id$
819 824
820 825 $ hg rollback
821 826 repository tip rolled back to revision 2 (undo commit)
822 827 working directory now based on revision 2
823 828 $ hg update --clean
824 829 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
825 830
826 831 kwexpand/kwshrink on selected files
827 832
828 833 $ mkdir x
829 834 $ hg copy a x/a
830 835 $ hg --verbose kwshrink a
831 836 overwriting a shrinking keywords
832 837 $ hg status a
833 838 $ hg --verbose kwexpand a
834 839 overwriting a expanding keywords
835 840 $ hg status a
836 841
837 842 kwexpand x/a should abort
838 843
839 844 $ hg --verbose kwexpand x/a
840 845 abort: outstanding uncommitted changes
841 846 [255]
842 847 $ cd x
843 848 $ hg --debug commit -m xa -d '3 0' -u 'User Name <user@example.com>'
844 849 x/a
845 850 x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e
846 851 overwriting x/a expanding keywords
847 852 committed changeset 3:b4560182a3f9a358179fd2d835c15e9da379c1e4
848 853 $ cat a
849 854 expand $Id: x/a b4560182a3f9 Thu, 01 Jan 1970 00:00:03 +0000 user $
850 855 do not process $Id:
851 856 xxx $
852 857 $Xinfo: User Name <user@example.com>: xa $
853 858
854 859 kwshrink a inside directory x
855 860
856 861 $ hg --verbose kwshrink a
857 862 overwriting x/a shrinking keywords
858 863 $ cat a
859 864 expand $Id$
860 865 do not process $Id:
861 866 xxx $
862 867 $Xinfo$
863 868 $ cd ..
864 869
865 870 kwexpand nonexistent
866 871
867 872 $ hg kwexpand nonexistent
868 873 nonexistent:* (glob)
869 874
870 875
871 876 hg serve
872 877 - expand with hgweb file
873 878 - no expansion with hgweb annotate/changeset/filediff
874 879 - check errors
875 880
876 881 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
877 882 $ cat hg.pid >> $DAEMON_PIDS
878 883 $ $TESTDIR/get-with-headers.py localhost:$HGPORT '/file/tip/a/?style=raw'
879 884 200 Script output follows
880 885
881 886 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
882 887 do not process $Id:
883 888 xxx $
884 889 $Xinfo: User Name <user@example.com>: firstline $
885 890 $ $TESTDIR/get-with-headers.py localhost:$HGPORT '/annotate/tip/a/?style=raw'
886 891 200 Script output follows
887 892
888 893
889 894 user@1: expand $Id$
890 895 user@1: do not process $Id:
891 896 user@1: xxx $
892 897 user@2: $Xinfo$
893 898
894 899
895 900
896 901
897 902 $ $TESTDIR/get-with-headers.py localhost:$HGPORT '/rev/tip/?style=raw'
898 903 200 Script output follows
899 904
900 905
901 906 # HG changeset patch
902 907 # User User Name <user@example.com>
903 908 # Date 3 0
904 909 # Node ID b4560182a3f9a358179fd2d835c15e9da379c1e4
905 910 # Parent bb948857c743469b22bbf51f7ec8112279ca5d83
906 911 xa
907 912
908 913 diff -r bb948857c743 -r b4560182a3f9 x/a
909 914 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
910 915 +++ b/x/a Thu Jan 01 00:00:03 1970 +0000
911 916 @@ -0,0 +1,4 @@
912 917 +expand $Id$
913 918 +do not process $Id:
914 919 +xxx $
915 920 +$Xinfo$
916 921
917 922 $ $TESTDIR/get-with-headers.py localhost:$HGPORT '/diff/bb948857c743/a?style=raw'
918 923 200 Script output follows
919 924
920 925
921 926 diff -r ef63ca68695b -r bb948857c743 a
922 927 --- a/a Thu Jan 01 00:00:00 1970 +0000
923 928 +++ b/a Thu Jan 01 00:00:02 1970 +0000
924 929 @@ -1,3 +1,4 @@
925 930 expand $Id$
926 931 do not process $Id:
927 932 xxx $
928 933 +$Xinfo$
929 934
930 935
931 936
932 937
933 938 $ cat errors.log
934 939
935 940 Prepare merge and resolve tests
936 941
937 942 $ echo '$Id$' > m
938 943 $ hg add m
939 944 $ hg commit -m 4kw
940 945 $ echo foo >> m
941 946 $ hg commit -m 5foo
942 947
943 948 simplemerge
944 949
945 950 $ hg update 4
946 951 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
947 952 $ echo foo >> m
948 953 $ hg commit -m 6foo
949 954 created new head
950 955 $ hg merge
951 956 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
952 957 (branch merge, don't forget to commit)
953 958 $ hg commit -m simplemerge
954 959 $ cat m
955 960 $Id: m 27d48ee14f67 Thu, 01 Jan 1970 00:00:00 +0000 test $
956 961 foo
957 962
958 963 conflict: keyword should stay outside conflict zone
959 964
960 965 $ hg update 4
961 966 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
962 967 $ echo bar >> m
963 968 $ hg commit -m 8bar
964 969 created new head
965 970 $ hg merge
966 971 merging m
967 972 warning: conflicts during merge.
968 973 merging m failed!
969 974 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
970 975 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
971 976 [1]
972 977 $ cat m
973 978 $Id$
974 979 <<<<<<< local
975 980 bar
976 981 =======
977 982 foo
978 983 >>>>>>> other
979 984
980 985 resolve to local
981 986
982 987 $ HGMERGE=internal:local hg resolve -a
983 988 $ hg commit -m localresolve
984 989 $ cat m
985 990 $Id: m 800511b3a22d Thu, 01 Jan 1970 00:00:00 +0000 test $
986 991 bar
987 992
988 993 Test restricted mode with transplant -b
989 994
990 995 $ hg update 6
991 996 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
992 997 $ hg branch foo
993 998 marked working directory as branch foo
994 999 $ mv a a.bak
995 1000 $ echo foobranch > a
996 1001 $ cat a.bak >> a
997 1002 $ rm a.bak
998 1003 $ hg commit -m 9foobranch
999 1004 $ hg update default
1000 1005 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1001 1006 $ hg -y transplant -b foo tip
1002 1007 applying 4aa30d025d50
1003 1008 4aa30d025d50 transplanted to e00abbf63521
1004 1009
1005 1010 Expansion in changeset but not in file
1006 1011
1007 1012 $ hg tip -p
1008 1013 changeset: 11:e00abbf63521
1009 1014 tag: tip
1010 1015 parent: 9:800511b3a22d
1011 1016 user: test
1012 1017 date: Thu Jan 01 00:00:00 1970 +0000
1013 1018 summary: 9foobranch
1014 1019
1015 1020 diff -r 800511b3a22d -r e00abbf63521 a
1016 1021 --- a/a Thu Jan 01 00:00:00 1970 +0000
1017 1022 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1018 1023 @@ -1,3 +1,4 @@
1019 1024 +foobranch
1020 1025 expand $Id$
1021 1026 do not process $Id:
1022 1027 xxx $
1023 1028
1024 1029 $ head -n 2 a
1025 1030 foobranch
1026 1031 expand $Id: a e00abbf63521 Thu, 01 Jan 1970 00:00:00 +0000 test $
1027 1032
1028 1033 Turn off expansion
1029 1034
1030 1035 $ hg -q rollback
1031 1036 $ hg -q update -C
1032 1037
1033 1038 kwshrink with unknown file u
1034 1039
1035 1040 $ cp a u
1036 1041 $ hg --verbose kwshrink
1037 1042 overwriting a shrinking keywords
1038 1043 overwriting m shrinking keywords
1039 1044 overwriting x/a shrinking keywords
1040 1045
1041 1046 Keywords shrunk in working directory, but not yet disabled
1042 1047 - cat shows unexpanded keywords
1043 1048 - hg cat shows expanded keywords
1044 1049
1045 1050 $ cat a b
1046 1051 expand $Id$
1047 1052 do not process $Id:
1048 1053 xxx $
1049 1054 $Xinfo$
1050 1055 ignore $Id$
1051 1056 $ hg cat sym a b && echo
1052 1057 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
1053 1058 do not process $Id:
1054 1059 xxx $
1055 1060 $Xinfo: User Name <user@example.com>: firstline $
1056 1061 ignore $Id$
1057 1062 a
1058 1063
1059 1064 Now disable keyword expansion
1060 1065
1061 1066 $ rm "$HGRCPATH"
1062 1067 $ cat a b
1063 1068 expand $Id$
1064 1069 do not process $Id:
1065 1070 xxx $
1066 1071 $Xinfo$
1067 1072 ignore $Id$
1068 1073 $ hg cat sym a b && echo
1069 1074 expand $Id$
1070 1075 do not process $Id:
1071 1076 xxx $
1072 1077 $Xinfo$
1073 1078 ignore $Id$
1074 1079 a
General Comments 0
You need to be logged in to leave comments. Login now