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