comparison hgext/keyword.py @ 43076:2372284d9457

formatting: blacken the codebase This is using my patch to black (https://github.com/psf/black/pull/826) so we don't un-wrap collection literals. Done with: hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S # skip-blame mass-reformatting only # no-check-commit reformats foo_bar functions Differential Revision: https://phab.mercurial-scm.org/D6971
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:45:02 -0400
parents d98ec36be808
children 687b865b95ad
comparison
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
123 # be specifying the version(s) of Mercurial they are tested with, or 123 # be specifying the version(s) of Mercurial they are tested with, or
124 # leave the attribute unspecified. 124 # leave the attribute unspecified.
125 testedwith = 'ships-with-hg-core' 125 testedwith = 'ships-with-hg-core'
126 126
127 # hg commands that do not act on keywords 127 # hg commands that do not act on keywords
128 nokwcommands = ('add addremove annotate bundle export grep incoming init log' 128 nokwcommands = (
129 ' outgoing push tip verify convert email glog') 129 'add addremove annotate bundle export grep incoming init log'
130 ' outgoing push tip verify convert email glog'
131 )
130 132
131 # webcommands that do not act on keywords 133 # webcommands that do not act on keywords
132 nokwwebcommands = ('annotate changeset rev filediff diff comparison') 134 nokwwebcommands = 'annotate changeset rev filediff diff comparison'
133 135
134 # hg commands that trigger expansion only when writing to working dir, 136 # hg commands that trigger expansion only when writing to working dir,
135 # not when reading filelog, and unexpand when reading from working dir 137 # not when reading filelog, and unexpand when reading from working dir
136 restricted = ('merge kwexpand kwshrink record qrecord resolve transplant' 138 restricted = (
137 ' unshelve rebase graft backout histedit fetch') 139 'merge kwexpand kwshrink record qrecord resolve transplant'
140 ' unshelve rebase graft backout histedit fetch'
141 )
138 142
139 # names of extensions using dorecord 143 # names of extensions using dorecord
140 recordextensions = 'record' 144 recordextensions = 'record'
141 145
142 colortable = { 146 colortable = {
143 'kwfiles.enabled': 'green bold', 147 'kwfiles.enabled': 'green bold',
144 'kwfiles.deleted': 'cyan bold underline', 148 'kwfiles.deleted': 'cyan bold underline',
145 'kwfiles.enabledunknown': 'green', 149 'kwfiles.enabledunknown': 'green',
146 'kwfiles.ignored': 'bold', 150 'kwfiles.ignored': 'bold',
147 'kwfiles.ignoredunknown': 'none' 151 'kwfiles.ignoredunknown': 'none',
148 } 152 }
149 153
150 templatefilter = registrar.templatefilter() 154 templatefilter = registrar.templatefilter()
151 155
152 configtable = {} 156 configtable = {}
153 configitem = registrar.configitem(configtable) 157 configitem = registrar.configitem(configtable)
154 158
155 configitem('keywordset', 'svn', 159 configitem(
156 default=False, 160 'keywordset', 'svn', default=False,
157 ) 161 )
158 # date like in cvs' $Date 162 # date like in cvs' $Date
159 @templatefilter('utcdate', intype=templateutil.date) 163 @templatefilter('utcdate', intype=templateutil.date)
160 def utcdate(date): 164 def utcdate(date):
161 '''Date. Returns a UTC-date in this format: "2009/08/18 11:00:13". 165 '''Date. Returns a UTC-date in this format: "2009/08/18 11:00:13".
162 ''' 166 '''
163 dateformat = '%Y/%m/%d %H:%M:%S' 167 dateformat = '%Y/%m/%d %H:%M:%S'
164 return dateutil.datestr((date[0], 0), dateformat) 168 return dateutil.datestr((date[0], 0), dateformat)
169
170
165 # date like in svn's $Date 171 # date like in svn's $Date
166 @templatefilter('svnisodate', intype=templateutil.date) 172 @templatefilter('svnisodate', intype=templateutil.date)
167 def svnisodate(date): 173 def svnisodate(date):
168 '''Date. Returns a date in this format: "2009-08-18 13:00:13 174 '''Date. Returns a date in this format: "2009-08-18 13:00:13
169 +0200 (Tue, 18 Aug 2009)". 175 +0200 (Tue, 18 Aug 2009)".
170 ''' 176 '''
171 return dateutil.datestr(date, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)') 177 return dateutil.datestr(date, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
178
179
172 # date like in svn's $Id 180 # date like in svn's $Id
173 @templatefilter('svnutcdate', intype=templateutil.date) 181 @templatefilter('svnutcdate', intype=templateutil.date)
174 def svnutcdate(date): 182 def svnutcdate(date):
175 '''Date. Returns a UTC-date in this format: "2009-08-18 183 '''Date. Returns a UTC-date in this format: "2009-08-18
176 11:00:13Z". 184 11:00:13Z".
177 ''' 185 '''
178 dateformat = '%Y-%m-%d %H:%M:%SZ' 186 dateformat = '%Y-%m-%d %H:%M:%SZ'
179 return dateutil.datestr((date[0], 0), dateformat) 187 return dateutil.datestr((date[0], 0), dateformat)
180 188
189
181 # make keyword tools accessible 190 # make keyword tools accessible
182 kwtools = {'hgcmd': ''} 191 kwtools = {'hgcmd': ''}
192
183 193
184 def _defaultkwmaps(ui): 194 def _defaultkwmaps(ui):
185 '''Returns default keywordmaps according to keywordset configuration.''' 195 '''Returns default keywordmaps according to keywordset configuration.'''
186 templates = { 196 templates = {
187 'Revision': '{node|short}', 197 'Revision': '{node|short}',
188 'Author': '{author|user}', 198 'Author': '{author|user}',
189 } 199 }
190 kwsets = ({ 200 kwsets = (
191 'Date': '{date|utcdate}', 201 {
192 'RCSfile': '{file|basename},v', 202 'Date': '{date|utcdate}',
193 'RCSFile': '{file|basename},v', # kept for backwards compatibility 203 'RCSfile': '{file|basename},v',
194 # with hg-keyword 204 'RCSFile': '{file|basename},v', # kept for backwards compatibility
195 'Source': '{root}/{file},v', 205 # with hg-keyword
196 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}', 206 'Source': '{root}/{file},v',
197 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}', 207 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
198 }, { 208 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
199 'Date': '{date|svnisodate}', 209 },
200 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}', 210 {
201 'LastChangedRevision': '{node|short}', 211 'Date': '{date|svnisodate}',
202 'LastChangedBy': '{author|user}', 212 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
203 'LastChangedDate': '{date|svnisodate}', 213 'LastChangedRevision': '{node|short}',
204 }) 214 'LastChangedBy': '{author|user}',
215 'LastChangedDate': '{date|svnisodate}',
216 },
217 )
205 templates.update(kwsets[ui.configbool('keywordset', 'svn')]) 218 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
206 return templates 219 return templates
220
207 221
208 def _shrinktext(text, subfunc): 222 def _shrinktext(text, subfunc):
209 '''Helper for keyword expansion removal in text. 223 '''Helper for keyword expansion removal in text.
210 Depending on subfunc also returns number of substitutions.''' 224 Depending on subfunc also returns number of substitutions.'''
211 return subfunc(br'$\1$', text) 225 return subfunc(br'$\1$', text)
226
212 227
213 def _preselect(wstatus, changed): 228 def _preselect(wstatus, changed):
214 '''Retrieves modified and added files from a working directory state 229 '''Retrieves modified and added files from a working directory state
215 and returns the subset of each contained in given changed files 230 and returns the subset of each contained in given changed files
216 retrieved from a change context.''' 231 retrieved from a change context.'''
231 self.match = match.match(repo.root, '', [], inc, exc) 246 self.match = match.match(repo.root, '', [], inc, exc)
232 self.restrict = kwtools['hgcmd'] in restricted.split() 247 self.restrict = kwtools['hgcmd'] in restricted.split()
233 self.postcommit = False 248 self.postcommit = False
234 249
235 kwmaps = self.ui.configitems('keywordmaps') 250 kwmaps = self.ui.configitems('keywordmaps')
236 if kwmaps: # override default templates 251 if kwmaps: # override default templates
237 self.templates = dict(kwmaps) 252 self.templates = dict(kwmaps)
238 else: 253 else:
239 self.templates = _defaultkwmaps(self.ui) 254 self.templates = _defaultkwmaps(self.ui)
240 255
241 @property 256 @property
257 '''Returns regex for expanded keywords.''' 272 '''Returns regex for expanded keywords.'''
258 return re.compile(br'\$(%s): [^$\n\r]*? \$' % self.escape) 273 return re.compile(br'\$(%s): [^$\n\r]*? \$' % self.escape)
259 274
260 def substitute(self, data, path, ctx, subfunc): 275 def substitute(self, data, path, ctx, subfunc):
261 '''Replaces keywords in data with expanded template.''' 276 '''Replaces keywords in data with expanded template.'''
277
262 def kwsub(mobj): 278 def kwsub(mobj):
263 kw = mobj.group(1) 279 kw = mobj.group(1)
264 ct = logcmdutil.maketemplater(self.ui, self.repo, 280 ct = logcmdutil.maketemplater(
265 self.templates[kw]) 281 self.ui, self.repo, self.templates[kw]
282 )
266 self.ui.pushbuffer() 283 self.ui.pushbuffer()
267 ct.show(ctx, root=self.repo.root, file=path) 284 ct.show(ctx, root=self.repo.root, file=path)
268 ekw = templatefilters.firstline(self.ui.popbuffer()) 285 ekw = templatefilters.firstline(self.ui.popbuffer())
269 return '$%s: %s $' % (kw, ekw) 286 return '$%s: %s $' % (kw, ekw)
287
270 return subfunc(kwsub, data) 288 return subfunc(kwsub, data)
271 289
272 def linkctx(self, path, fileid): 290 def linkctx(self, path, fileid):
273 '''Similar to filelog.linkrev, but returns a changectx.''' 291 '''Similar to filelog.linkrev, but returns a changectx.'''
274 return self.repo.filectx(path, fileid=fileid).changectx() 292 return self.repo.filectx(path, fileid=fileid).changectx()
275 293
276 def expand(self, path, node, data): 294 def expand(self, path, node, data):
277 '''Returns data with keywords expanded.''' 295 '''Returns data with keywords expanded.'''
278 if (not self.restrict and self.match(path) 296 if (
279 and not stringutil.binary(data)): 297 not self.restrict
298 and self.match(path)
299 and not stringutil.binary(data)
300 ):
280 ctx = self.linkctx(path, node) 301 ctx = self.linkctx(path, node)
281 return self.substitute(data, path, ctx, self.rekw.sub) 302 return self.substitute(data, path, ctx, self.rekw.sub)
282 return data 303 return data
283 304
284 def iskwfile(self, cand, ctx): 305 def iskwfile(self, cand, ctx):
286 expansion but are not symbolic links.''' 307 expansion but are not symbolic links.'''
287 return [f for f in cand if self.match(f) and 'l' not in ctx.flags(f)] 308 return [f for f in cand if self.match(f) and 'l' not in ctx.flags(f)]
288 309
289 def overwrite(self, ctx, candidates, lookup, expand, rekw=False): 310 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
290 '''Overwrites selected files expanding/shrinking keywords.''' 311 '''Overwrites selected files expanding/shrinking keywords.'''
291 if self.restrict or lookup or self.postcommit: # exclude kw_copy 312 if self.restrict or lookup or self.postcommit: # exclude kw_copy
292 candidates = self.iskwfile(candidates, ctx) 313 candidates = self.iskwfile(candidates, ctx)
293 if not candidates: 314 if not candidates:
294 return 315 return
295 kwcmd = self.restrict and lookup # kwexpand/kwshrink 316 kwcmd = self.restrict and lookup # kwexpand/kwshrink
296 if self.restrict or expand and lookup: 317 if self.restrict or expand and lookup:
297 mf = ctx.manifest() 318 mf = ctx.manifest()
298 if self.restrict or rekw: 319 if self.restrict or rekw:
299 re_kw = self.rekw 320 re_kw = self.rekw
300 else: 321 else:
356 keyword substitutions removed.''' 377 keyword substitutions removed.'''
357 if self.restrict: 378 if self.restrict:
358 return self.shrink(fname, data) 379 return self.shrink(fname, data)
359 return data 380 return data
360 381
382
361 class kwfilelog(filelog.filelog): 383 class kwfilelog(filelog.filelog):
362 ''' 384 '''
363 Subclass of filelog to hook into its read, add, cmp methods. 385 Subclass of filelog to hook into its read, add, cmp methods.
364 Keywords are "stored" unexpanded, and processed on reading. 386 Keywords are "stored" unexpanded, and processed on reading.
365 ''' 387 '''
388
366 def __init__(self, opener, kwt, path): 389 def __init__(self, opener, kwt, path):
367 super(kwfilelog, self).__init__(opener, path) 390 super(kwfilelog, self).__init__(opener, path)
368 self.kwt = kwt 391 self.kwt = kwt
369 self.path = path 392 self.path = path
370 393
383 def cmp(self, node, text): 406 def cmp(self, node, text):
384 '''Removes keyword substitutions for comparison.''' 407 '''Removes keyword substitutions for comparison.'''
385 text = self.kwt.shrink(self.path, text) 408 text = self.kwt.shrink(self.path, text)
386 return super(kwfilelog, self).cmp(node, text) 409 return super(kwfilelog, self).cmp(node, text)
387 410
411
388 def _status(ui, repo, wctx, kwt, *pats, **opts): 412 def _status(ui, repo, wctx, kwt, *pats, **opts):
389 '''Bails out if [keyword] configuration is not active. 413 '''Bails out if [keyword] configuration is not active.
390 Returns status of working directory.''' 414 Returns status of working directory.'''
391 if kwt: 415 if kwt:
392 opts = pycompat.byteskwargs(opts) 416 opts = pycompat.byteskwargs(opts)
393 return repo.status(match=scmutil.match(wctx, pats, opts), clean=True, 417 return repo.status(
394 unknown=opts.get('unknown') or opts.get('all')) 418 match=scmutil.match(wctx, pats, opts),
419 clean=True,
420 unknown=opts.get('unknown') or opts.get('all'),
421 )
395 if ui.configitems('keyword'): 422 if ui.configitems('keyword'):
396 raise error.Abort(_('[keyword] patterns cannot match')) 423 raise error.Abort(_('[keyword] patterns cannot match'))
397 raise error.Abort(_('no [keyword] patterns configured')) 424 raise error.Abort(_('no [keyword] patterns configured'))
425
398 426
399 def _kwfwrite(ui, repo, expand, *pats, **opts): 427 def _kwfwrite(ui, repo, expand, *pats, **opts):
400 '''Selects files and passes them to kwtemplater.overwrite.''' 428 '''Selects files and passes them to kwtemplater.overwrite.'''
401 wctx = repo[None] 429 wctx = repo[None]
402 if len(wctx.parents()) > 1: 430 if len(wctx.parents()) > 1:
406 status = _status(ui, repo, wctx, kwt, *pats, **opts) 434 status = _status(ui, repo, wctx, kwt, *pats, **opts)
407 if status.modified or status.added or status.removed or status.deleted: 435 if status.modified or status.added or status.removed or status.deleted:
408 raise error.Abort(_('outstanding uncommitted changes')) 436 raise error.Abort(_('outstanding uncommitted changes'))
409 kwt.overwrite(wctx, status.clean, True, expand) 437 kwt.overwrite(wctx, status.clean, True, expand)
410 438
411 @command('kwdemo', 439
412 [('d', 'default', None, _('show default keyword template maps')), 440 @command(
413 ('f', 'rcfile', '', 441 'kwdemo',
414 _('read maps from rcfile'), _('FILE'))], 442 [
415 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'), 443 ('d', 'default', None, _('show default keyword template maps')),
416 optionalrepo=True) 444 ('f', 'rcfile', '', _('read maps from rcfile'), _('FILE')),
445 ],
446 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'),
447 optionalrepo=True,
448 )
417 def demo(ui, repo, *args, **opts): 449 def demo(ui, repo, *args, **opts):
418 '''print [keywordmaps] configuration and an expansion example 450 '''print [keywordmaps] configuration and an expansion example
419 451
420 Show current, custom, or default keyword template maps and their 452 Show current, custom, or default keyword template maps and their
421 expansions. 453 expansions.
425 457
426 Use -d/--default to disable current configuration. 458 Use -d/--default to disable current configuration.
427 459
428 See :hg:`help templates` for information on templates and filters. 460 See :hg:`help templates` for information on templates and filters.
429 ''' 461 '''
462
430 def demoitems(section, items): 463 def demoitems(section, items):
431 ui.write('[%s]\n' % section) 464 ui.write('[%s]\n' % section)
432 for k, v in sorted(items): 465 for k, v in sorted(items):
433 if isinstance(v, bool): 466 if isinstance(v, bool):
434 v = stringutil.pprint(v) 467 v = stringutil.pprint(v)
482 else: 515 else:
483 kwmaps = _defaultkwmaps(ui) 516 kwmaps = _defaultkwmaps(ui)
484 517
485 uisetup(ui) 518 uisetup(ui)
486 reposetup(ui, repo) 519 reposetup(ui, repo)
487 ui.write(('[extensions]\nkeyword =\n')) 520 ui.write('[extensions]\nkeyword =\n')
488 demoitems('keyword', ui.configitems('keyword')) 521 demoitems('keyword', ui.configitems('keyword'))
489 demoitems('keywordset', ui.configitems('keywordset')) 522 demoitems('keywordset', ui.configitems('keywordset'))
490 demoitems('keywordmaps', kwmaps.iteritems()) 523 demoitems('keywordmaps', kwmaps.iteritems())
491 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n' 524 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
492 repo.wvfs.write(fn, keywords) 525 repo.wvfs.write(fn, keywords)
503 repo.commit(text=msg) 536 repo.commit(text=msg)
504 ui.status(_('\n\tkeywords expanded\n')) 537 ui.status(_('\n\tkeywords expanded\n'))
505 ui.write(repo.wread(fn)) 538 ui.write(repo.wread(fn))
506 repo.wvfs.rmtree(repo.root) 539 repo.wvfs.rmtree(repo.root)
507 540
508 @command('kwexpand', 541
542 @command(
543 'kwexpand',
509 cmdutil.walkopts, 544 cmdutil.walkopts,
510 _('hg kwexpand [OPTION]... [FILE]...'), 545 _('hg kwexpand [OPTION]... [FILE]...'),
511 inferrepo=True) 546 inferrepo=True,
547 )
512 def expand(ui, repo, *pats, **opts): 548 def expand(ui, repo, *pats, **opts):
513 '''expand keywords in the working directory 549 '''expand keywords in the working directory
514 550
515 Run after (re)enabling keyword expansion. 551 Run after (re)enabling keyword expansion.
516 552
517 kwexpand refuses to run if given files contain local changes. 553 kwexpand refuses to run if given files contain local changes.
518 ''' 554 '''
519 # 3rd argument sets expansion to True 555 # 3rd argument sets expansion to True
520 _kwfwrite(ui, repo, True, *pats, **opts) 556 _kwfwrite(ui, repo, True, *pats, **opts)
521 557
522 @command('kwfiles', 558
523 [('A', 'all', None, _('show keyword status flags of all files')), 559 @command(
524 ('i', 'ignore', None, _('show files excluded from expansion')), 560 'kwfiles',
525 ('u', 'unknown', None, _('only show unknown (not tracked) files')), 561 [
526 ] + cmdutil.walkopts, 562 ('A', 'all', None, _('show keyword status flags of all files')),
527 _('hg kwfiles [OPTION]... [FILE]...'), 563 ('i', 'ignore', None, _('show files excluded from expansion')),
528 inferrepo=True) 564 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
565 ]
566 + cmdutil.walkopts,
567 _('hg kwfiles [OPTION]... [FILE]...'),
568 inferrepo=True,
569 )
529 def files(ui, repo, *pats, **opts): 570 def files(ui, repo, *pats, **opts):
530 '''show files configured for keyword expansion 571 '''show files configured for keyword expansion
531 572
532 List which files in the working directory are matched by the 573 List which files in the working directory are matched by the
533 [keyword] configuration patterns. 574 [keyword] configuration patterns.
564 if not opts.get('ignore') or opts.get('all'): 605 if not opts.get('ignore') or opts.get('all'):
565 showfiles = kwfiles, kwdeleted, kwunknown 606 showfiles = kwfiles, kwdeleted, kwunknown
566 else: 607 else:
567 showfiles = [], [], [] 608 showfiles = [], [], []
568 if opts.get('all') or opts.get('ignore'): 609 if opts.get('all') or opts.get('ignore'):
569 showfiles += ([f for f in files if f not in kwfiles], 610 showfiles += (
570 [f for f in status.unknown if f not in kwunknown]) 611 [f for f in files if f not in kwfiles],
612 [f for f in status.unknown if f not in kwunknown],
613 )
571 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split() 614 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
572 kwstates = zip(kwlabels, pycompat.bytestr('K!kIi'), showfiles) 615 kwstates = zip(kwlabels, pycompat.bytestr('K!kIi'), showfiles)
573 fm = ui.formatter('kwfiles', opts) 616 fm = ui.formatter('kwfiles', opts)
574 fmt = '%.0s%s\n' 617 fmt = '%.0s%s\n'
575 if opts.get('all') or ui.verbose: 618 if opts.get('all') or ui.verbose:
580 fm.startitem() 623 fm.startitem()
581 fm.data(kwstatus=char, path=f) 624 fm.data(kwstatus=char, path=f)
582 fm.plain(fmt % (char, repo.pathto(f, cwd)), label=label) 625 fm.plain(fmt % (char, repo.pathto(f, cwd)), label=label)
583 fm.end() 626 fm.end()
584 627
585 @command('kwshrink', 628
629 @command(
630 'kwshrink',
586 cmdutil.walkopts, 631 cmdutil.walkopts,
587 _('hg kwshrink [OPTION]... [FILE]...'), 632 _('hg kwshrink [OPTION]... [FILE]...'),
588 inferrepo=True) 633 inferrepo=True,
634 )
589 def shrink(ui, repo, *pats, **opts): 635 def shrink(ui, repo, *pats, **opts):
590 '''revert expanded keywords in the working directory 636 '''revert expanded keywords in the working directory
591 637
592 Must be run before changing/disabling active keywords. 638 Must be run before changing/disabling active keywords.
593 639
594 kwshrink refuses to run if given files contain local changes. 640 kwshrink refuses to run if given files contain local changes.
595 ''' 641 '''
596 # 3rd argument sets expansion to False 642 # 3rd argument sets expansion to False
597 _kwfwrite(ui, repo, False, *pats, **opts) 643 _kwfwrite(ui, repo, False, *pats, **opts)
598 644
645
599 # monkeypatches 646 # monkeypatches
647
600 648
601 def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None): 649 def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None):
602 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid 650 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
603 rejects or conflicts due to expanded keywords in working dir.''' 651 rejects or conflicts due to expanded keywords in working dir.'''
604 orig(self, ui, gp, backend, store, eolmode) 652 orig(self, ui, gp, backend, store, eolmode)
605 kwt = getattr(getattr(backend, 'repo', None), '_keywordkwt', None) 653 kwt = getattr(getattr(backend, 'repo', None), '_keywordkwt', None)
606 if kwt: 654 if kwt:
607 # shrink keywords read from working dir 655 # shrink keywords read from working dir
608 self.lines = kwt.shrinklines(self.fname, self.lines) 656 self.lines = kwt.shrinklines(self.fname, self.lines)
657
609 658
610 def kwdiff(orig, repo, *args, **kwargs): 659 def kwdiff(orig, repo, *args, **kwargs):
611 '''Monkeypatch patch.diff to avoid expansion.''' 660 '''Monkeypatch patch.diff to avoid expansion.'''
612 kwt = getattr(repo, '_keywordkwt', None) 661 kwt = getattr(repo, '_keywordkwt', None)
613 if kwt: 662 if kwt:
618 yield chunk 667 yield chunk
619 finally: 668 finally:
620 if kwt: 669 if kwt:
621 kwt.restrict = restrict 670 kwt.restrict = restrict
622 671
672
623 def kwweb_skip(orig, web): 673 def kwweb_skip(orig, web):
624 '''Wraps webcommands.x turning off keyword expansion.''' 674 '''Wraps webcommands.x turning off keyword expansion.'''
625 kwt = getattr(web.repo, '_keywordkwt', None) 675 kwt = getattr(web.repo, '_keywordkwt', None)
626 if kwt: 676 if kwt:
627 origmatch = kwt.match 677 origmatch = kwt.match
630 for chunk in orig(web): 680 for chunk in orig(web):
631 yield chunk 681 yield chunk
632 finally: 682 finally:
633 if kwt: 683 if kwt:
634 kwt.match = origmatch 684 kwt.match = origmatch
685
635 686
636 def kw_amend(orig, ui, repo, old, extra, pats, opts): 687 def kw_amend(orig, ui, repo, old, extra, pats, opts):
637 '''Wraps cmdutil.amend expanding keywords after amend.''' 688 '''Wraps cmdutil.amend expanding keywords after amend.'''
638 kwt = getattr(repo, '_keywordkwt', None) 689 kwt = getattr(repo, '_keywordkwt', None)
639 if kwt is None: 690 if kwt is None:
645 ctx = repo[newid] 696 ctx = repo[newid]
646 kwt.restrict = True 697 kwt.restrict = True
647 kwt.overwrite(ctx, ctx.files(), False, True) 698 kwt.overwrite(ctx, ctx.files(), False, True)
648 kwt.restrict = False 699 kwt.restrict = False
649 return newid 700 return newid
701
650 702
651 def kw_copy(orig, ui, repo, pats, opts, rename=False): 703 def kw_copy(orig, ui, repo, pats, opts, rename=False):
652 '''Wraps cmdutil.copy so that copy/rename destinations do not 704 '''Wraps cmdutil.copy so that copy/rename destinations do not
653 contain expanded keywords. 705 contain expanded keywords.
654 Note that the source of a regular file destination may also be a 706 Note that the source of a regular file destination may also be a
672 '''Returns true if dest is a regular file and configured for 724 '''Returns true if dest is a regular file and configured for
673 expansion or a symlink which points to a file configured for 725 expansion or a symlink which points to a file configured for
674 expansion. ''' 726 expansion. '''
675 source = repo.dirstate.copied(dest) 727 source = repo.dirstate.copied(dest)
676 if 'l' in wctx.flags(source): 728 if 'l' in wctx.flags(source):
677 source = pathutil.canonpath(repo.root, cwd, 729 source = pathutil.canonpath(
678 os.path.realpath(source)) 730 repo.root, cwd, os.path.realpath(source)
731 )
679 return kwt.match(source) 732 return kwt.match(source)
680 733
681 candidates = [f for f in repo.dirstate.copies() if 734 candidates = [
682 'l' not in wctx.flags(f) and haskwsource(f)] 735 f
736 for f in repo.dirstate.copies()
737 if 'l' not in wctx.flags(f) and haskwsource(f)
738 ]
683 kwt.overwrite(wctx, candidates, False, False) 739 kwt.overwrite(wctx, candidates, False, False)
740
684 741
685 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts): 742 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
686 '''Wraps record.dorecord expanding keywords after recording.''' 743 '''Wraps record.dorecord expanding keywords after recording.'''
687 kwt = getattr(repo, '_keywordkwt', None) 744 kwt = getattr(repo, '_keywordkwt', None)
688 if kwt is None: 745 if kwt is None:
701 kwt.overwrite(recctx, modified, False, True) 758 kwt.overwrite(recctx, modified, False, True)
702 kwt.overwrite(recctx, added, False, True, True) 759 kwt.overwrite(recctx, added, False, True, True)
703 kwt.restrict = True 760 kwt.restrict = True
704 return ret 761 return ret
705 762
763
706 def kwfilectx_cmp(orig, self, fctx): 764 def kwfilectx_cmp(orig, self, fctx):
707 if fctx._customcmp: 765 if fctx._customcmp:
708 return fctx.cmp(self) 766 return fctx.cmp(self)
709 kwt = getattr(self._repo, '_keywordkwt', None) 767 kwt = getattr(self._repo, '_keywordkwt', None)
710 if kwt is None: 768 if kwt is None:
711 return orig(self, fctx) 769 return orig(self, fctx)
712 # keyword affects data size, comparing wdir and filelog size does 770 # keyword affects data size, comparing wdir and filelog size does
713 # not make sense 771 # not make sense
714 if (fctx._filenode is None and 772 if (
715 (self._repo._encodefilterpats or 773 fctx._filenode is None
716 kwt.match(fctx.path()) and 'l' not in fctx.flags() or 774 and (
717 self.size() - 4 == fctx.size()) or 775 self._repo._encodefilterpats
718 self.size() == fctx.size()): 776 or kwt.match(fctx.path())
777 and 'l' not in fctx.flags()
778 or self.size() - 4 == fctx.size()
779 )
780 or self.size() == fctx.size()
781 ):
719 return self._filelog.cmp(self._filenode, fctx.data()) 782 return self._filelog.cmp(self._filenode, fctx.data())
720 return True 783 return True
784
721 785
722 def uisetup(ui): 786 def uisetup(ui):
723 ''' Monkeypatches dispatch._parse to retrieve user command. 787 ''' Monkeypatches dispatch._parse to retrieve user command.
724 Overrides file method to return kwfilelog instead of filelog 788 Overrides file method to return kwfilelog instead of filelog
725 if file matches user configuration. 789 if file matches user configuration.
742 extensions.wrapfunction(cmdutil, 'copy', kw_copy) 806 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
743 extensions.wrapfunction(cmdutil, 'dorecord', kw_dorecord) 807 extensions.wrapfunction(cmdutil, 'dorecord', kw_dorecord)
744 for c in nokwwebcommands.split(): 808 for c in nokwwebcommands.split():
745 extensions.wrapfunction(webcommands, c, kwweb_skip) 809 extensions.wrapfunction(webcommands, c, kwweb_skip)
746 810
811
747 def reposetup(ui, repo): 812 def reposetup(ui, repo):
748 '''Sets up repo as kwrepo for keyword substitution.''' 813 '''Sets up repo as kwrepo for keyword substitution.'''
749 814
750 try: 815 try:
751 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split() 816 if (
817 not repo.local()
818 or kwtools['hgcmd'] in nokwcommands.split()
752 or '.hg' in util.splitpath(repo.root) 819 or '.hg' in util.splitpath(repo.root)
753 or repo._url.startswith('bundle:')): 820 or repo._url.startswith('bundle:')
821 ):
754 return 822 return
755 except AttributeError: 823 except AttributeError:
756 pass 824 pass
757 825
758 inc, exc = [], ['.hg*'] 826 inc, exc = [], ['.hg*']
789 n = super(kwrepo, self).commitctx(ctx, error, origctx) 857 n = super(kwrepo, self).commitctx(ctx, error, origctx)
790 # no lock needed, only called from repo.commit() which already locks 858 # no lock needed, only called from repo.commit() which already locks
791 if not kwt.postcommit: 859 if not kwt.postcommit:
792 restrict = kwt.restrict 860 restrict = kwt.restrict
793 kwt.restrict = True 861 kwt.restrict = True
794 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()), 862 kwt.overwrite(
795 False, True) 863 self[n], sorted(ctx.added() + ctx.modified()), False, True
864 )
796 kwt.restrict = restrict 865 kwt.restrict = restrict
797 return n 866 return n
798 867
799 def rollback(self, dryrun=False, force=False): 868 def rollback(self, dryrun=False, force=False):
800 with self.wlock(): 869 with self.wlock():