2009-04-26 03:47:44 +04:00
|
|
|
# darcs.py - darcs support for the convert extension
|
|
|
|
#
|
|
|
|
# Copyright 2007-2009 Matt Mackall <mpm@selenic.com> and others
|
|
|
|
#
|
|
|
|
# This software may be used and distributed according to the terms of the
|
2010-01-20 07:20:08 +03:00
|
|
|
# GNU General Public License version 2 or any later version.
|
2016-03-02 17:23:23 +03:00
|
|
|
from __future__ import absolute_import
|
2007-10-03 00:49:11 +04:00
|
|
|
|
2016-03-02 17:23:23 +03:00
|
|
|
import errno
|
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import shutil
|
|
|
|
import tempfile
|
2007-10-03 00:49:11 +04:00
|
|
|
from mercurial.i18n import _
|
2016-03-02 17:23:23 +03:00
|
|
|
from mercurial import (
|
|
|
|
error,
|
|
|
|
util,
|
|
|
|
)
|
|
|
|
from . import common
|
|
|
|
NoRepo = common.NoRepo
|
2007-10-03 00:49:11 +04:00
|
|
|
|
|
|
|
# The naming drift of ElementTree is fun!
|
|
|
|
|
2010-01-25 09:05:27 +03:00
|
|
|
try:
|
2016-03-02 17:23:23 +03:00
|
|
|
import xml.etree.cElementTree.ElementTree as ElementTree
|
|
|
|
import xml.etree.cElementTree.XMLParser as XMLParser
|
2007-10-03 00:49:11 +04:00
|
|
|
except ImportError:
|
2010-01-25 09:05:27 +03:00
|
|
|
try:
|
2016-03-02 17:23:23 +03:00
|
|
|
import xml.etree.ElementTree.ElementTree as ElementTree
|
|
|
|
import xml.etree.ElementTree.XMLParser as XMLParser
|
2007-10-03 00:49:11 +04:00
|
|
|
except ImportError:
|
2010-01-25 09:05:27 +03:00
|
|
|
try:
|
2016-03-02 17:23:23 +03:00
|
|
|
import elementtree.cElementTree.ElementTree as ElementTree
|
|
|
|
import elementtree.cElementTree.XMLParser as XMLParser
|
2007-10-03 00:49:11 +04:00
|
|
|
except ImportError:
|
2010-01-25 09:05:27 +03:00
|
|
|
try:
|
2016-03-02 17:23:23 +03:00
|
|
|
import elementtree.ElementTree.ElementTree as ElementTree
|
|
|
|
import elementtree.ElementTree.XMLParser as XMLParser
|
2010-01-25 09:05:27 +03:00
|
|
|
except ImportError:
|
2011-11-10 02:36:54 +04:00
|
|
|
pass
|
2007-10-03 00:49:11 +04:00
|
|
|
|
2016-03-02 17:23:23 +03:00
|
|
|
class darcs_source(common.converter_source, common.commandline):
|
2017-11-23 04:49:01 +03:00
|
|
|
def __init__(self, ui, repotype, path, revs=None):
|
|
|
|
common.converter_source.__init__(self, ui, repotype, path, revs=revs)
|
2016-03-02 17:23:23 +03:00
|
|
|
common.commandline.__init__(self, ui, 'darcs')
|
2007-10-03 00:49:11 +04:00
|
|
|
|
2010-09-24 02:04:07 +04:00
|
|
|
# check for _darcs, ElementTree so that we can easily skip
|
|
|
|
# test-convert-darcs if ElementTree is not around
|
2007-11-10 01:21:35 +03:00
|
|
|
if not os.path.exists(os.path.join(path, '_darcs')):
|
2010-04-18 17:47:49 +04:00
|
|
|
raise NoRepo(_("%s does not look like a darcs repository") % path)
|
2007-10-03 00:49:11 +04:00
|
|
|
|
2016-03-02 17:23:23 +03:00
|
|
|
common.checktool('darcs')
|
2009-07-25 21:08:20 +04:00
|
|
|
version = self.run0('--version').splitlines()[0].strip()
|
|
|
|
if version < '2.1':
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_('darcs version 2.1 or newer needed (found %r)')
|
|
|
|
% version)
|
2007-10-31 00:14:15 +03:00
|
|
|
|
2011-11-10 02:36:54 +04:00
|
|
|
if "ElementTree" not in globals():
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_("Python ElementTree module is not available"))
|
2007-10-03 00:49:11 +04:00
|
|
|
|
2011-10-29 20:02:23 +04:00
|
|
|
self.path = os.path.realpath(path)
|
2007-10-03 00:49:11 +04:00
|
|
|
|
|
|
|
self.lastrev = None
|
|
|
|
self.changes = {}
|
|
|
|
self.parents = {}
|
|
|
|
self.tags = {}
|
|
|
|
|
2010-09-24 02:04:07 +04:00
|
|
|
# Check darcs repository format
|
|
|
|
format = self.format()
|
|
|
|
if format:
|
|
|
|
if format in ('darcs-1.0', 'hashed'):
|
|
|
|
raise NoRepo(_("%s repository format is unsupported, "
|
|
|
|
"please upgrade") % format)
|
|
|
|
else:
|
|
|
|
self.ui.warn(_('failed to detect repository format!'))
|
|
|
|
|
2007-10-03 00:49:11 +04:00
|
|
|
def before(self):
|
|
|
|
self.tmppath = tempfile.mkdtemp(
|
|
|
|
prefix='convert-' + os.path.basename(self.path) + '-')
|
|
|
|
output, status = self.run('init', repodir=self.tmppath)
|
|
|
|
self.checkexit(status)
|
|
|
|
|
2007-11-08 04:40:39 +03:00
|
|
|
tree = self.xml('changes', xml_output=True, summary=True,
|
|
|
|
repodir=self.path)
|
2007-10-03 00:49:11 +04:00
|
|
|
tagname = None
|
|
|
|
child = None
|
|
|
|
for elt in tree.findall('patch'):
|
|
|
|
node = elt.get('hash')
|
|
|
|
name = elt.findtext('name', '')
|
|
|
|
if name.startswith('TAG '):
|
|
|
|
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):
|
2009-09-19 03:15:38 +04:00
|
|
|
self.ui.debug('cleaning up %s\n' % self.tmppath)
|
2007-10-03 08:00:38 +04:00
|
|
|
shutil.rmtree(self.tmppath, ignore_errors=True)
|
2007-10-03 00:49:11 +04:00
|
|
|
|
2010-10-01 19:15:04 +04:00
|
|
|
def recode(self, s, encoding=None):
|
|
|
|
if isinstance(s, unicode):
|
|
|
|
# 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)
|
|
|
|
|
2007-11-08 04:40:39 +03:00
|
|
|
def xml(self, cmd, **kwargs):
|
2010-09-10 18:30:50 +04:00
|
|
|
# NOTE: darcs is currently encoding agnostic and will print
|
|
|
|
# patch metadata byte-for-byte, even in the XML changelog.
|
2007-10-03 00:49:11 +04:00
|
|
|
etree = ElementTree()
|
2010-10-01 19:15:04 +04:00
|
|
|
# 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.
|
|
|
|
parser = XMLParser(encoding='latin-1')
|
2012-08-03 23:37:33 +04:00
|
|
|
p = self._run(cmd, **kwargs)
|
|
|
|
etree.parse(p.stdout, parser=parser)
|
|
|
|
p.wait()
|
|
|
|
self.checkexit(p.returncode)
|
2007-10-03 00:49:11 +04:00
|
|
|
return etree.getroot()
|
|
|
|
|
2010-09-24 02:04:07 +04:00
|
|
|
def format(self):
|
|
|
|
output, status = self.run('show', 'repo', no_files=True,
|
|
|
|
repodir=self.path)
|
|
|
|
self.checkexit(status)
|
|
|
|
m = re.search(r'^\s*Format:\s*(.*)$', output, re.MULTILINE)
|
|
|
|
if not m:
|
|
|
|
return None
|
|
|
|
return ','.join(sorted(f.strip() for f in m.group(1).split(',')))
|
|
|
|
|
2009-10-05 01:06:14 +04:00
|
|
|
def manifest(self):
|
|
|
|
man = []
|
|
|
|
output, status = self.run('show', 'files', no_directories=True,
|
|
|
|
repodir=self.tmppath)
|
|
|
|
self.checkexit(status)
|
|
|
|
for line in output.split('\n'):
|
|
|
|
path = line[2:]
|
|
|
|
if path:
|
|
|
|
man.append(path)
|
|
|
|
return man
|
|
|
|
|
2007-10-03 00:49:11 +04:00
|
|
|
def getheads(self):
|
|
|
|
return self.parents[None]
|
|
|
|
|
|
|
|
def getcommit(self, rev):
|
|
|
|
elt = self.changes[rev]
|
|
|
|
date = util.strdate(elt.get('local_date'), '%a %b %d %H:%M:%S %Z %Y')
|
|
|
|
desc = elt.findtext('name') + '\n' + elt.findtext('comment', '')
|
2010-09-10 18:30:50 +04:00
|
|
|
# etree can return unicode objects for name, comment, and author,
|
|
|
|
# so recode() is used to ensure str objects are emitted.
|
2016-03-02 17:23:23 +03:00
|
|
|
return common.commit(author=self.recode(elt.get('author')),
|
|
|
|
date=util.datestr(date, '%Y-%m-%d %H:%M:%S %1%2'),
|
|
|
|
desc=self.recode(desc).strip(),
|
|
|
|
parents=self.parents[rev])
|
2007-10-03 00:49:11 +04:00
|
|
|
|
|
|
|
def pull(self, rev):
|
2007-11-08 04:40:39 +03:00
|
|
|
output, status = self.run('pull', self.path, all=True,
|
|
|
|
match='hash %s' % rev,
|
|
|
|
no_test=True, no_posthook=True,
|
|
|
|
external_merge='/bin/false',
|
2007-10-03 00:49:11 +04:00
|
|
|
repodir=self.tmppath)
|
|
|
|
if status:
|
|
|
|
if output.find('We have conflicts in') == -1:
|
|
|
|
self.checkexit(status, output)
|
2007-11-08 04:40:39 +03:00
|
|
|
output, status = self.run('revert', all=True, repodir=self.tmppath)
|
2007-10-03 00:49:11 +04:00
|
|
|
self.checkexit(status, output)
|
|
|
|
|
convert: introduce --full for converting all files
Convert will normally only process files that were changed in a source
revision, apply the filemap, and record it has a change in the target
repository. (If it ends up not really changing anything, nothing changes.)
That means that _if_ the filemap is changed before continuing an incremental
convert, the change will only kick in when the files it affects are modified in
a source revision and thus processed.
With --full, convert will make a full conversion every time and process
all files in the source repo and remove target repo files that shouldn't be
there. Filemap changes will thus kick in on the first converted revision, no
matter what is changed.
This flag should in most cases not make any difference but will make convert
significantly slower.
Other names has been considered for this feature, such as "resync", "sync",
"checkunmodified", "all" or "allfiles", but I found that they were less obvious
and required more explanation than "full" and were harder to describe
consistently.
2014-08-27 00:03:32 +04:00
|
|
|
def getchanges(self, rev, full):
|
|
|
|
if full:
|
2015-10-14 09:06:54 +03:00
|
|
|
raise error.Abort(_("convert from darcs does not support --full"))
|
2007-10-03 00:49:11 +04:00
|
|
|
copies = {}
|
|
|
|
changes = []
|
2009-10-05 01:06:14 +04:00
|
|
|
man = None
|
2007-10-03 00:49:11 +04:00
|
|
|
for elt in self.changes[rev].find('summary').getchildren():
|
|
|
|
if elt.tag in ('add_directory', 'remove_directory'):
|
|
|
|
continue
|
|
|
|
if elt.tag == 'move':
|
2009-10-05 01:06:14 +04:00
|
|
|
if man is None:
|
|
|
|
man = self.manifest()
|
|
|
|
source, dest = elt.get('from'), elt.get('to')
|
|
|
|
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
|
|
|
|
source = source + '/'
|
|
|
|
for f in man:
|
|
|
|
if not f.startswith(source):
|
|
|
|
continue
|
|
|
|
fdest = dest + '/' + f[len(source):]
|
|
|
|
changes.append((f, rev))
|
|
|
|
changes.append((fdest, rev))
|
|
|
|
copies[fdest] = f
|
2007-10-03 00:49:11 +04:00
|
|
|
else:
|
|
|
|
changes.append((elt.text.strip(), rev))
|
2009-10-05 01:06:14 +04:00
|
|
|
self.pull(rev)
|
2007-10-03 00:49:11 +04:00
|
|
|
self.lastrev = rev
|
2015-03-19 19:40:19 +03:00
|
|
|
return sorted(changes), copies, set()
|
2007-10-03 00:49:11 +04:00
|
|
|
|
|
|
|
def getfile(self, name, rev):
|
|
|
|
if rev != self.lastrev:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_('internal calling inconsistency'))
|
2010-05-09 23:52:34 +04:00
|
|
|
path = os.path.join(self.tmppath, name)
|
2014-08-27 00:03:32 +04:00
|
|
|
try:
|
|
|
|
data = util.readfile(path)
|
|
|
|
mode = os.lstat(path).st_mode
|
2015-06-24 08:20:08 +03:00
|
|
|
except IOError as inst:
|
2014-08-27 00:03:32 +04:00
|
|
|
if inst.errno == errno.ENOENT:
|
|
|
|
return None, None
|
|
|
|
raise
|
2015-06-24 08:30:33 +03:00
|
|
|
mode = (mode & 0o111) and 'x' or ''
|
2010-05-09 23:52:34 +04:00
|
|
|
return data, mode
|
2007-10-03 00:49:11 +04:00
|
|
|
|
|
|
|
def gettags(self):
|
|
|
|
return self.tags
|