##// END OF EJS Templates
keyword: update documentation for kwshrink...
Christian Ebert -
r13270:1e0d631b default
parent child Browse files
Show More
@@ -1,674 +1,672
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 Before changing/disabling active keywords, run :hg:`kwshrink` to avoid
74 the risk of inadvertently storing expanded keywords in the change
75 history.
73 Before changing/disabling active keywords, you must run :hg:`kwshrink`
74 to avoid storing expanded keywords in the change history.
76 75
77 76 To force expansion after enabling it, or a configuration change, run
78 77 :hg:`kwexpand`.
79 78
80 79 Expansions spanning more than one line and incremental expansions,
81 80 like CVS' $Log$, are not supported. A keyword template map "Log =
82 81 {desc}" expands to the first line of the changeset description.
83 82 '''
84 83
85 84 from mercurial import commands, context, cmdutil, dispatch, filelog, extensions
86 85 from mercurial import localrepo, match, patch, templatefilters, templater, util
87 86 from mercurial.hgweb import webcommands
88 87 from mercurial.i18n import _
89 88 import os, re, shutil, tempfile
90 89
91 90 commands.optionalrepo += ' kwdemo'
92 91
93 92 # hg commands that do not act on keywords
94 93 nokwcommands = ('add addremove annotate bundle export grep incoming init log'
95 94 ' outgoing push tip verify convert email glog')
96 95
97 96 # hg commands that trigger expansion only when writing to working dir,
98 97 # not when reading filelog, and unexpand when reading from working dir
99 98 restricted = 'merge kwexpand kwshrink record qrecord resolve transplant'
100 99
101 100 # names of extensions using dorecord
102 101 recordextensions = 'record'
103 102
104 103 colortable = {
105 104 'kwfiles.enabled': 'green bold',
106 105 'kwfiles.deleted': 'cyan bold underline',
107 106 'kwfiles.enabledunknown': 'green',
108 107 'kwfiles.ignored': 'bold',
109 108 'kwfiles.ignoredunknown': 'none'
110 109 }
111 110
112 111 # date like in cvs' $Date
113 112 utcdate = lambda x: util.datestr((x[0], 0), '%Y/%m/%d %H:%M:%S')
114 113 # date like in svn's $Date
115 114 svnisodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
116 115 # date like in svn's $Id
117 116 svnutcdate = lambda x: util.datestr((x[0], 0), '%Y-%m-%d %H:%M:%SZ')
118 117
119 118 # make keyword tools accessible
120 119 kwtools = {'templater': None, 'hgcmd': ''}
121 120
122 121 def _defaultkwmaps(ui):
123 122 '''Returns default keywordmaps according to keywordset configuration.'''
124 123 templates = {
125 124 'Revision': '{node|short}',
126 125 'Author': '{author|user}',
127 126 }
128 127 kwsets = ({
129 128 'Date': '{date|utcdate}',
130 129 'RCSfile': '{file|basename},v',
131 130 'RCSFile': '{file|basename},v', # kept for backwards compatibility
132 131 # with hg-keyword
133 132 'Source': '{root}/{file},v',
134 133 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
135 134 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
136 135 }, {
137 136 'Date': '{date|svnisodate}',
138 137 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
139 138 'LastChangedRevision': '{node|short}',
140 139 'LastChangedBy': '{author|user}',
141 140 'LastChangedDate': '{date|svnisodate}',
142 141 })
143 142 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
144 143 return templates
145 144
146 145 def _shrinktext(text, subfunc):
147 146 '''Helper for keyword expansion removal in text.
148 147 Depending on subfunc also returns number of substitutions.'''
149 148 return subfunc(r'$\1$', text)
150 149
151 150 def _preselect(wstatus, changed):
152 151 '''Retrieves modfied and added files from a working directory state
153 152 and returns the subset of each contained in given changed files
154 153 retrieved from a change context.'''
155 154 modified, added = wstatus[:2]
156 155 modified = [f for f in modified if f in changed]
157 156 added = [f for f in added if f in changed]
158 157 return modified, added
159 158
160 159
161 160 class kwtemplater(object):
162 161 '''
163 162 Sets up keyword templates, corresponding keyword regex, and
164 163 provides keyword substitution functions.
165 164 '''
166 165
167 166 def __init__(self, ui, repo, inc, exc):
168 167 self.ui = ui
169 168 self.repo = repo
170 169 self.match = match.match(repo.root, '', [], inc, exc)
171 170 self.restrict = kwtools['hgcmd'] in restricted.split()
172 171 self.record = False
173 172
174 173 kwmaps = self.ui.configitems('keywordmaps')
175 174 if kwmaps: # override default templates
176 175 self.templates = dict((k, templater.parsestring(v, False))
177 176 for k, v in kwmaps)
178 177 else:
179 178 self.templates = _defaultkwmaps(self.ui)
180 179 templatefilters.filters.update({'utcdate': utcdate,
181 180 'svnisodate': svnisodate,
182 181 'svnutcdate': svnutcdate})
183 182
184 183 @util.propertycache
185 184 def escape(self):
186 185 '''Returns bar-separated and escaped keywords.'''
187 186 return '|'.join(map(re.escape, self.templates.keys()))
188 187
189 188 @util.propertycache
190 189 def rekw(self):
191 190 '''Returns regex for unexpanded keywords.'''
192 191 return re.compile(r'\$(%s)\$' % self.escape)
193 192
194 193 @util.propertycache
195 194 def rekwexp(self):
196 195 '''Returns regex for expanded keywords.'''
197 196 return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
198 197
199 198 def substitute(self, data, path, ctx, subfunc):
200 199 '''Replaces keywords in data with expanded template.'''
201 200 def kwsub(mobj):
202 201 kw = mobj.group(1)
203 202 ct = cmdutil.changeset_templater(self.ui, self.repo,
204 203 False, None, '', False)
205 204 ct.use_template(self.templates[kw])
206 205 self.ui.pushbuffer()
207 206 ct.show(ctx, root=self.repo.root, file=path)
208 207 ekw = templatefilters.firstline(self.ui.popbuffer())
209 208 return '$%s: %s $' % (kw, ekw)
210 209 return subfunc(kwsub, data)
211 210
212 211 def linkctx(self, path, fileid):
213 212 '''Similar to filelog.linkrev, but returns a changectx.'''
214 213 return self.repo.filectx(path, fileid=fileid).changectx()
215 214
216 215 def expand(self, path, node, data):
217 216 '''Returns data with keywords expanded.'''
218 217 if not self.restrict and self.match(path) and not util.binary(data):
219 218 ctx = self.linkctx(path, node)
220 219 return self.substitute(data, path, ctx, self.rekw.sub)
221 220 return data
222 221
223 222 def iskwfile(self, cand, ctx):
224 223 '''Returns subset of candidates which are configured for keyword
225 224 expansion are not symbolic links.'''
226 225 return [f for f in cand if self.match(f) and not 'l' in ctx.flags(f)]
227 226
228 227 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
229 228 '''Overwrites selected files expanding/shrinking keywords.'''
230 229 if self.restrict or lookup or self.record: # exclude kw_copy
231 230 candidates = self.iskwfile(candidates, ctx)
232 231 if not candidates:
233 232 return
234 233 kwcmd = self.restrict and lookup # kwexpand/kwshrink
235 234 if self.restrict or expand and lookup:
236 235 mf = ctx.manifest()
237 236 lctx = ctx
238 237 re_kw = (self.restrict or rekw) and self.rekw or self.rekwexp
239 238 msg = (expand and _('overwriting %s expanding keywords\n')
240 239 or _('overwriting %s shrinking keywords\n'))
241 240 for f in candidates:
242 241 if self.restrict:
243 242 data = self.repo.file(f).read(mf[f])
244 243 else:
245 244 data = self.repo.wread(f)
246 245 if util.binary(data):
247 246 continue
248 247 if expand:
249 248 if lookup:
250 249 lctx = self.linkctx(f, mf[f])
251 250 data, found = self.substitute(data, f, lctx, re_kw.subn)
252 251 elif self.restrict:
253 252 found = re_kw.search(data)
254 253 else:
255 254 data, found = _shrinktext(data, re_kw.subn)
256 255 if found:
257 256 self.ui.note(msg % f)
258 257 self.repo.wwrite(f, data, ctx.flags(f))
259 258 if kwcmd:
260 259 self.repo.dirstate.normal(f)
261 260 elif self.record:
262 261 self.repo.dirstate.normallookup(f)
263 262
264 263 def shrink(self, fname, text):
265 264 '''Returns text with all keyword substitutions removed.'''
266 265 if self.match(fname) and not util.binary(text):
267 266 return _shrinktext(text, self.rekwexp.sub)
268 267 return text
269 268
270 269 def shrinklines(self, fname, lines):
271 270 '''Returns lines with keyword substitutions removed.'''
272 271 if self.match(fname):
273 272 text = ''.join(lines)
274 273 if not util.binary(text):
275 274 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
276 275 return lines
277 276
278 277 def wread(self, fname, data):
279 278 '''If in restricted mode returns data read from wdir with
280 279 keyword substitutions removed.'''
281 280 return self.restrict and self.shrink(fname, data) or data
282 281
283 282 class kwfilelog(filelog.filelog):
284 283 '''
285 284 Subclass of filelog to hook into its read, add, cmp methods.
286 285 Keywords are "stored" unexpanded, and processed on reading.
287 286 '''
288 287 def __init__(self, opener, kwt, path):
289 288 super(kwfilelog, self).__init__(opener, path)
290 289 self.kwt = kwt
291 290 self.path = path
292 291
293 292 def read(self, node):
294 293 '''Expands keywords when reading filelog.'''
295 294 data = super(kwfilelog, self).read(node)
296 295 if self.renamed(node):
297 296 return data
298 297 return self.kwt.expand(self.path, node, data)
299 298
300 299 def add(self, text, meta, tr, link, p1=None, p2=None):
301 300 '''Removes keyword substitutions when adding to filelog.'''
302 301 text = self.kwt.shrink(self.path, text)
303 302 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
304 303
305 304 def cmp(self, node, text):
306 305 '''Removes keyword substitutions for comparison.'''
307 306 text = self.kwt.shrink(self.path, text)
308 307 return super(kwfilelog, self).cmp(node, text)
309 308
310 309 def _status(ui, repo, kwt, *pats, **opts):
311 310 '''Bails out if [keyword] configuration is not active.
312 311 Returns status of working directory.'''
313 312 if kwt:
314 313 return repo.status(match=cmdutil.match(repo, pats, opts), clean=True,
315 314 unknown=opts.get('unknown') or opts.get('all'))
316 315 if ui.configitems('keyword'):
317 316 raise util.Abort(_('[keyword] patterns cannot match'))
318 317 raise util.Abort(_('no [keyword] patterns configured'))
319 318
320 319 def _kwfwrite(ui, repo, expand, *pats, **opts):
321 320 '''Selects files and passes them to kwtemplater.overwrite.'''
322 321 wctx = repo[None]
323 322 if len(wctx.parents()) > 1:
324 323 raise util.Abort(_('outstanding uncommitted merge'))
325 324 kwt = kwtools['templater']
326 325 wlock = repo.wlock()
327 326 try:
328 327 status = _status(ui, repo, kwt, *pats, **opts)
329 328 modified, added, removed, deleted, unknown, ignored, clean = status
330 329 if modified or added or removed or deleted:
331 330 raise util.Abort(_('outstanding uncommitted changes'))
332 331 kwt.overwrite(wctx, clean, True, expand)
333 332 finally:
334 333 wlock.release()
335 334
336 335 def demo(ui, repo, *args, **opts):
337 336 '''print [keywordmaps] configuration and an expansion example
338 337
339 338 Show current, custom, or default keyword template maps and their
340 339 expansions.
341 340
342 341 Extend the current configuration by specifying maps as arguments
343 342 and using -f/--rcfile to source an external hgrc file.
344 343
345 344 Use -d/--default to disable current configuration.
346 345
347 346 See :hg:`help templates` for information on templates and filters.
348 347 '''
349 348 def demoitems(section, items):
350 349 ui.write('[%s]\n' % section)
351 350 for k, v in sorted(items):
352 351 ui.write('%s = %s\n' % (k, v))
353 352
354 353 fn = 'demo.txt'
355 354 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
356 355 ui.note(_('creating temporary repository at %s\n') % tmpdir)
357 356 repo = localrepo.localrepository(ui, tmpdir, True)
358 357 ui.setconfig('keyword', fn, '')
359 358
360 359 uikwmaps = ui.configitems('keywordmaps')
361 360 if args or opts.get('rcfile'):
362 361 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
363 362 if uikwmaps:
364 363 ui.status(_('\textending current template maps\n'))
365 364 if opts.get('default') or not uikwmaps:
366 365 ui.status(_('\toverriding default template maps\n'))
367 366 if opts.get('rcfile'):
368 367 ui.readconfig(opts.get('rcfile'))
369 368 if args:
370 369 # simulate hgrc parsing
371 370 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
372 371 fp = repo.opener('hgrc', 'w')
373 372 fp.writelines(rcmaps)
374 373 fp.close()
375 374 ui.readconfig(repo.join('hgrc'))
376 375 kwmaps = dict(ui.configitems('keywordmaps'))
377 376 elif opts.get('default'):
378 377 ui.status(_('\n\tconfiguration using default keyword template maps\n'))
379 378 kwmaps = _defaultkwmaps(ui)
380 379 if uikwmaps:
381 380 ui.status(_('\tdisabling current template maps\n'))
382 381 for k, v in kwmaps.iteritems():
383 382 ui.setconfig('keywordmaps', k, v)
384 383 else:
385 384 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
386 385 kwmaps = dict(uikwmaps) or _defaultkwmaps(ui)
387 386
388 387 uisetup(ui)
389 388 reposetup(ui, repo)
390 389 ui.write('[extensions]\nkeyword =\n')
391 390 demoitems('keyword', ui.configitems('keyword'))
392 391 demoitems('keywordmaps', kwmaps.iteritems())
393 392 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
394 393 repo.wopener(fn, 'w').write(keywords)
395 394 repo[None].add([fn])
396 395 ui.note(_('\nkeywords written to %s:\n') % fn)
397 396 ui.note(keywords)
398 397 repo.dirstate.setbranch('demobranch')
399 398 for name, cmd in ui.configitems('hooks'):
400 399 if name.split('.', 1)[0].find('commit') > -1:
401 400 repo.ui.setconfig('hooks', name, '')
402 401 msg = _('hg keyword configuration and expansion example')
403 402 ui.note("hg ci -m '%s'\n" % msg)
404 403 repo.commit(text=msg)
405 404 ui.status(_('\n\tkeywords expanded\n'))
406 405 ui.write(repo.wread(fn))
407 406 shutil.rmtree(tmpdir, ignore_errors=True)
408 407
409 408 def expand(ui, repo, *pats, **opts):
410 409 '''expand keywords in the working directory
411 410
412 411 Run after (re)enabling keyword expansion.
413 412
414 413 kwexpand refuses to run if given files contain local changes.
415 414 '''
416 415 # 3rd argument sets expansion to True
417 416 _kwfwrite(ui, repo, True, *pats, **opts)
418 417
419 418 def files(ui, repo, *pats, **opts):
420 419 '''show files configured for keyword expansion
421 420
422 421 List which files in the working directory are matched by the
423 422 [keyword] configuration patterns.
424 423
425 424 Useful to prevent inadvertent keyword expansion and to speed up
426 425 execution by including only files that are actual candidates for
427 426 expansion.
428 427
429 428 See :hg:`help keyword` on how to construct patterns both for
430 429 inclusion and exclusion of files.
431 430
432 431 With -A/--all and -v/--verbose the codes used to show the status
433 432 of files are::
434 433
435 434 K = keyword expansion candidate
436 435 k = keyword expansion candidate (not tracked)
437 436 I = ignored
438 437 i = ignored (not tracked)
439 438 '''
440 439 kwt = kwtools['templater']
441 440 status = _status(ui, repo, kwt, *pats, **opts)
442 441 cwd = pats and repo.getcwd() or ''
443 442 modified, added, removed, deleted, unknown, ignored, clean = status
444 443 files = []
445 444 if not opts.get('unknown') or opts.get('all'):
446 445 files = sorted(modified + added + clean)
447 446 wctx = repo[None]
448 447 kwfiles = kwt.iskwfile(files, wctx)
449 448 kwdeleted = kwt.iskwfile(deleted, wctx)
450 449 kwunknown = kwt.iskwfile(unknown, wctx)
451 450 if not opts.get('ignore') or opts.get('all'):
452 451 showfiles = kwfiles, kwdeleted, kwunknown
453 452 else:
454 453 showfiles = [], [], []
455 454 if opts.get('all') or opts.get('ignore'):
456 455 showfiles += ([f for f in files if f not in kwfiles],
457 456 [f for f in unknown if f not in kwunknown])
458 457 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
459 458 kwstates = zip('K!kIi', showfiles, kwlabels)
460 459 for char, filenames, kwstate in kwstates:
461 460 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
462 461 for f in filenames:
463 462 ui.write(fmt % repo.pathto(f, cwd), label='kwfiles.' + kwstate)
464 463
465 464 def shrink(ui, repo, *pats, **opts):
466 465 '''revert expanded keywords in the working directory
467 466
468 Run before changing/disabling active keywords or if you experience
469 problems with :hg:`import` or :hg:`merge`.
467 Must be run before changing/disabling active keywords.
470 468
471 469 kwshrink refuses to run if given files contain local changes.
472 470 '''
473 471 # 3rd argument sets expansion to False
474 472 _kwfwrite(ui, repo, False, *pats, **opts)
475 473
476 474
477 475 def uisetup(ui):
478 476 ''' Monkeypatches dispatch._parse to retrieve user command.'''
479 477
480 478 def kwdispatch_parse(orig, ui, args):
481 479 '''Monkeypatch dispatch._parse to obtain running hg command.'''
482 480 cmd, func, args, options, cmdoptions = orig(ui, args)
483 481 kwtools['hgcmd'] = cmd
484 482 return cmd, func, args, options, cmdoptions
485 483
486 484 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
487 485
488 486 def reposetup(ui, repo):
489 487 '''Sets up repo as kwrepo for keyword substitution.
490 488 Overrides file method to return kwfilelog instead of filelog
491 489 if file matches user configuration.
492 490 Wraps commit to overwrite configured files with updated
493 491 keyword substitutions.
494 492 Monkeypatches patch and webcommands.'''
495 493
496 494 try:
497 495 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
498 496 or '.hg' in util.splitpath(repo.root)
499 497 or repo._url.startswith('bundle:')):
500 498 return
501 499 except AttributeError:
502 500 pass
503 501
504 502 inc, exc = [], ['.hg*']
505 503 for pat, opt in ui.configitems('keyword'):
506 504 if opt != 'ignore':
507 505 inc.append(pat)
508 506 else:
509 507 exc.append(pat)
510 508 if not inc:
511 509 return
512 510
513 511 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
514 512
515 513 class kwrepo(repo.__class__):
516 514 def file(self, f):
517 515 if f[0] == '/':
518 516 f = f[1:]
519 517 return kwfilelog(self.sopener, kwt, f)
520 518
521 519 def wread(self, filename):
522 520 data = super(kwrepo, self).wread(filename)
523 521 return kwt.wread(filename, data)
524 522
525 523 def commit(self, *args, **opts):
526 524 # use custom commitctx for user commands
527 525 # other extensions can still wrap repo.commitctx directly
528 526 self.commitctx = self.kwcommitctx
529 527 try:
530 528 return super(kwrepo, self).commit(*args, **opts)
531 529 finally:
532 530 del self.commitctx
533 531
534 532 def kwcommitctx(self, ctx, error=False):
535 533 n = super(kwrepo, self).commitctx(ctx, error)
536 534 # no lock needed, only called from repo.commit() which already locks
537 535 if not kwt.record:
538 536 restrict = kwt.restrict
539 537 kwt.restrict = True
540 538 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
541 539 False, True)
542 540 kwt.restrict = restrict
543 541 return n
544 542
545 543 def rollback(self, dryrun=False):
546 544 wlock = self.wlock()
547 545 try:
548 546 if not dryrun:
549 547 changed = self['.'].files()
550 548 ret = super(kwrepo, self).rollback(dryrun)
551 549 if not dryrun:
552 550 ctx = self['.']
553 551 modified, added = _preselect(self[None].status(), changed)
554 552 kwt.overwrite(ctx, modified, True, True)
555 553 kwt.overwrite(ctx, added, True, False)
556 554 return ret
557 555 finally:
558 556 wlock.release()
559 557
560 558 # monkeypatches
561 559 def kwpatchfile_init(orig, self, ui, fname, opener,
562 560 missing=False, eolmode=None):
563 561 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
564 562 rejects or conflicts due to expanded keywords in working dir.'''
565 563 orig(self, ui, fname, opener, missing, eolmode)
566 564 # shrink keywords read from working dir
567 565 self.lines = kwt.shrinklines(self.fname, self.lines)
568 566
569 567 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
570 568 opts=None, prefix=''):
571 569 '''Monkeypatch patch.diff to avoid expansion.'''
572 570 kwt.restrict = True
573 571 return orig(repo, node1, node2, match, changes, opts, prefix)
574 572
575 573 def kwweb_skip(orig, web, req, tmpl):
576 574 '''Wraps webcommands.x turning off keyword expansion.'''
577 575 kwt.match = util.never
578 576 return orig(web, req, tmpl)
579 577
580 578 def kw_copy(orig, ui, repo, pats, opts, rename=False):
581 579 '''Wraps cmdutil.copy so that copy/rename destinations do not
582 580 contain expanded keywords.
583 581 Note that the source of a regular file destination may also be a
584 582 symlink:
585 583 hg cp sym x -> x is symlink
586 584 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
587 585 For the latter we have to follow the symlink to find out whether its
588 586 target is configured for expansion and we therefore must unexpand the
589 587 keywords in the destination.'''
590 588 orig(ui, repo, pats, opts, rename)
591 589 if opts.get('dry_run'):
592 590 return
593 591 wctx = repo[None]
594 592 cwd = repo.getcwd()
595 593
596 594 def haskwsource(dest):
597 595 '''Returns true if dest is a regular file and configured for
598 596 expansion or a symlink which points to a file configured for
599 597 expansion. '''
600 598 source = repo.dirstate.copied(dest)
601 599 if 'l' in wctx.flags(source):
602 600 source = util.canonpath(repo.root, cwd,
603 601 os.path.realpath(source))
604 602 return kwt.match(source)
605 603
606 604 candidates = [f for f in repo.dirstate.copies() if
607 605 not 'l' in wctx.flags(f) and haskwsource(f)]
608 606 kwt.overwrite(wctx, candidates, False, False)
609 607
610 608 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
611 609 '''Wraps record.dorecord expanding keywords after recording.'''
612 610 wlock = repo.wlock()
613 611 try:
614 612 # record returns 0 even when nothing has changed
615 613 # therefore compare nodes before and after
616 614 kwt.record = True
617 615 ctx = repo['.']
618 616 wstatus = repo[None].status()
619 617 ret = orig(ui, repo, commitfunc, *pats, **opts)
620 618 recctx = repo['.']
621 619 if ctx != recctx:
622 620 modified, added = _preselect(wstatus, recctx.files())
623 621 kwt.restrict = False
624 622 kwt.overwrite(recctx, modified, False, True)
625 623 kwt.overwrite(recctx, added, False, True, True)
626 624 kwt.restrict = True
627 625 return ret
628 626 finally:
629 627 wlock.release()
630 628
631 629 repo.__class__ = kwrepo
632 630
633 631 def kwfilectx_cmp(orig, self, fctx):
634 632 # keyword affects data size, comparing wdir and filelog size does
635 633 # not make sense
636 634 if (fctx._filerev is None and
637 635 (self._repo._encodefilterpats or
638 636 kwt.match(fctx.path()) and not 'l' in fctx.flags()) or
639 637 self.size() == fctx.size()):
640 638 return self._filelog.cmp(self._filenode, fctx.data())
641 639 return True
642 640
643 641 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
644 642 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
645 643 extensions.wrapfunction(patch, 'diff', kw_diff)
646 644 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
647 645 for c in 'annotate changeset rev filediff diff'.split():
648 646 extensions.wrapfunction(webcommands, c, kwweb_skip)
649 647 for name in recordextensions.split():
650 648 try:
651 649 record = extensions.find(name)
652 650 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
653 651 except KeyError:
654 652 pass
655 653
656 654 cmdtable = {
657 655 'kwdemo':
658 656 (demo,
659 657 [('d', 'default', None, _('show default keyword template maps')),
660 658 ('f', 'rcfile', '',
661 659 _('read maps from rcfile'), _('FILE'))],
662 660 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
663 661 'kwexpand': (expand, commands.walkopts,
664 662 _('hg kwexpand [OPTION]... [FILE]...')),
665 663 'kwfiles':
666 664 (files,
667 665 [('A', 'all', None, _('show keyword status flags of all files')),
668 666 ('i', 'ignore', None, _('show files excluded from expansion')),
669 667 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
670 668 ] + commands.walkopts,
671 669 _('hg kwfiles [OPTION]... [FILE]...')),
672 670 'kwshrink': (shrink, commands.walkopts,
673 671 _('hg kwshrink [OPTION]... [FILE]...')),
674 672 }
General Comments 0
You need to be logged in to leave comments. Login now