# HG changeset patch # User Christian Ebert # Date 1196758572 -3600 # Node ID 6160401f94f2285a355b356d1c6745ecd8b771a7 # Parent 46ccec2f325f9bb174fcc179f0b661cb87b07c9a (0.9.2compat) implement context based changes from default branch NOTE: Interrupted commit is broken with versions < 0.9.5! Leave tests outcommented. diff -r 46ccec2f325f -r 6160401f94f2 hgkw/keyword.py --- a/hgkw/keyword.py Mon Nov 26 12:52:54 2007 +0100 +++ b/hgkw/keyword.py Tue Dec 04 09:56:12 2007 +0100 @@ -82,11 +82,30 @@ from mercurial import commands, cmdutil, context from mercurial import filelog, localrepo, revlog, templater, util +from mercurial.node import * from mercurial.i18n import gettext as _ import getopt, os.path, re, shutil, sys, tempfile, time # backwards compatibility hacks +def _wwrite(repo, f, data, mf): + '''Makes repo.wwrite backwards compatible.''' + # 656e06eebda7 removed file descriptor argument + # 67982d3ee76c added flags argument + try: + repo.wwrite(f, data, mf.flags(f)) + except (AttributeError, TypeError): + repo.wwrite(f, data) + +def _normal(repo, files): + '''Backwards compatible repo.dirstate.normal/update.''' + # 6fd953d5faea introduced dirstate.normal() + try: + for f in files: + repo.dirstate.normal(f) + except AttributeError: + repo.dirstate.update(files, 'n') + def _pathto(repo, f, cwd=None): '''kwfiles behaves similar to status, using pathto since 78b6add1f966.''' try: @@ -147,6 +166,8 @@ '''Returns hgdate in cvs-like UTC format.''' return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0])) +_kwtemplater = None + class kwtemplater(object): ''' Sets up keyword templates, corresponding keyword regex, and @@ -162,12 +183,12 @@ 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}', } - def __init__(self, ui, repo, expand, path='', node=None): + def __init__(self, ui, repo, inc, exc): self.ui = ui self.repo = repo - self.t = expand or None - self.path = path - self.node = node + self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1] + self.node = None + self.path = '' kwmaps = self.ui.configitems('keywordmaps') if kwmaps: # override default templates @@ -177,9 +198,9 @@ escaped = map(re.escape, self.templates.keys()) kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped) self.re_kw = re.compile(kwpat) - if self.t: - templater.common_filters['utcdate'] = utcdate - self.t = self._changeset_templater() + + templater.common_filters['utcdate'] = utcdate + self.ct = self._changeset_templater() def _changeset_templater(self): '''Backwards compatible cmdutil.changeset_templater.''' @@ -191,24 +212,6 @@ return cmdutil.changeset_templater(self.ui, self.repo, False, None, '', False) - def _wwrite(self, f, data, man): - '''Makes repo.wwrite backwards compatible.''' - # 656e06eebda7 removed file descriptor argument - # 67982d3ee76c added flags argument - try: - self.repo.wwrite(f, data, man.flags(f)) - except (AttributeError, TypeError): - self.repo.wwrite(f, data) - - def _normal(self, files): - '''Backwards compatible repo.dirstate.normal/update.''' - # 6fd953d5faea introduced dirstate.normal() - try: - for f in files: - self.repo.dirstate.normal(f) - except AttributeError: - self.repo.dirstate.update(files, 'n') - def substitute(self, node, data, subfunc): '''Obtains node if missing, and calls given substitution function.''' if not self.node: @@ -218,10 +221,10 @@ def kwsub(mobj): '''Substitutes keyword using corresponding template.''' kw = mobj.group(1) - self.t.use_template(self.templates[kw]) + self.ct.use_template(self.templates[kw]) self.ui.pushbuffer() - self.t.show(changenode=self.node, - root=self.repo.root, file=self.path) + self.ct.show(changenode=self.node, + root=self.repo.root, file=self.path) return '$%s: %s $' % (kw, templater.firstline(self.ui.popbuffer())) return subfunc(kwsub, data) @@ -232,14 +235,14 @@ return data return self.substitute(node, data, self.re_kw.sub) - def process(self, node, data): + def process(self, node, data, expand): '''Returns a tuple: data, count. Count is number of keywords/keyword substitutions, indicates to caller whether to act on file containing data. Keywords in data are expanded, if templater was initialized.''' if util.binary(data): return data, None - if self.t: + if expand: return self.substitute(node, data, self.re_kw.subn) return data, self.re_kw.search(data) @@ -249,23 +252,6 @@ return text return self.re_kw.sub(r'$\1$', text) - def overwrite(self, candidates, man, commit): - '''Overwrites files in working directory if keywords are detected. - Keywords are expanded if keyword templater is initialized, - otherwise their substitution is removed.''' - expand = self.t is not None - action = ('shrinking', 'expanding')[expand] - notify = (self.ui.note, self.ui.debug)[commit] - overwritten = [] - for f in candidates: - fp = self.repo.file(f, kwexp=expand, kwmatch=True) - data, kwfound = fp.kwctread(man[f]) - if kwfound: - notify(_('overwriting %s %s keywords\n') % (f, action)) - self._wwrite(f, data, man) - overwritten.append(f) - self._normal(overwritten) - class kwfilelog(filelog.filelog): ''' Subclass of filelog to hook into its read, add, cmp methods. @@ -274,12 +260,13 @@ def __init__(self, opener, path, kwtemplater): super(kwfilelog, self).__init__(opener, path) self.kwtemplater = kwtemplater + self.kwtemplater.path = path - def kwctread(self, node): + def kwctread(self, node, expand): '''Reads expanding and counting keywords (only called from kwtemplater.overwrite).''' data = super(kwfilelog, self).read(node) - return self.kwtemplater.process(node, data) + return self.kwtemplater.process(node, data, expand) def read(self, node): '''Expands keywords when reading filelog.''' @@ -299,28 +286,47 @@ return t2 != text return revlog.revlog.cmp(self, node, text) -def _status(ui, repo, *pats, **opts): +def _iskwfile(f, kwtemplater, link): + return not link(f) and kwtemplater.matcher(f) + +def _status(ui, repo, kwtemplater, *pats, **opts): '''Bails out if [keyword] configuration is not active. Returns status of working directory.''' - if hasattr(ui, 'kwfmatcher'): + if kwtemplater: files, match, anypats = cmdutil.matchpats(repo, pats, opts) return repo.status(files=files, match=match, list_clean=True) if ui.configitems('keyword'): raise util.Abort(_('[keyword] patterns cannot match')) raise util.Abort(_('no [keyword] patterns configured')) -def _iskwfile(ui, man, f): - return not man.linkf(f) and ui.kwfmatcher(f) - -def _overwrite(ui, repo, files, node, man, expand, commit): - '''Passes given files to kwtemplater for overwriting.''' - files.sort() - kwt = kwtemplater(ui, repo, expand, node=node) - kwt.overwrite(files, man, commit) +def _overwrite(ui, repo, kwtemplater, node=None, expand=True, files=None): + '''Overwrites selected files expanding/shrinking keywords.''' + ctx = repo.changectx(node) + mf = ctx.manifest() + if files is None: + notify = ui.debug # commit + files = [f for f in ctx.files() if mf.has_key(f)] + else: + notify = ui.note # kwexpand/kwshrink + candidates = [f for f in files if _iskwfile(f, kwtemplater, mf.linkf)] + if candidates: + overwritten = [] + candidates.sort() + action = expand and 'expanding' or 'shrinking' + kwtemplater.node = node or ctx.node() + for f in candidates: + fp = repo.file(f, kwmatch=True) + data, kwfound = fp.kwctread(mf[f], expand) + if kwfound: + notify(_('overwriting %s %s keywords\n') % (f, action)) + _wwrite(repo, f, data, mf) + overwritten.append(f) + _normal(repo, overwritten) def _kwfwrite(ui, repo, expand, *pats, **opts): '''Selects files and passes them to _overwrite.''' - status = _status(ui, repo, *pats, **opts) + global _kwtemplater + status = _status(ui, repo, _kwtemplater, *pats, **opts) modified, added, removed, deleted, unknown, ignored, clean = status if modified or added or removed or deleted: raise util.Abort(_('outstanding uncommitted changes in given files')) @@ -328,12 +334,7 @@ try: wlock = repo.wlock() lock = repo.lock() - ctx = repo.changectx() - man = ctx.manifest() - candidates = [f for f in clean if _iskwfile(ui, man, f)] - if candidates: - # 7th argument sets commit to False - _overwrite(ui, repo, candidates, ctx.node(), man, expand, False) + _overwrite(ui, repo, _kwtemplater, expand=expand, files=clean) finally: del wlock, lock @@ -407,10 +408,14 @@ ui.quiet = not verbose commands.branch(ui, repo, branchname) ui.quiet = quiet + for name, cmd in ui.configitems('hooks'): + if name.split('.', 1)[0].find('commit') > -1: + repo.ui.setconfig('hooks', name, '') + ui.note(_('unhooked all commit hooks\n')) ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg)) repo.commit(text=msg) - pathinfo = ('', ' in %s' % path)[ui.verbose] - demostatus('%s keywords expanded%s' % (kwstatus, pathinfo)) + format = ui.verbose and ' in %s' % path or '' + demostatus('%s keywords expanded%s' % (kwstatus, format)) ui.write(repo.wread(fn)) ui.debug(_('\nremoving temporary repo %s\n') % tmpdir) shutil.rmtree(tmpdir, ignore_errors=True) @@ -432,7 +437,8 @@ keyword expansion. That is, files matched by [keyword] config patterns but not symlinks. ''' - status = _status(ui, repo, *pats, **opts) + global _kwtemplater + status = _status(ui, repo, _kwtemplater, *pats, **opts) modified, added, removed, deleted, unknown, ignored, clean = status if opts['untracked']: files = modified + added + unknown + clean @@ -440,7 +446,7 @@ files = modified + added + clean files.sort() # use the full definition of repo._link for backwards compatibility - kwfiles = [f for f in files if ui.kwfmatcher(f) + kwfiles = [f for f in files if _kwtemplater.matcher(f) and not os.path.islink(repo.wjoin(f))] cwd = pats and repo.getcwd() or '' allf = opts['all'] @@ -452,12 +458,9 @@ if allf or ignore: kwfstats += (('I', [f for f in files if f not in kwfiles]),) for char, filenames in kwfstats: - if allf or ui.verbose: - format = '%s %%s\n' % char - else: - format = '%s\n' + format = (allf or ui.verbose) and '%s %%s\n' % char or '%s\n' for f in filenames: - ui.write(format % repo.pathto(f, cwd)) + ui.write(format % _pathto(repo, f, cwd)) def shrink(ui, repo, *pats, **opts): '''revert expanded keywords in working directory @@ -503,15 +506,15 @@ if not inc: return - ui.kwfmatcher = util.matcher(repo.root, inc=inc, exc=exc)[1] + global _kwtemplater + _kwtemplater = kwtemplater(ui, repo, inc, exc) class kwrepo(repo.__class__): - def file(self, f, kwexp=True, kwmatch=False): + def file(self, f, kwmatch=False): if f[0] == '/': f = f[1:] - if kwmatch or ui.kwfmatcher(f): - kwt = kwtemplater(ui, self, kwexp, path=f) - return kwfilelog(self.sopener, f, kwt) + if kwmatch or _kwtemplater.matcher(f): + return kwfilelog(self.sopener, f, _kwtemplater) return filelog.filelog(self.sopener, f) def _commit(self, files, text, user, date, match, force, lock, wlock, @@ -540,21 +543,39 @@ _lock = lock _wlock = wlock del wlock, lock + _p1 = _p2 = None try: if not _wlock: _wlock = self.wlock() if not _lock: _lock = self.lock() + # store and postpone commit hooks + commithooks = [] + for name, cmd in ui.configitems('hooks'): + if name.split('.', 1)[0] == 'commit': + commithooks.append((name, cmd)) + ui.setconfig('hooks', name, '') + if commithooks: + # store parents for commit hook environment + if p1 is None: + _p1, _p2 = repo.dirstate.parents() + else: + _p1, _p2 = p1, p2 or nullid + _p1 = hex(_p1) + if _p2 == nullid: + _p2 = '' + else: + _p2 = hex(_p2) + node = self._commit(files, text, user, date, match, force, _lock, _wlock, force_editor, p1, p2, extra) + + # restore commit hooks + for name, cmd in commithooks: + ui.setconfig('hooks', name, cmd) if node is not None: - cl = self.changelog.read(node) - mn = self.manifest.read(cl[0]) - candidates = [f for f in cl[3] if mn.has_key(f) - and _iskwfile(ui, mn, f)] - if candidates: - # 6th, 7th arguments set expansion, commit to True - _overwrite(ui, self, candidates, node, mn, True, True) + _overwrite(ui, self, _kwtemplater, node=node) + repo.hook('commit', node=node, parent1=_p1, parent2=_p2) return node finally: del _wlock, _lock diff -r 46ccec2f325f -r 6160401f94f2 tests/test-keyword --- a/tests/test-keyword Mon Nov 26 12:52:54 2007 +0100 +++ b/tests/test-keyword Tue Dec 04 09:56:12 2007 +0100 @@ -6,6 +6,9 @@ [keyword] * = b = ignore +[hooks] +commit= +commit.test=cp a hooktest EOF echo % help @@ -35,9 +38,19 @@ echo % cat cat a b -echo % default keyword expansion +echo % addremove +hg addremove +echo % status +hg status + +echo % default keyword expansion including commit hook +#echo % interrupted commit should not change state or run commit hook +#HGEDITOR=false hg --debug commit +#echo % status +#hg status + echo % commit -hg --debug commit -A -mab -d '0 0' -u 'User Name ' +hg --debug commit -mab -d '0 0' -u 'User Name ' echo % status hg status echo % identify @@ -47,6 +60,15 @@ echo % hg cat hg cat a b +echo +echo % diff a hooktest +diff a hooktest + +echo % removing commit hook from config +sed -e '/\[hooks\]/,$ d' $HGRCPATH > $HGRCPATH.nohook +mv $HGRCPATH.nohook $HGRCPATH +rm hooktest + echo % touch touch a b echo % status @@ -108,11 +130,11 @@ secondline EOF -echo % interrupted commit -# redirection and grep for backwards compatibility -HGEDITOR=false hg commit 2>&1 | grep -v 'edit failed:' -echo % status -hg status +#echo % interrupted commit +## redirection and grep for backwards compatibility +#HGEDITOR=false hg commit 2>&1 | grep -v 'edit failed:' +#echo % status +#hg status echo % commit hg --debug commit -l log -d '2 0' -u 'User Name ' diff -r 46ccec2f325f -r 6160401f94f2 tests/test-keyword.out --- a/tests/test-keyword.out Mon Nov 26 12:52:54 2007 +0100 +++ b/tests/test-keyword.out Tue Dec 04 09:56:12 2007 +0100 @@ -71,14 +71,20 @@ do not process $Id: xxx $ ignore $Id$ -% default keyword expansion -% commit +% addremove adding a adding b +% status +A a +A b +% default keyword expansion including commit hook +% commit a b overwriting a expanding keywords +running hook commit.test: cp a hooktest % status +? hooktest % identify 7f0665a496fd % cat @@ -91,6 +97,9 @@ do not process $Id: xxx $ ignore $Id$ + +% diff a hooktest +% removing commit hook from config % touch % status % update @@ -154,12 +163,6 @@ do not process $Id: xxx $ ignore $Id$ -% interrupted commit -transaction abort! -rollback completed -% status -M a -? log % commit a overwriting a expanding keywords