monotone.py
198 lines
| 6.7 KiB
| text/x-python
|
PythonLexer
|
r6306 | # monotone support for the convert extension | ||
|
r6307 | import os, re, time | ||
|
r6306 | from mercurial import util | ||
|
r6332 | from common import NoRepo, MissingTool, commit, converter_source, checktool | ||
from common import commandline | ||||
|
r6307 | from mercurial.i18n import _ | ||
|
r6306 | |||
|
r6307 | class monotone_source(converter_source, commandline): | ||
|
r6306 | def __init__(self, ui, path=None, rev=None): | ||
converter_source.__init__(self, ui, path, rev) | ||||
|
r6307 | commandline.__init__(self, ui, 'mtn') | ||
|
r6306 | self.ui = ui | ||
self.path = path | ||||
# regular expressions for parsing monotone output | ||||
space = r'\s*' | ||||
name = r'\s+"((?:[^"]|\\")*)"\s*' | ||||
value = name | ||||
revision = r'\s+\[(\w+)\]\s*' | ||||
lines = r'(?:.|\n)+' | ||||
|
r6307 | |||
self.dir_re = re.compile(space + "dir" + name) | ||||
self.file_re = re.compile(space + "file" + name + "content" + revision) | ||||
|
r6306 | self.add_file_re = re.compile(space + "add_file" + name + "content" + revision) | ||
|
r6307 | self.patch_re = re.compile(space + "patch" + name + "from" + revision + "to" + revision) | ||
self.rename_re = re.compile(space + "rename" + name + "to" + name) | ||||
|
r6376 | self.delete_re = re.compile(space + "delete" + name) | ||
|
r6307 | self.tag_re = re.compile(space + "tag" + name + "revision" + revision) | ||
|
r6306 | self.cert_re = re.compile(lines + space + "name" + name + "value" + value) | ||
attr = space + "file" + lines + space + "attr" + space | ||||
self.attr_execute_re = re.compile(attr + '"mtn:execute"' + space + '"true"') | ||||
# cached data | ||||
self.manifest_rev = None | ||||
self.manifest = None | ||||
|
r6307 | self.files = None | ||
self.dirs = None | ||||
norepo = NoRepo (_("%s does not look like a monotone repo") % path) | ||||
|
r6306 | if not os.path.exists(path): | ||
raise norepo | ||||
|
r6307 | |||
|
r6332 | checktool('mtn', abort=False) | ||
|
r6307 | |||
# test if there are any revisions | ||||
|
r6306 | self.rev = None | ||
|
r6307 | try: | ||
|
r6306 | self.getheads() | ||
|
r6307 | except: | ||
raise norepo | ||||
|
r6306 | self.rev = rev | ||
|
r6307 | def mtnrun(self, *args, **kwargs): | ||
kwargs['d'] = self.path | ||||
return self.run0('automate', *args, **kwargs) | ||||
|
r6306 | def mtnloadmanifest(self, rev): | ||
if self.manifest_rev == rev: | ||||
return | ||||
|
r6307 | self.manifest = self.mtnrun("get_manifest_of", rev).split("\n\n") | ||
|
r6306 | self.manifest_rev = rev | ||
|
r6307 | self.files = {} | ||
self.dirs = {} | ||||
|
r6306 | |||
|
r6307 | for e in self.manifest: | ||
|
r6306 | m = self.file_re.match(e) | ||
|
r6307 | if m: | ||
|
r6306 | attr = "" | ||
name = m.group(1) | ||||
node = m.group(2) | ||||
if self.attr_execute_re.match(e): | ||||
attr += "x" | ||||
|
r6307 | self.files[name] = (node, attr) | ||
|
r6306 | m = self.dir_re.match(e) | ||
if m: | ||||
|
r6307 | self.dirs[m.group(1)] = True | ||
|
r6306 | |||
def mtnisfile(self, name, rev): | ||||
# a non-file could be a directory or a deleted or renamed file | ||||
self.mtnloadmanifest(rev) | ||||
|
r6307 | try: | ||
|
r6306 | self.files[name] | ||
return True | ||||
except KeyError: | ||||
return False | ||||
|
r6307 | |||
|
r6306 | def mtnisdir(self, name, rev): | ||
self.mtnloadmanifest(rev) | ||||
|
r6307 | try: | ||
|
r6306 | self.dirs[name] | ||
return True | ||||
except KeyError: | ||||
return False | ||||
|
r6307 | |||
|
r6306 | def mtngetcerts(self, rev): | ||
certs = {"author":"<missing>", "date":"<missing>", | ||||
"changelog":"<missing>", "branch":"<missing>"} | ||||
|
r6307 | cert_list = self.mtnrun("certs", rev).split("\n\n") | ||
|
r6306 | for e in cert_list: | ||
m = self.cert_re.match(e) | ||||
if m: | ||||
certs[m.group(1)] = m.group(2) | ||||
return certs | ||||
def mtnrenamefiles(self, files, fromdir, todir): | ||||
renamed = {} | ||||
for tofile in files: | ||||
suffix = tofile.lstrip(todir) | ||||
if todir + suffix == tofile: | ||||
renamed[tofile] = (fromdir + suffix).lstrip("/") | ||||
return renamed | ||||
|
r6307 | |||
|
r6306 | # implement the converter_source interface: | ||
|
r6307 | |||
|
r6306 | def getheads(self): | ||
|
r6307 | if not self.rev: | ||
return self.mtnrun("leaves").splitlines() | ||||
|
r6306 | else: | ||
return [self.rev] | ||||
def getchanges(self, rev): | ||||
|
r6307 | #revision = self.mtncmd("get_revision %s" % rev).split("\n\n") | ||
revision = self.mtnrun("get_revision", rev).split("\n\n") | ||||
|
r6306 | files = {} | ||
copies = {} | ||||
for e in revision: | ||||
m = self.add_file_re.match(e) | ||||
if m: | ||||
files[m.group(1)] = rev | ||||
m = self.patch_re.match(e) | ||||
if m: | ||||
files[m.group(1)] = rev | ||||
# Delete/rename is handled later when the convert engine | ||||
# discovers an IOError exception from getfile, | ||||
# but only if we add the "from" file to the list of changes. | ||||
|
r6376 | m = self.delete_re.match(e) | ||
if m: | ||||
files[m.group(1)] = rev | ||||
|
r6306 | m = self.rename_re.match(e) | ||
if m: | ||||
toname = m.group(2) | ||||
fromname = m.group(1) | ||||
if self.mtnisfile(toname, rev): | ||||
copies[toname] = fromname | ||||
files[toname] = rev | ||||
files[fromname] = rev | ||||
if self.mtnisdir(toname, rev): | ||||
renamed = self.mtnrenamefiles(self.files, fromname, toname) | ||||
for tofile, fromfile in renamed.items(): | ||||
|
r6307 | self.ui.debug (_("copying file in renamed dir from '%s' to '%s'") % (fromfile, tofile), '\n') | ||
|
r6306 | files[tofile] = rev | ||
for fromfile in renamed.values(): | ||||
files[fromfile] = rev | ||||
|
r6307 | return (files.items(), copies) | ||
|
r6306 | |||
def getmode(self, name, rev): | ||||
self.mtnloadmanifest(rev) | ||||
|
r6307 | try: | ||
|
r6306 | node, attr = self.files[name] | ||
return attr | ||||
except KeyError: | ||||
return "" | ||||
|
r6307 | |||
|
r6306 | def getfile(self, name, rev): | ||
if not self.mtnisfile(name, rev): | ||||
raise IOError() # file was deleted or renamed | ||||
|
r6307 | try: | ||
return self.mtnrun("get_file_of", name, r=rev) | ||||
except: | ||||
raise IOError() # file was deleted or renamed | ||||
def getcommit(self, rev): | ||||
|
r6306 | certs = self.mtngetcerts(rev) | ||
return commit( | ||||
author=certs["author"], | ||||
date=util.datestr(util.strdate(certs["date"], "%Y-%m-%dT%H:%M:%S")), | ||||
desc=certs["changelog"], | ||||
rev=rev, | ||||
|
r6307 | parents=self.mtnrun("parents", rev).splitlines(), | ||
|
r6306 | branch=certs["branch"]) | ||
def gettags(self): | ||||
tags = {} | ||||
|
r6307 | for e in self.mtnrun("tags").split("\n\n"): | ||
|
r6306 | m = self.tag_re.match(e) | ||
if m: | ||||
tags[m.group(1)] = m.group(2) | ||||
return tags | ||||
def getchangedfiles(self, rev, i): | ||||
# This function is only needed to support --filemap | ||||
# ... and we don't support that | ||||
raise NotImplementedError() | ||||