author | Christian Ebert <blacktrash@gmx.net> |
Sat, 06 Jan 2007 14:34:49 +0100 | |
branch | solo-extension |
changeset 69 | 4c5d9635b517 |
parent 68 | b285ab731fff |
child 71 | f7a2a246740c |
permissions | -rw-r--r-- |
# keyword.py - keyword expansion for mercurial '''keyword expansion hack against the grain of a DSCM This extension lets you expand RCS/CVS-like keywords in a Mercurial repository. There are many good reasons why this is not needed in a distributed SCM, still it may be useful in very small projects based on single files (like LaTeX packages), that are mostly addressed to an audience not running a version control system. Supported $keywords$ are: Revision: changeset id Author: full username Date: %a %b %d %H:%M:%S %Y %z $ RCSFile: basename,v Source: /path/to/basename,v Id: basename,v csetid %Y-%m-%d %H:%M:%S %s shortname Header: /path/to/basename,v csetid %Y-%m-%d %H:%M:%S %s shortname Simple setup in hgrc: # enable extension # keyword.py in hgext folder, specify full path otherwise hgext.keyword = # filename patterns for expansion are configured in this section [keyword] **.py = expand ... ''' from mercurial.node import * from mercurial.i18n import _ from mercurial import context, filelog, revlog, util import os.path, re re_kw = re.compile( r'\$(Id|Header|Author|Date|Revision|RCSFile|Source)[^$]*?\$') def kwexpand(matchobj, repo, path, changeid=None, fileid=None, filelog=None): '''Called by kwrepo.commit and kwfilelog.read. Sets supported keywords as local variables and evaluates them to their expansion if matchobj is equal to string representation.''' c = context.filectx(repo, path, changeid=changeid, fileid=fileid, filelog=filelog) date = c.date() Revision = c.changectx() Author = c.user() RCSFile = os.path.basename(path)+',v' Source = repo.wjoin(path)+',v' Date = util.datestr(date=date) revdateauth = '%s %s %s' % (Revision, util.datestr(date=date, format=util.defaultdateformats[0]), util.shortuser(Author)) Header = '%s %s' % (Source, revdateauth) Id = '%s %s' % (RCSFile, revdateauth) return '$%s: %s $' % (matchobj.group(1), eval(matchobj.group(1))) def kwfmatches(ui, repo, files): '''Selects candidates for keyword substitution configured in keyword section in hgrc.''' files = [f for f in files if not f.startswith('.hg')] if not files: return [] candidates = [] fmatchers = [util.matcher(repo.root, '', [pat], [], [])[1] for pat, opt in ui.configitems('keyword') if opt == 'expand'] for f in files: for mf in fmatchers: if mf(f): candidates.append(f) break return candidates def reposetup(ui, repo): if not repo.local(): return class kwrepo(repo.__class__): def file(self, f): if f[0] == '/': f = f[1:] return filelog.filelog(self.sopener, f, self, self.revlogversion) def commit(self, files=None, text="", user=None, date=None, match=util.always, force=False, lock=None, wlock=None, force_editor=False, p1=None, p2=None, extra={}): commit = [] remove = [] changed = [] use_dirstate = (p1 is None) # not rawcommit extra = extra.copy() if use_dirstate: if files: for f in files: s = self.dirstate.state(f) if s in 'nmai': commit.append(f) elif s == 'r': remove.append(f) else: ui.warn(_("%s not tracked!\n") % f) else: changes = self.status(match=match)[:5] modified, added, removed, deleted, unknown = changes commit = modified + added remove = removed else: commit = files if use_dirstate: p1, p2 = self.dirstate.parents() update_dirstate = True else: p1, p2 = p1, p2 or nullid update_dirstate = (self.dirstate.parents()[0] == p1) c1 = self.changelog.read(p1) c2 = self.changelog.read(p2) m1 = self.manifest.read(c1[0]).copy() m2 = self.manifest.read(c2[0]) if use_dirstate: branchname = self.workingctx().branch() try: branchname = branchname.decode('UTF-8').encode('UTF-8') except UnicodeDecodeError: raise util.Abort(_('branch name not in UTF-8!')) else: branchname = "" if use_dirstate: oldname = c1[5].get("branch", "") # stored in UTF-8 if not commit and not remove and not force and p2 == nullid and \ branchname == oldname: ui.status(_("nothing changed\n")) return None xp1 = hex(p1) if p2 == nullid: xp2 = '' else: xp2 = hex(p2) self.hook("precommit", throw=True, parent1=xp1, parent2=xp2) if not wlock: wlock = self.wlock() if not lock: lock = self.lock() tr = self.transaction() # check in files new = {} linkrev = self.changelog.count() commit.sort() is_exec = util.execfunc(self.root, m1.execf) is_link = util.linkfunc(self.root, m1.linkf) for f in commit: ui.note(f + "\n") try: new[f] = self.filecommit(f, m1, m2, linkrev, tr, changed) m1.set(f, is_exec(f), is_link(f)) except OSError: if use_dirstate: ui.warn(_("trouble committing %s!\n") % f) raise else: remove.append(f) # update manifest m1.update(new) remove.sort() removed = [] for f in remove: if f in m1: del m1[f] removed.append(f) mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0], (new, removed)) # add changeset new = new.keys() new.sort() user = user or ui.username() if not text or force_editor: edittext = [] if text: edittext.append(text) edittext.append("") edittext.append("HG: user: %s" % user) if p2 != nullid: edittext.append("HG: branch merge") edittext.extend(["HG: changed %s" % f for f in changed]) edittext.extend(["HG: removed %s" % f for f in removed]) if not changed and not remove: edittext.append("HG: no files changed") edittext.append("") # run editor in the repository root olddir = os.getcwd() os.chdir(self.root) text = ui.edit("\n".join(edittext), user) os.chdir(olddir) lines = [line.rstrip() for line in text.rstrip().splitlines()] while lines and not lines[0]: del lines[0] if not lines: return None text = '\n'.join(lines) if branchname: extra["branch"] = branchname n = self.changelog.add(mn, changed + removed, text, tr, p1, p2, user, date, extra) self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1, parent2=xp2) # substitute keywords for f in kwfmatches(ui, self, changed): data = self.wfile(f).read() if not util.binary(data): data, kwct = re_kw.subn(lambda m: kwexpand(m, self, f, changeid=hex(n)), data) if kwct: ui.debug(_('overwriting %s expanding keywords\n' % f)) self.wfile(f, 'w').write(data) tr.close() if use_dirstate or update_dirstate: self.dirstate.setparents(n) if use_dirstate: self.dirstate.update(new, "n") self.dirstate.forget(removed) self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2) return n class kwfilelog(filelog.filelog): def __init__(self, opener, path, repo, defversion=revlog.REVLOG_DEFAULT_VERSION): super(kwfilelog, self).__init__(opener, path, defversion) self._repo = repo self._path = path def read(self, node): data = super(kwfilelog, self).read(node) if not util.binary(data) and \ kwfmatches(ui, self._repo, [self._path]): ui.debug(_('expanding keywords in %s\n' % self._path)) return re_kw.sub(lambda m: kwexpand(m, self._repo, self._path, fileid=node, filelog=self), data) return data def size(self, rev): '''Overrides filelog's size() to use kwfilelog.read().''' node = revlog.node(self, rev) if super(kwfilelog, self).renamed(node): return len(self.read(node)) return revlog.size(self, rev) def cmp(self, node, text): '''Overrides filelog's cmp() to use kwfilelog.read().''' if super(kwfilelog, self).renamed(node): t2 = self.read(node) return t2 != text filelog.filelog = kwfilelog repo.__class__ = kwrepo