mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 00:45:18 +03:00
0df22182cc
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.
209 lines
7.8 KiB
Python
209 lines
7.8 KiB
Python
# 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
|
|
# GNU General Public License version 2 or any later version.
|
|
|
|
from common import NoRepo, checktool, commandline, commit, converter_source
|
|
from mercurial.i18n import _
|
|
from mercurial import util
|
|
import os, shutil, tempfile, re, errno
|
|
|
|
# The naming drift of ElementTree is fun!
|
|
|
|
try:
|
|
from xml.etree.cElementTree import ElementTree, XMLParser
|
|
except ImportError:
|
|
try:
|
|
from xml.etree.ElementTree import ElementTree, XMLParser
|
|
except ImportError:
|
|
try:
|
|
from elementtree.cElementTree import ElementTree, XMLParser
|
|
except ImportError:
|
|
try:
|
|
from elementtree.ElementTree import ElementTree, XMLParser
|
|
except ImportError:
|
|
pass
|
|
|
|
class darcs_source(converter_source, commandline):
|
|
def __init__(self, ui, path, rev=None):
|
|
converter_source.__init__(self, ui, path, rev=rev)
|
|
commandline.__init__(self, ui, 'darcs')
|
|
|
|
# check for _darcs, ElementTree so that we can easily skip
|
|
# test-convert-darcs if ElementTree is not around
|
|
if not os.path.exists(os.path.join(path, '_darcs')):
|
|
raise NoRepo(_("%s does not look like a darcs repository") % path)
|
|
|
|
checktool('darcs')
|
|
version = self.run0('--version').splitlines()[0].strip()
|
|
if version < '2.1':
|
|
raise util.Abort(_('darcs version 2.1 or newer needed (found %r)') %
|
|
version)
|
|
|
|
if "ElementTree" not in globals():
|
|
raise util.Abort(_("Python ElementTree module is not available"))
|
|
|
|
self.path = os.path.realpath(path)
|
|
|
|
self.lastrev = None
|
|
self.changes = {}
|
|
self.parents = {}
|
|
self.tags = {}
|
|
|
|
# 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!'))
|
|
|
|
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)
|
|
|
|
tree = self.xml('changes', xml_output=True, summary=True,
|
|
repodir=self.path)
|
|
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):
|
|
self.ui.debug('cleaning up %s\n' % self.tmppath)
|
|
shutil.rmtree(self.tmppath, ignore_errors=True)
|
|
|
|
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)
|
|
|
|
def xml(self, cmd, **kwargs):
|
|
# NOTE: darcs is currently encoding agnostic and will print
|
|
# patch metadata byte-for-byte, even in the XML changelog.
|
|
etree = ElementTree()
|
|
# 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')
|
|
p = self._run(cmd, **kwargs)
|
|
etree.parse(p.stdout, parser=parser)
|
|
p.wait()
|
|
self.checkexit(p.returncode)
|
|
return etree.getroot()
|
|
|
|
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(',')))
|
|
|
|
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
|
|
|
|
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', '')
|
|
# etree can return unicode objects for name, comment, and author,
|
|
# so recode() is used to ensure str objects are emitted.
|
|
return 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])
|
|
|
|
def pull(self, rev):
|
|
output, status = self.run('pull', self.path, all=True,
|
|
match='hash %s' % rev,
|
|
no_test=True, no_posthook=True,
|
|
external_merge='/bin/false',
|
|
repodir=self.tmppath)
|
|
if status:
|
|
if output.find('We have conflicts in') == -1:
|
|
self.checkexit(status, output)
|
|
output, status = self.run('revert', all=True, repodir=self.tmppath)
|
|
self.checkexit(status, output)
|
|
|
|
def getchanges(self, rev, full):
|
|
if full:
|
|
raise util.Abort(_("convert from darcs do not support --full"))
|
|
copies = {}
|
|
changes = []
|
|
man = None
|
|
for elt in self.changes[rev].find('summary').getchildren():
|
|
if elt.tag in ('add_directory', 'remove_directory'):
|
|
continue
|
|
if elt.tag == 'move':
|
|
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
|
|
else:
|
|
changes.append((elt.text.strip(), rev))
|
|
self.pull(rev)
|
|
self.lastrev = rev
|
|
return sorted(changes), copies
|
|
|
|
def getfile(self, name, rev):
|
|
if rev != self.lastrev:
|
|
raise util.Abort(_('internal calling inconsistency'))
|
|
path = os.path.join(self.tmppath, name)
|
|
try:
|
|
data = util.readfile(path)
|
|
mode = os.lstat(path).st_mode
|
|
except IOError, inst:
|
|
if inst.errno == errno.ENOENT:
|
|
return None, None
|
|
raise
|
|
mode = (mode & 0111) and 'x' or ''
|
|
return data, mode
|
|
|
|
def gettags(self):
|
|
return self.tags
|