##// END OF EJS Templates
keyword: correct grammar in iskwfile docstring
Christian Ebert -
r15324:0890842c stable
parent child Browse files
Show More
@@ -1,701 +1,701
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 expansion are not symbolic links.'''
240 expansion but 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 if self.restrict or rekw:
253 253 re_kw = self.rekw
254 254 else:
255 255 re_kw = self.rekwexp
256 256 if expand:
257 257 msg = _('overwriting %s expanding keywords\n')
258 258 else:
259 259 msg = _('overwriting %s shrinking keywords\n')
260 260 for f in candidates:
261 261 if self.restrict:
262 262 data = self.repo.file(f).read(mf[f])
263 263 else:
264 264 data = self.repo.wread(f)
265 265 if util.binary(data):
266 266 continue
267 267 if expand:
268 268 if lookup:
269 269 ctx = self.linkctx(f, mf[f])
270 270 data, found = self.substitute(data, f, ctx, re_kw.subn)
271 271 elif self.restrict:
272 272 found = re_kw.search(data)
273 273 else:
274 274 data, found = _shrinktext(data, re_kw.subn)
275 275 if found:
276 276 self.ui.note(msg % f)
277 277 fp = self.repo.wopener(f, "wb", atomictemp=True)
278 278 fp.write(data)
279 279 fp.close()
280 280 if kwcmd:
281 281 self.repo.dirstate.normal(f)
282 282 elif self.record:
283 283 self.repo.dirstate.normallookup(f)
284 284
285 285 def shrink(self, fname, text):
286 286 '''Returns text with all keyword substitutions removed.'''
287 287 if self.match(fname) and not util.binary(text):
288 288 return _shrinktext(text, self.rekwexp.sub)
289 289 return text
290 290
291 291 def shrinklines(self, fname, lines):
292 292 '''Returns lines with keyword substitutions removed.'''
293 293 if self.match(fname):
294 294 text = ''.join(lines)
295 295 if not util.binary(text):
296 296 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
297 297 return lines
298 298
299 299 def wread(self, fname, data):
300 300 '''If in restricted mode returns data read from wdir with
301 301 keyword substitutions removed.'''
302 302 if self.restrict:
303 303 return self.shrink(fname, data)
304 304 return data
305 305
306 306 class kwfilelog(filelog.filelog):
307 307 '''
308 308 Subclass of filelog to hook into its read, add, cmp methods.
309 309 Keywords are "stored" unexpanded, and processed on reading.
310 310 '''
311 311 def __init__(self, opener, kwt, path):
312 312 super(kwfilelog, self).__init__(opener, path)
313 313 self.kwt = kwt
314 314 self.path = path
315 315
316 316 def read(self, node):
317 317 '''Expands keywords when reading filelog.'''
318 318 data = super(kwfilelog, self).read(node)
319 319 if self.renamed(node):
320 320 return data
321 321 return self.kwt.expand(self.path, node, data)
322 322
323 323 def add(self, text, meta, tr, link, p1=None, p2=None):
324 324 '''Removes keyword substitutions when adding to filelog.'''
325 325 text = self.kwt.shrink(self.path, text)
326 326 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
327 327
328 328 def cmp(self, node, text):
329 329 '''Removes keyword substitutions for comparison.'''
330 330 text = self.kwt.shrink(self.path, text)
331 331 return super(kwfilelog, self).cmp(node, text)
332 332
333 333 def _status(ui, repo, wctx, kwt, *pats, **opts):
334 334 '''Bails out if [keyword] configuration is not active.
335 335 Returns status of working directory.'''
336 336 if kwt:
337 337 return repo.status(match=scmutil.match(wctx, pats, opts), clean=True,
338 338 unknown=opts.get('unknown') or opts.get('all'))
339 339 if ui.configitems('keyword'):
340 340 raise util.Abort(_('[keyword] patterns cannot match'))
341 341 raise util.Abort(_('no [keyword] patterns configured'))
342 342
343 343 def _kwfwrite(ui, repo, expand, *pats, **opts):
344 344 '''Selects files and passes them to kwtemplater.overwrite.'''
345 345 wctx = repo[None]
346 346 if len(wctx.parents()) > 1:
347 347 raise util.Abort(_('outstanding uncommitted merge'))
348 348 kwt = kwtools['templater']
349 349 wlock = repo.wlock()
350 350 try:
351 351 status = _status(ui, repo, wctx, kwt, *pats, **opts)
352 352 modified, added, removed, deleted, unknown, ignored, clean = status
353 353 if modified or added or removed or deleted:
354 354 raise util.Abort(_('outstanding uncommitted changes'))
355 355 kwt.overwrite(wctx, clean, True, expand)
356 356 finally:
357 357 wlock.release()
358 358
359 359 @command('kwdemo',
360 360 [('d', 'default', None, _('show default keyword template maps')),
361 361 ('f', 'rcfile', '',
362 362 _('read maps from rcfile'), _('FILE'))],
363 363 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'))
364 364 def demo(ui, repo, *args, **opts):
365 365 '''print [keywordmaps] configuration and an expansion example
366 366
367 367 Show current, custom, or default keyword template maps and their
368 368 expansions.
369 369
370 370 Extend the current configuration by specifying maps as arguments
371 371 and using -f/--rcfile to source an external hgrc file.
372 372
373 373 Use -d/--default to disable current configuration.
374 374
375 375 See :hg:`help templates` for information on templates and filters.
376 376 '''
377 377 def demoitems(section, items):
378 378 ui.write('[%s]\n' % section)
379 379 for k, v in sorted(items):
380 380 ui.write('%s = %s\n' % (k, v))
381 381
382 382 fn = 'demo.txt'
383 383 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
384 384 ui.note(_('creating temporary repository at %s\n') % tmpdir)
385 385 repo = localrepo.localrepository(ui, tmpdir, True)
386 386 ui.setconfig('keyword', fn, '')
387 387 svn = ui.configbool('keywordset', 'svn')
388 388 # explicitly set keywordset for demo output
389 389 ui.setconfig('keywordset', 'svn', svn)
390 390
391 391 uikwmaps = ui.configitems('keywordmaps')
392 392 if args or opts.get('rcfile'):
393 393 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
394 394 if uikwmaps:
395 395 ui.status(_('\textending current template maps\n'))
396 396 if opts.get('default') or not uikwmaps:
397 397 if svn:
398 398 ui.status(_('\toverriding default svn keywordset\n'))
399 399 else:
400 400 ui.status(_('\toverriding default cvs keywordset\n'))
401 401 if opts.get('rcfile'):
402 402 ui.readconfig(opts.get('rcfile'))
403 403 if args:
404 404 # simulate hgrc parsing
405 405 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
406 406 fp = repo.opener('hgrc', 'w')
407 407 fp.writelines(rcmaps)
408 408 fp.close()
409 409 ui.readconfig(repo.join('hgrc'))
410 410 kwmaps = dict(ui.configitems('keywordmaps'))
411 411 elif opts.get('default'):
412 412 if svn:
413 413 ui.status(_('\n\tconfiguration using default svn keywordset\n'))
414 414 else:
415 415 ui.status(_('\n\tconfiguration using default cvs keywordset\n'))
416 416 kwmaps = _defaultkwmaps(ui)
417 417 if uikwmaps:
418 418 ui.status(_('\tdisabling current template maps\n'))
419 419 for k, v in kwmaps.iteritems():
420 420 ui.setconfig('keywordmaps', k, v)
421 421 else:
422 422 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
423 423 if uikwmaps:
424 424 kwmaps = dict(uikwmaps)
425 425 else:
426 426 kwmaps = _defaultkwmaps(ui)
427 427
428 428 uisetup(ui)
429 429 reposetup(ui, repo)
430 430 ui.write('[extensions]\nkeyword =\n')
431 431 demoitems('keyword', ui.configitems('keyword'))
432 432 demoitems('keywordset', ui.configitems('keywordset'))
433 433 demoitems('keywordmaps', kwmaps.iteritems())
434 434 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
435 435 repo.wopener.write(fn, keywords)
436 436 repo[None].add([fn])
437 437 ui.note(_('\nkeywords written to %s:\n') % fn)
438 438 ui.note(keywords)
439 439 repo.dirstate.setbranch('demobranch')
440 440 for name, cmd in ui.configitems('hooks'):
441 441 if name.split('.', 1)[0].find('commit') > -1:
442 442 repo.ui.setconfig('hooks', name, '')
443 443 msg = _('hg keyword configuration and expansion example')
444 444 ui.note("hg ci -m '%s'\n" % msg)
445 445 repo.commit(text=msg)
446 446 ui.status(_('\n\tkeywords expanded\n'))
447 447 ui.write(repo.wread(fn))
448 448 shutil.rmtree(tmpdir, ignore_errors=True)
449 449
450 450 @command('kwexpand', commands.walkopts, _('hg kwexpand [OPTION]... [FILE]...'))
451 451 def expand(ui, repo, *pats, **opts):
452 452 '''expand keywords in the working directory
453 453
454 454 Run after (re)enabling keyword expansion.
455 455
456 456 kwexpand refuses to run if given files contain local changes.
457 457 '''
458 458 # 3rd argument sets expansion to True
459 459 _kwfwrite(ui, repo, True, *pats, **opts)
460 460
461 461 @command('kwfiles',
462 462 [('A', 'all', None, _('show keyword status flags of all files')),
463 463 ('i', 'ignore', None, _('show files excluded from expansion')),
464 464 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
465 465 ] + commands.walkopts,
466 466 _('hg kwfiles [OPTION]... [FILE]...'))
467 467 def files(ui, repo, *pats, **opts):
468 468 '''show files configured for keyword expansion
469 469
470 470 List which files in the working directory are matched by the
471 471 [keyword] configuration patterns.
472 472
473 473 Useful to prevent inadvertent keyword expansion and to speed up
474 474 execution by including only files that are actual candidates for
475 475 expansion.
476 476
477 477 See :hg:`help keyword` on how to construct patterns both for
478 478 inclusion and exclusion of files.
479 479
480 480 With -A/--all and -v/--verbose the codes used to show the status
481 481 of files are::
482 482
483 483 K = keyword expansion candidate
484 484 k = keyword expansion candidate (not tracked)
485 485 I = ignored
486 486 i = ignored (not tracked)
487 487 '''
488 488 kwt = kwtools['templater']
489 489 wctx = repo[None]
490 490 status = _status(ui, repo, wctx, kwt, *pats, **opts)
491 491 cwd = pats and repo.getcwd() or ''
492 492 modified, added, removed, deleted, unknown, ignored, clean = status
493 493 files = []
494 494 if not opts.get('unknown') or opts.get('all'):
495 495 files = sorted(modified + added + clean)
496 496 kwfiles = kwt.iskwfile(files, wctx)
497 497 kwdeleted = kwt.iskwfile(deleted, wctx)
498 498 kwunknown = kwt.iskwfile(unknown, wctx)
499 499 if not opts.get('ignore') or opts.get('all'):
500 500 showfiles = kwfiles, kwdeleted, kwunknown
501 501 else:
502 502 showfiles = [], [], []
503 503 if opts.get('all') or opts.get('ignore'):
504 504 showfiles += ([f for f in files if f not in kwfiles],
505 505 [f for f in unknown if f not in kwunknown])
506 506 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
507 507 kwstates = zip('K!kIi', showfiles, kwlabels)
508 508 for char, filenames, kwstate in kwstates:
509 509 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
510 510 for f in filenames:
511 511 ui.write(fmt % repo.pathto(f, cwd), label='kwfiles.' + kwstate)
512 512
513 513 @command('kwshrink', commands.walkopts, _('hg kwshrink [OPTION]... [FILE]...'))
514 514 def shrink(ui, repo, *pats, **opts):
515 515 '''revert expanded keywords in the working directory
516 516
517 517 Must be run before changing/disabling active keywords.
518 518
519 519 kwshrink refuses to run if given files contain local changes.
520 520 '''
521 521 # 3rd argument sets expansion to False
522 522 _kwfwrite(ui, repo, False, *pats, **opts)
523 523
524 524
525 525 def uisetup(ui):
526 526 ''' Monkeypatches dispatch._parse to retrieve user command.'''
527 527
528 528 def kwdispatch_parse(orig, ui, args):
529 529 '''Monkeypatch dispatch._parse to obtain running hg command.'''
530 530 cmd, func, args, options, cmdoptions = orig(ui, args)
531 531 kwtools['hgcmd'] = cmd
532 532 return cmd, func, args, options, cmdoptions
533 533
534 534 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
535 535
536 536 def reposetup(ui, repo):
537 537 '''Sets up repo as kwrepo for keyword substitution.
538 538 Overrides file method to return kwfilelog instead of filelog
539 539 if file matches user configuration.
540 540 Wraps commit to overwrite configured files with updated
541 541 keyword substitutions.
542 542 Monkeypatches patch and webcommands.'''
543 543
544 544 try:
545 545 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
546 546 or '.hg' in util.splitpath(repo.root)
547 547 or repo._url.startswith('bundle:')):
548 548 return
549 549 except AttributeError:
550 550 pass
551 551
552 552 inc, exc = [], ['.hg*']
553 553 for pat, opt in ui.configitems('keyword'):
554 554 if opt != 'ignore':
555 555 inc.append(pat)
556 556 else:
557 557 exc.append(pat)
558 558 if not inc:
559 559 return
560 560
561 561 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
562 562
563 563 class kwrepo(repo.__class__):
564 564 def file(self, f):
565 565 if f[0] == '/':
566 566 f = f[1:]
567 567 return kwfilelog(self.sopener, kwt, f)
568 568
569 569 def wread(self, filename):
570 570 data = super(kwrepo, self).wread(filename)
571 571 return kwt.wread(filename, data)
572 572
573 573 def commit(self, *args, **opts):
574 574 # use custom commitctx for user commands
575 575 # other extensions can still wrap repo.commitctx directly
576 576 self.commitctx = self.kwcommitctx
577 577 try:
578 578 return super(kwrepo, self).commit(*args, **opts)
579 579 finally:
580 580 del self.commitctx
581 581
582 582 def kwcommitctx(self, ctx, error=False):
583 583 n = super(kwrepo, self).commitctx(ctx, error)
584 584 # no lock needed, only called from repo.commit() which already locks
585 585 if not kwt.record:
586 586 restrict = kwt.restrict
587 587 kwt.restrict = True
588 588 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
589 589 False, True)
590 590 kwt.restrict = restrict
591 591 return n
592 592
593 593 def rollback(self, dryrun=False, force=False):
594 594 wlock = self.wlock()
595 595 try:
596 596 if not dryrun:
597 597 changed = self['.'].files()
598 598 ret = super(kwrepo, self).rollback(dryrun, force)
599 599 if not dryrun:
600 600 ctx = self['.']
601 601 modified, added = _preselect(self[None].status(), changed)
602 602 kwt.overwrite(ctx, modified, True, True)
603 603 kwt.overwrite(ctx, added, True, False)
604 604 return ret
605 605 finally:
606 606 wlock.release()
607 607
608 608 # monkeypatches
609 609 def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None):
610 610 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
611 611 rejects or conflicts due to expanded keywords in working dir.'''
612 612 orig(self, ui, gp, backend, store, eolmode)
613 613 # shrink keywords read from working dir
614 614 self.lines = kwt.shrinklines(self.fname, self.lines)
615 615
616 616 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
617 617 opts=None, prefix=''):
618 618 '''Monkeypatch patch.diff to avoid expansion.'''
619 619 kwt.restrict = True
620 620 return orig(repo, node1, node2, match, changes, opts, prefix)
621 621
622 622 def kwweb_skip(orig, web, req, tmpl):
623 623 '''Wraps webcommands.x turning off keyword expansion.'''
624 624 kwt.match = util.never
625 625 return orig(web, req, tmpl)
626 626
627 627 def kw_copy(orig, ui, repo, pats, opts, rename=False):
628 628 '''Wraps cmdutil.copy so that copy/rename destinations do not
629 629 contain expanded keywords.
630 630 Note that the source of a regular file destination may also be a
631 631 symlink:
632 632 hg cp sym x -> x is symlink
633 633 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
634 634 For the latter we have to follow the symlink to find out whether its
635 635 target is configured for expansion and we therefore must unexpand the
636 636 keywords in the destination.'''
637 637 orig(ui, repo, pats, opts, rename)
638 638 if opts.get('dry_run'):
639 639 return
640 640 wctx = repo[None]
641 641 cwd = repo.getcwd()
642 642
643 643 def haskwsource(dest):
644 644 '''Returns true if dest is a regular file and configured for
645 645 expansion or a symlink which points to a file configured for
646 646 expansion. '''
647 647 source = repo.dirstate.copied(dest)
648 648 if 'l' in wctx.flags(source):
649 649 source = scmutil.canonpath(repo.root, cwd,
650 650 os.path.realpath(source))
651 651 return kwt.match(source)
652 652
653 653 candidates = [f for f in repo.dirstate.copies() if
654 654 not 'l' in wctx.flags(f) and haskwsource(f)]
655 655 kwt.overwrite(wctx, candidates, False, False)
656 656
657 657 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
658 658 '''Wraps record.dorecord expanding keywords after recording.'''
659 659 wlock = repo.wlock()
660 660 try:
661 661 # record returns 0 even when nothing has changed
662 662 # therefore compare nodes before and after
663 663 kwt.record = True
664 664 ctx = repo['.']
665 665 wstatus = repo[None].status()
666 666 ret = orig(ui, repo, commitfunc, *pats, **opts)
667 667 recctx = repo['.']
668 668 if ctx != recctx:
669 669 modified, added = _preselect(wstatus, recctx.files())
670 670 kwt.restrict = False
671 671 kwt.overwrite(recctx, modified, False, True)
672 672 kwt.overwrite(recctx, added, False, True, True)
673 673 kwt.restrict = True
674 674 return ret
675 675 finally:
676 676 wlock.release()
677 677
678 678 def kwfilectx_cmp(orig, self, fctx):
679 679 # keyword affects data size, comparing wdir and filelog size does
680 680 # not make sense
681 681 if (fctx._filerev is None and
682 682 (self._repo._encodefilterpats or
683 683 kwt.match(fctx.path()) and not 'l' in fctx.flags()) or
684 684 self.size() == fctx.size()):
685 685 return self._filelog.cmp(self._filenode, fctx.data())
686 686 return True
687 687
688 688 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
689 689 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
690 690 extensions.wrapfunction(patch, 'diff', kw_diff)
691 691 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
692 692 for c in 'annotate changeset rev filediff diff'.split():
693 693 extensions.wrapfunction(webcommands, c, kwweb_skip)
694 694 for name in recordextensions.split():
695 695 try:
696 696 record = extensions.find(name)
697 697 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
698 698 except KeyError:
699 699 pass
700 700
701 701 repo.__class__ = kwrepo
General Comments 0
You need to be logged in to leave comments. Login now