monotone.py
228 lines
| 7.9 KiB
| text/x-python
|
PythonLexer
|
r8250 | # monotone.py - monotone support for the convert extension | ||
# | ||||
# Copyright 2008, 2009 Mikkel Fahnoe Jorgensen <mikkel@dvide.com> and | ||||
# others | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2, incorporated herein by reference. | ||||
|
r6306 | |||
|
r7873 | import os, re | ||
|
r6306 | from mercurial import util | ||
|
r7873 | from common import NoRepo, commit, converter_source, checktool | ||
|
r6332 | 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 | ||||
|
r7973 | norepo = NoRepo (_("%s does not look like a monotone repo") % path) | ||
if not os.path.exists(os.path.join(path, '_MTN')): | ||||
|
r8052 | # Could be a monotone repository (SQLite db file) | ||
try: | ||||
header = file(path, 'rb').read(16) | ||||
except: | ||||
header = '' | ||||
if header != 'SQLite format 3\x00': | ||||
raise norepo | ||||
|
r7973 | |||
|
r6306 | # regular expressions for parsing monotone output | ||
space = r'\s*' | ||||
|
r6632 | name = r'\s+"((?:\\"|[^"])*)"\s*' | ||
|
r6306 | 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 | ||||
|
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>"} | ||||
|
r6632 | cert_list = self.mtnrun("certs", rev).split('\n\n key "') | ||
|
r6306 | for e in cert_list: | ||
m = self.cert_re.match(e) | ||||
if m: | ||||
|
r6632 | name, value = m.groups() | ||
value = value.replace(r'\"', '"') | ||||
value = value.replace(r'\\', '\\') | ||||
certs[name] = value | ||||
|
r8101 | # Monotone may have subsecond dates: 2005-02-05T09:39:12.364306 | ||
|
r8125 | # and all times are stored in UTC | ||
certs["date"] = certs["date"].split('.')[0] + " UTC" | ||||
|
r6306 | return certs | ||
# 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 = {} | ||
|
r8123 | ignoremove = {} | ||
|
r8099 | renameddirs = [] | ||
|
r6306 | copies = {} | ||
for e in revision: | ||||
m = self.add_file_re.match(e) | ||||
if m: | ||||
files[m.group(1)] = rev | ||||
|
r8123 | ignoremove[m.group(1)] = rev | ||
|
r6306 | 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): | ||||
|
r8123 | ignoremove[toname] = 1 | ||
|
r6306 | copies[toname] = fromname | ||
files[toname] = rev | ||||
files[fromname] = rev | ||||
|
r8099 | elif self.mtnisdir(toname, rev): | ||
renameddirs.append((fromname, toname)) | ||||
# Directory renames can be handled only once we have recorded | ||||
# all new files | ||||
for fromdir, todir in renameddirs: | ||||
renamed = {} | ||||
for tofile in self.files: | ||||
|
r8123 | if tofile in ignoremove: | ||
|
r8099 | continue | ||
if tofile.startswith(todir + '/'): | ||||
renamed[tofile] = fromdir + tofile[len(todir):] | ||||
|
r8124 | # Avoid chained moves like: | ||
# d1(/a) => d3/d1(/a) | ||||
# d2 => d3 | ||||
ignoremove[tofile] = 1 | ||||
|
r8099 | for tofile, fromfile in renamed.items(): | ||
|
r8100 | self.ui.debug (_("copying file in renamed directory " | ||
"from '%s' to '%s'") | ||||
|
r8099 | % (fromfile, tofile), '\n') | ||
files[tofile] = rev | ||||
copies[tofile] = fromfile | ||||
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() | ||||