comparison hgext/keyword.py @ 11045:eb67196d20fa

keyword: support (q)record Monkeypatch hgext.dorecord to trigger keyword expansion. Read data from working directory, not from filelog. Prevent keyword expansion from within record's commitfunc, thereby fixing a bug/inconsistency where files which are clean after recording were overwritten twice.
author Christian Ebert <blacktrash@gmx.net>
date Mon, 26 Apr 2010 04:01:07 +0200
parents 5ab414f71e87
children dc2f37864348
comparison
equal deleted inserted replaced
11044:5ab414f71e87 11045:eb67196d20fa
63 the risk of inadvertently storing expanded keywords in the change 63 the risk of inadvertently storing expanded keywords in the change
64 history. 64 history.
65 65
66 To force expansion after enabling it, or a configuration change, run 66 To force expansion after enabling it, or a configuration change, run
67 :hg:`kwexpand`. 67 :hg:`kwexpand`.
68
69 Also, when committing with the record extension or using mq's qrecord,
70 be aware that keywords cannot be updated. Again, run :hg:`kwexpand` on
71 the files in question to update keyword expansions after all changes
72 have been checked in.
73 68
74 Expansions spanning more than one line and incremental expansions, 69 Expansions spanning more than one line and incremental expansions,
75 like CVS' $Log$, are not supported. A keyword template map "Log = 70 like CVS' $Log$, are not supported. A keyword template map "Log =
76 {desc}" expands to the first line of the changeset description. 71 {desc}" expands to the first line of the changeset description.
77 ''' 72 '''
91 ' convert email glog') 86 ' convert email glog')
92 87
93 # hg commands that trigger expansion only when writing to working dir, 88 # hg commands that trigger expansion only when writing to working dir,
94 # not when reading filelog, and unexpand when reading from working dir 89 # not when reading filelog, and unexpand when reading from working dir
95 restricted = 'merge record qrecord resolve transplant' 90 restricted = 'merge record qrecord resolve transplant'
91
92 # commands using dorecord
93 recordcommands = 'record qrecord'
96 94
97 # provide cvs-like UTC date filter 95 # provide cvs-like UTC date filter
98 utcdate = lambda x: util.datestr((x[0], 0), '%Y/%m/%d %H:%M:%S') 96 utcdate = lambda x: util.datestr((x[0], 0), '%Y/%m/%d %H:%M:%S')
99 97
100 # make keyword tools accessible 98 # make keyword tools accessible
122 self.ui = ui 120 self.ui = ui
123 self.repo = repo 121 self.repo = repo
124 self.match = match.match(repo.root, '', [], 122 self.match = match.match(repo.root, '', [],
125 kwtools['inc'], kwtools['exc']) 123 kwtools['inc'], kwtools['exc'])
126 self.restrict = kwtools['hgcmd'] in restricted.split() 124 self.restrict = kwtools['hgcmd'] in restricted.split()
125 self.record = kwtools['hgcmd'] in recordcommands.split()
127 126
128 kwmaps = self.ui.configitems('keywordmaps') 127 kwmaps = self.ui.configitems('keywordmaps')
129 if kwmaps: # override default templates 128 if kwmaps: # override default templates
130 self.templates = dict((k, templater.parsestring(v, False)) 129 self.templates = dict((k, templater.parsestring(v, False))
131 for k, v in kwmaps) 130 for k, v in kwmaps)
159 '''Returns true if path matches [keyword] pattern 158 '''Returns true if path matches [keyword] pattern
160 and is not a symbolic link. 159 and is not a symbolic link.
161 Caveat: localrepository._link fails on Windows.''' 160 Caveat: localrepository._link fails on Windows.'''
162 return self.match(path) and not 'l' in flagfunc(path) 161 return self.match(path) and not 'l' in flagfunc(path)
163 162
164 def overwrite(self, node, expand, candidates): 163 def overwrite(self, node, expand, candidates, recctx=None):
165 '''Overwrites selected files expanding/shrinking keywords.''' 164 '''Overwrites selected files expanding/shrinking keywords.'''
166 ctx = self.repo[node] 165 if recctx is None:
166 ctx = self.repo[node]
167 else:
168 ctx = recctx
167 mf = ctx.manifest() 169 mf = ctx.manifest()
168 if node is not None: # commit 170 if node is not None: # commit, record
169 candidates = [f for f in ctx.files() if f in mf] 171 candidates = [f for f in ctx.files() if f in mf]
170 candidates = [f for f in candidates if self.iskwfile(f, ctx.flags)] 172 candidates = [f for f in candidates if self.iskwfile(f, ctx.flags)]
171 if candidates: 173 if candidates:
172 self.restrict = True # do not expand when reading 174 self.restrict = True # do not expand when reading
173 msg = (expand and _('overwriting %s expanding keywords\n') 175 msg = (expand and _('overwriting %s expanding keywords\n')
174 or _('overwriting %s shrinking keywords\n')) 176 or _('overwriting %s shrinking keywords\n'))
175 for f in candidates: 177 for f in candidates:
176 fp = self.repo.file(f) 178 if recctx is None:
177 data = fp.read(mf[f]) 179 data = self.repo.file(f).read(mf[f])
180 else:
181 data = self.repo.wread(f)
178 if util.binary(data): 182 if util.binary(data):
179 continue 183 continue
180 if expand: 184 if expand:
181 if node is None: 185 if node is None:
182 ctx = self.repo.filectx(f, fileid=mf[f]).changectx() 186 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
463 del self.commitctx 467 del self.commitctx
464 468
465 def kwcommitctx(self, ctx, error=False): 469 def kwcommitctx(self, ctx, error=False):
466 n = super(kwrepo, self).commitctx(ctx, error) 470 n = super(kwrepo, self).commitctx(ctx, error)
467 # no lock needed, only called from repo.commit() which already locks 471 # no lock needed, only called from repo.commit() which already locks
468 kwt.overwrite(n, True, None) 472 if not kwt.record:
473 kwt.overwrite(n, True, None)
469 return n 474 return n
470 475
471 # monkeypatches 476 # monkeypatches
472 def kwpatchfile_init(orig, self, ui, fname, opener, 477 def kwpatchfile_init(orig, self, ui, fname, opener,
473 missing=False, eolmode=None): 478 missing=False, eolmode=None):
490 def kwweb_skip(orig, web, req, tmpl): 495 def kwweb_skip(orig, web, req, tmpl):
491 '''Wraps webcommands.x turning off keyword expansion.''' 496 '''Wraps webcommands.x turning off keyword expansion.'''
492 kwt.match = util.never 497 kwt.match = util.never
493 return orig(web, req, tmpl) 498 return orig(web, req, tmpl)
494 499
500 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
501 '''Wraps record.dorecord expanding keywords after recording.'''
502 wlock = repo.wlock()
503 try:
504 # record returns 0 even when nothing has changed
505 # therefore compare nodes before and after
506 ctx = repo['.']
507 ret = orig(ui, repo, commitfunc, *pats, **opts)
508 recctx = repo['.']
509 if ctx != recctx:
510 kwt.overwrite('.', True, None, recctx)
511 return ret
512 finally:
513 wlock.release()
514
495 repo.__class__ = kwrepo 515 repo.__class__ = kwrepo
496 516
497 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init) 517 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
498 if not kwt.restrict: 518 if not kwt.restrict:
499 extensions.wrapfunction(patch, 'diff', kw_diff) 519 extensions.wrapfunction(patch, 'diff', kw_diff)
500 for c in 'annotate changeset rev filediff diff'.split(): 520 for c in 'annotate changeset rev filediff diff'.split():
501 extensions.wrapfunction(webcommands, c, kwweb_skip) 521 extensions.wrapfunction(webcommands, c, kwweb_skip)
522 try:
523 record = extensions.find('record')
524 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
525 except KeyError:
526 pass
502 527
503 cmdtable = { 528 cmdtable = {
504 'kwdemo': 529 'kwdemo':
505 (demo, 530 (demo,
506 [('d', 'default', None, _('show default keyword template maps')), 531 [('d', 'default', None, _('show default keyword template maps')),