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