darcs.py
241 lines
| 8.3 KiB
| text/x-python
|
PythonLexer
|
r8250 | # darcs.py - darcs support for the convert extension | ||
# | ||||
|
r47575 | # Copyright 2007-2009 Olivia Mackall <olivia@selenic.com> and others | ||
|
r8250 | # | ||
# This software may be used and distributed according to the terms of the | ||||
|
r10263 | # GNU General Public License version 2 or any later version. | ||
|
r5359 | |||
|
r28368 | import errno | ||
import os | ||||
import re | ||||
import shutil | ||||
|
r38183 | |||
|
r5359 | from mercurial.i18n import _ | ||
|
r28368 | from mercurial import ( | ||
error, | ||||
|
r38183 | pycompat, | ||
|
r28368 | util, | ||
) | ||||
|
r36625 | from mercurial.utils import dateutil | ||
|
r28368 | from . import common | ||
|
r43346 | |||
|
r28368 | NoRepo = common.NoRepo | ||
|
r5359 | |||
# The naming drift of ElementTree is fun! | ||||
|
r10282 | try: | ||
|
r28368 | import xml.etree.cElementTree.ElementTree as ElementTree | ||
import xml.etree.cElementTree.XMLParser as XMLParser | ||||
|
r5359 | except ImportError: | ||
|
r10282 | try: | ||
|
r28368 | import xml.etree.ElementTree.ElementTree as ElementTree | ||
import xml.etree.ElementTree.XMLParser as XMLParser | ||||
|
r5359 | except ImportError: | ||
|
r10282 | try: | ||
|
r28368 | import elementtree.cElementTree.ElementTree as ElementTree | ||
import elementtree.cElementTree.XMLParser as XMLParser | ||||
|
r5359 | except ImportError: | ||
|
r10282 | try: | ||
|
r28368 | import elementtree.ElementTree.ElementTree as ElementTree | ||
|
r43346 | import elementtree.ElementTree.XMLParser as XMLParser | ||
|
r10282 | except ImportError: | ||
|
r15457 | pass | ||
|
r5359 | |||
|
r43346 | |||
|
r28368 | class darcs_source(common.converter_source, common.commandline): | ||
|
r35168 | def __init__(self, ui, repotype, path, revs=None): | ||
common.converter_source.__init__(self, ui, repotype, path, revs=revs) | ||||
|
r43347 | common.commandline.__init__(self, ui, b'darcs') | ||
|
r5359 | |||
|
r12393 | # check for _darcs, ElementTree so that we can easily skip | ||
# test-convert-darcs if ElementTree is not around | ||||
|
r43347 | if not os.path.exists(os.path.join(path, b'_darcs')): | ||
raise NoRepo(_(b"%s does not look like a darcs repository") % path) | ||||
|
r5359 | |||
|
r43347 | common.checktool(b'darcs') | ||
version = self.run0(b'--version').splitlines()[0].strip() | ||||
if version < b'2.1': | ||||
|
r43346 | raise error.Abort( | ||
|
r43347 | _(b'darcs version 2.1 or newer needed (found %r)') % version | ||
|
r43346 | ) | ||
|
r5497 | |||
|
r43347 | if b"ElementTree" not in globals(): | ||
raise error.Abort(_(b"Python ElementTree module is not available")) | ||||
|
r5359 | |||
|
r15381 | self.path = os.path.realpath(path) | ||
|
r5359 | |||
self.lastrev = None | ||||
self.changes = {} | ||||
self.parents = {} | ||||
self.tags = {} | ||||
|
r12393 | # Check darcs repository format | ||
format = self.format() | ||||
if format: | ||||
|
r43347 | if format in (b'darcs-1.0', b'hashed'): | ||
|
r43346 | raise NoRepo( | ||
|
r43347 | _( | ||
b"%s repository format is unsupported, " | ||||
b"please upgrade" | ||||
) | ||||
|
r43346 | % format | ||
) | ||||
|
r12393 | else: | ||
|
r43347 | self.ui.warn(_(b'failed to detect repository format!')) | ||
|
r12393 | |||
|
r5359 | def before(self): | ||
|
r38183 | self.tmppath = pycompat.mkdtemp( | ||
|
r43347 | prefix=b'convert-' + os.path.basename(self.path) + b'-' | ||
|
r43346 | ) | ||
|
r43347 | output, status = self.run(b'init', repodir=self.tmppath) | ||
|
r5359 | self.checkexit(status) | ||
|
r43346 | tree = self.xml( | ||
|
r43347 | b'changes', xml_output=True, summary=True, repodir=self.path | ||
|
r43346 | ) | ||
|
r5359 | tagname = None | ||
child = None | ||||
|
r43347 | for elt in tree.findall(b'patch'): | ||
node = elt.get(b'hash') | ||||
name = elt.findtext(b'name', b'') | ||||
if name.startswith(b'TAG '): | ||||
|
r5359 | tagname = name[4:].strip() | ||
elif tagname is not None: | ||||
self.tags[tagname] = node | ||||
tagname = None | ||||
self.changes[node] = elt | ||||
self.parents[child] = [node] | ||||
child = node | ||||
self.parents[child] = [] | ||||
def after(self): | ||||
|
r43347 | self.ui.debug(b'cleaning up %s\n' % self.tmppath) | ||
|
r5362 | shutil.rmtree(self.tmppath, ignore_errors=True) | ||
|
r5359 | |||
|
r12717 | def recode(self, s, encoding=None): | ||
|
r38332 | if isinstance(s, pycompat.unicode): | ||
|
r12717 | # XMLParser returns unicode objects for anything it can't | ||
# encode into ASCII. We convert them back to str to get | ||||
# recode's normal conversion behavior. | ||||
s = s.encode('latin-1') | ||||
return super(darcs_source, self).recode(s, encoding) | ||||
|
r5512 | def xml(self, cmd, **kwargs): | ||
|
r12252 | # NOTE: darcs is currently encoding agnostic and will print | ||
# patch metadata byte-for-byte, even in the XML changelog. | ||||
|
r5359 | etree = ElementTree() | ||
|
r12717 | # While we are decoding the XML as latin-1 to be as liberal as | ||
# possible, etree will still raise an exception if any | ||||
# non-printable characters are in the XML changelog. | ||||
|
r43347 | parser = XMLParser(encoding=b'latin-1') | ||
|
r17413 | p = self._run(cmd, **kwargs) | ||
etree.parse(p.stdout, parser=parser) | ||||
p.wait() | ||||
self.checkexit(p.returncode) | ||||
|
r5359 | return etree.getroot() | ||
|
r12393 | def format(self): | ||
|
r43347 | output, status = self.run(b'show', b'repo', repodir=self.path) | ||
|
r12393 | self.checkexit(status) | ||
m = re.search(r'^\s*Format:\s*(.*)$', output, re.MULTILINE) | ||||
if not m: | ||||
return None | ||||
|
r43347 | return b','.join(sorted(f.strip() for f in m.group(1).split(b','))) | ||
|
r12393 | |||
|
r9527 | def manifest(self): | ||
man = [] | ||||
|
r43346 | output, status = self.run( | ||
|
r43347 | b'show', b'files', no_directories=True, repodir=self.tmppath | ||
|
r43346 | ) | ||
|
r9527 | self.checkexit(status) | ||
|
r43347 | for line in output.split(b'\n'): | ||
|
r9527 | path = line[2:] | ||
if path: | ||||
man.append(path) | ||||
return man | ||||
|
r5359 | def getheads(self): | ||
return self.parents[None] | ||||
def getcommit(self, rev): | ||||
elt = self.changes[rev] | ||||
|
r43347 | dateformat = b'%a %b %d %H:%M:%S %Z %Y' | ||
date = dateutil.strdate(elt.get(b'local_date'), dateformat) | ||||
desc = elt.findtext(b'name') + b'\n' + elt.findtext(b'comment', b'') | ||||
|
r12252 | # etree can return unicode objects for name, comment, and author, | ||
# so recode() is used to ensure str objects are emitted. | ||||
|
r43347 | newdateformat = b'%Y-%m-%d %H:%M:%S %1%2' | ||
|
r43346 | return common.commit( | ||
|
r43347 | author=self.recode(elt.get(b'author')), | ||
|
r43346 | date=dateutil.datestr(date, newdateformat), | ||
desc=self.recode(desc).strip(), | ||||
parents=self.parents[rev], | ||||
) | ||||
|
r5359 | |||
def pull(self, rev): | ||||
|
r43346 | output, status = self.run( | ||
|
r43347 | b'pull', | ||
|
r43346 | self.path, | ||
all=True, | ||||
|
r43347 | match=b'hash %s' % rev, | ||
|
r43346 | no_test=True, | ||
no_posthook=True, | ||||
|
r43347 | external_merge=b'/bin/false', | ||
|
r43346 | repodir=self.tmppath, | ||
) | ||||
|
r5359 | if status: | ||
|
r43347 | if output.find(b'We have conflicts in') == -1: | ||
|
r5359 | self.checkexit(status, output) | ||
|
r43347 | output, status = self.run(b'revert', all=True, repodir=self.tmppath) | ||
|
r5359 | self.checkexit(status, output) | ||
|
r22300 | def getchanges(self, rev, full): | ||
if full: | ||||
|
r43347 | raise error.Abort(_(b"convert from darcs does not support --full")) | ||
|
r5359 | copies = {} | ||
changes = [] | ||||
|
r9527 | man = None | ||
|
r43347 | for elt in self.changes[rev].find(b'summary').getchildren(): | ||
if elt.tag in (b'add_directory', b'remove_directory'): | ||||
|
r5359 | continue | ||
|
r43347 | if elt.tag == b'move': | ||
|
r9527 | if man is None: | ||
man = self.manifest() | ||||
|
r43347 | source, dest = elt.get(b'from'), elt.get(b'to') | ||
|
r9527 | if source in man: | ||
# File move | ||||
changes.append((source, rev)) | ||||
changes.append((dest, rev)) | ||||
copies[dest] = source | ||||
else: | ||||
# Directory move, deduce file moves from manifest | ||||
|
r43347 | source = source + b'/' | ||
|
r9527 | for f in man: | ||
if not f.startswith(source): | ||||
continue | ||||
|
r43347 | fdest = dest + b'/' + f[len(source) :] | ||
|
r9527 | changes.append((f, rev)) | ||
changes.append((fdest, rev)) | ||||
copies[fdest] = f | ||||
|
r5359 | else: | ||
changes.append((elt.text.strip(), rev)) | ||||
|
r9527 | self.pull(rev) | ||
|
r5359 | self.lastrev = rev | ||
|
r24395 | return sorted(changes), copies, set() | ||
|
r5359 | |||
def getfile(self, name, rev): | ||||
if rev != self.lastrev: | ||||
|
r43347 | raise error.Abort(_(b'internal calling inconsistency')) | ||
|
r11134 | path = os.path.join(self.tmppath, name) | ||
|
r22296 | try: | ||
data = util.readfile(path) | ||||
mode = os.lstat(path).st_mode | ||||
|
r25660 | except IOError as inst: | ||
|
r22296 | if inst.errno == errno.ENOENT: | ||
return None, None | ||||
raise | ||||
|
r43347 | mode = (mode & 0o111) and b'x' or b'' | ||
|
r11134 | return data, mode | ||
|
r5359 | |||
def gettags(self): | ||||
return self.tags | ||||