2007-06-11 07:08:47 +04:00
|
|
|
# common code for the convert extension
|
2007-11-08 04:06:02 +03:00
|
|
|
import base64, errno
|
2008-01-11 06:07:43 +03:00
|
|
|
import os
|
2007-08-06 23:49:26 +04:00
|
|
|
import cPickle as pickle
|
2007-10-31 00:14:15 +03:00
|
|
|
from mercurial import util
|
2007-11-08 05:26:59 +03:00
|
|
|
from mercurial.i18n import _
|
2007-08-06 23:49:26 +04:00
|
|
|
|
|
|
|
def encodeargs(args):
|
|
|
|
def encodearg(s):
|
|
|
|
lines = base64.encodestring(s)
|
|
|
|
lines = [l.splitlines()[0] for l in lines]
|
|
|
|
return ''.join(lines)
|
2007-08-07 12:28:43 +04:00
|
|
|
|
2007-08-06 23:49:26 +04:00
|
|
|
s = pickle.dumps(args)
|
|
|
|
return encodearg(s)
|
|
|
|
|
|
|
|
def decodeargs(s):
|
|
|
|
s = base64.decodestring(s)
|
|
|
|
return pickle.loads(s)
|
2007-06-11 07:08:47 +04:00
|
|
|
|
2007-10-31 00:14:15 +03:00
|
|
|
def checktool(exe, name=None):
|
|
|
|
name = name or exe
|
|
|
|
if not util.find_exe(exe):
|
|
|
|
raise util.Abort('cannot find required "%s" tool' % name)
|
|
|
|
|
2007-06-11 07:08:47 +04:00
|
|
|
class NoRepo(Exception): pass
|
|
|
|
|
2007-10-06 22:30:15 +04:00
|
|
|
SKIPREV = 'SKIP'
|
2007-10-05 06:21:37 +04:00
|
|
|
|
2007-06-11 07:08:47 +04:00
|
|
|
class commit(object):
|
2007-10-11 02:30:00 +04:00
|
|
|
def __init__(self, author, date, desc, parents, branch=None, rev=None,
|
|
|
|
extra={}):
|
2007-07-27 00:34:36 +04:00
|
|
|
self.author = author
|
|
|
|
self.date = date
|
2007-07-28 01:30:20 +04:00
|
|
|
self.desc = desc
|
2007-07-27 00:34:36 +04:00
|
|
|
self.parents = parents
|
|
|
|
self.branch = branch
|
|
|
|
self.rev = rev
|
2007-10-11 02:30:00 +04:00
|
|
|
self.extra = extra
|
2007-06-11 07:08:47 +04:00
|
|
|
|
|
|
|
class converter_source(object):
|
|
|
|
"""Conversion source interface"""
|
|
|
|
|
2007-11-27 20:44:09 +03:00
|
|
|
def __init__(self, ui, path=None, rev=None):
|
2007-06-11 07:08:47 +04:00
|
|
|
"""Initialize conversion source (or raise NoRepo("message")
|
|
|
|
exception if path is not a valid repository)"""
|
2007-07-05 23:08:48 +04:00
|
|
|
self.ui = ui
|
|
|
|
self.path = path
|
|
|
|
self.rev = rev
|
|
|
|
|
|
|
|
self.encoding = 'utf-8'
|
2007-07-05 23:24:26 +04:00
|
|
|
|
2007-10-03 00:46:17 +04:00
|
|
|
def before(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def after(self):
|
|
|
|
pass
|
|
|
|
|
2007-11-08 04:06:02 +03:00
|
|
|
def setrevmap(self, revmap):
|
|
|
|
"""set the map of already-converted revisions"""
|
2007-07-05 23:41:58 +04:00
|
|
|
pass
|
2007-06-11 07:08:47 +04:00
|
|
|
|
|
|
|
def getheads(self):
|
|
|
|
"""Return a list of this repository's heads"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def getfile(self, name, rev):
|
|
|
|
"""Return file contents as a string"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def getmode(self, name, rev):
|
|
|
|
"""Return file mode, eg. '', 'x', or 'l'"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def getchanges(self, version):
|
2007-08-05 23:03:27 +04:00
|
|
|
"""Returns a tuple of (files, copies)
|
|
|
|
Files is a sorted list of (filename, id) tuples for all files changed
|
|
|
|
in version, where id is the source revision id of the file.
|
2007-06-11 07:08:47 +04:00
|
|
|
|
2007-08-05 23:03:27 +04:00
|
|
|
copies is a dictionary of dest: source
|
|
|
|
"""
|
2007-06-11 07:08:47 +04:00
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def getcommit(self, version):
|
|
|
|
"""Return the commit object for version"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def gettags(self):
|
|
|
|
"""Return the tags as a dictionary of name: revision"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
2007-07-01 23:58:08 +04:00
|
|
|
def recode(self, s, encoding=None):
|
|
|
|
if not encoding:
|
2007-07-05 23:08:48 +04:00
|
|
|
encoding = self.encoding or 'utf-8'
|
2007-07-21 12:30:51 +04:00
|
|
|
|
2007-09-07 18:14:51 +04:00
|
|
|
if isinstance(s, unicode):
|
|
|
|
return s.encode("utf-8")
|
2007-07-01 23:58:08 +04:00
|
|
|
try:
|
|
|
|
return s.decode(encoding).encode("utf-8")
|
|
|
|
except:
|
|
|
|
try:
|
|
|
|
return s.decode("latin-1").encode("utf-8")
|
|
|
|
except:
|
|
|
|
return s.decode(encoding, "replace").encode("utf-8")
|
|
|
|
|
2007-10-05 06:21:37 +04:00
|
|
|
def getchangedfiles(self, rev, i):
|
|
|
|
"""Return the files changed by rev compared to parent[i].
|
2007-12-29 21:49:48 +03:00
|
|
|
|
2007-10-05 06:21:37 +04:00
|
|
|
i is an index selecting one of the parents of rev. The return
|
|
|
|
value should be the list of files that are different in rev and
|
|
|
|
this parent.
|
|
|
|
|
|
|
|
If rev has no parents, i is None.
|
2007-12-29 21:49:48 +03:00
|
|
|
|
2007-10-05 06:21:37 +04:00
|
|
|
This function is only needed to support --filemap
|
|
|
|
"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
2007-11-27 04:23:20 +03:00
|
|
|
def converted(self, rev, sinkrev):
|
|
|
|
'''Notify the source that a revision has been converted.'''
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2007-06-11 07:08:47 +04:00
|
|
|
class converter_sink(object):
|
|
|
|
"""Conversion sink (target) interface"""
|
|
|
|
|
|
|
|
def __init__(self, ui, path):
|
|
|
|
"""Initialize conversion sink (or raise NoRepo("message")
|
2007-10-11 02:42:00 +04:00
|
|
|
exception if path is not a valid repository)
|
|
|
|
|
|
|
|
created is a list of paths to remove if a fatal error occurs
|
|
|
|
later"""
|
2007-10-11 02:30:00 +04:00
|
|
|
self.ui = ui
|
2007-10-11 02:42:00 +04:00
|
|
|
self.path = path
|
|
|
|
self.created = []
|
2007-06-11 07:08:47 +04:00
|
|
|
|
|
|
|
def getheads(self):
|
|
|
|
"""Return a list of this repository's heads"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
2007-07-27 00:34:36 +04:00
|
|
|
def revmapfile(self):
|
2007-06-11 07:08:47 +04:00
|
|
|
"""Path to a file that will contain lines
|
|
|
|
source_rev_id sink_rev_id
|
|
|
|
mapping equivalent revision identifiers for each system."""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
2007-06-15 01:25:55 +04:00
|
|
|
def authorfile(self):
|
|
|
|
"""Path to a file that will contain lines
|
|
|
|
srcauthor=dstauthor
|
|
|
|
mapping equivalent authors identifiers for each system."""
|
2007-06-15 03:12:08 +04:00
|
|
|
return None
|
2007-06-15 01:25:55 +04:00
|
|
|
|
2007-06-11 07:08:47 +04:00
|
|
|
def putfile(self, f, e, data):
|
|
|
|
"""Put file for next putcommit().
|
|
|
|
f: path to file
|
|
|
|
e: '', 'x', or 'l' (regular file, executable, or symlink)
|
|
|
|
data: file contents"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def delfile(self, f):
|
|
|
|
"""Delete file for next putcommit().
|
|
|
|
f: path to file"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def putcommit(self, files, parents, commit):
|
|
|
|
"""Create a revision with all changed files listed in 'files'
|
|
|
|
and having listed parents. 'commit' is a commit object containing
|
|
|
|
at a minimum the author, date, and message for this changeset.
|
|
|
|
Called after putfile() and delfile() calls. Note that the sink
|
|
|
|
repository is not told to update itself to a particular revision
|
|
|
|
(or even what that revision would be) before it receives the
|
|
|
|
file data."""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def puttags(self, tags):
|
|
|
|
"""Put tags into sink.
|
|
|
|
tags: {tagname: sink_rev_id, ...}"""
|
|
|
|
raise NotImplementedError()
|
2007-08-06 23:49:26 +04:00
|
|
|
|
2007-08-16 00:21:23 +04:00
|
|
|
def setbranch(self, branch, pbranch, parents):
|
|
|
|
"""Set the current branch name. Called before the first putfile
|
|
|
|
on the branch.
|
|
|
|
branch: branch name for subsequent commits
|
|
|
|
pbranch: branch name of parent commit
|
|
|
|
parents: destination revisions of parent"""
|
|
|
|
pass
|
convert: add a mode where mercurial_sink skips empty revisions.
The getchanges function of some converter_source classes can return
some false positives. I.e. they sometimes claim that a file "foo"
was changed in some revision, even though its contents are still the
same.
convert_svn is particularly bad, but I think this can also happen with
convert_cvs and, at least in theory, with mercurial_source.
For regular conversions this is not really a problem - as long as
getfile returns the right contents, we'll get a converted revision
with the right contents. But when we use --filemap, this could lead
to superfluous revisions being converted.
Instead of fixing every converter_source, I decided to change
mercurial_sink to work around this problem.
When --filemap is used, we're interested only in revisions that touch
some specific files. If a revision doesn't change any of these files,
then we're not interested in it (at least for revisions with a single
parent; merges are special).
For mercurial_sink, we abuse this property and rollback a commit if
the manifest text hasn't changed. This avoids duplicating the logic
from localrepo.filecommit to detect unchanged files.
2007-10-05 06:21:37 +04:00
|
|
|
|
|
|
|
def setfilemapmode(self, active):
|
|
|
|
"""Tell the destination that we're using a filemap
|
|
|
|
|
|
|
|
Some converter_sources (svn in particular) can claim that a file
|
|
|
|
was changed in a revision, even if there was no change. This method
|
|
|
|
tells the destination that we're using a filemap and that it should
|
|
|
|
filter empty revisions.
|
|
|
|
"""
|
|
|
|
pass
|
2007-11-08 04:06:02 +03:00
|
|
|
|
2007-11-08 04:40:39 +03:00
|
|
|
def before(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def after(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class commandline(object):
|
|
|
|
def __init__(self, ui, command):
|
|
|
|
self.ui = ui
|
|
|
|
self.command = command
|
|
|
|
|
|
|
|
def prerun(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def postrun(self):
|
|
|
|
pass
|
|
|
|
|
2008-01-11 06:07:43 +03:00
|
|
|
def _cmdline(self, cmd, *args, **kwargs):
|
2007-11-08 04:40:39 +03:00
|
|
|
cmdline = [self.command, cmd] + list(args)
|
|
|
|
for k, v in kwargs.iteritems():
|
|
|
|
if len(k) == 1:
|
|
|
|
cmdline.append('-' + k)
|
|
|
|
else:
|
|
|
|
cmdline.append('--' + k.replace('_', '-'))
|
|
|
|
try:
|
|
|
|
if len(k) == 1:
|
|
|
|
cmdline.append('' + v)
|
|
|
|
else:
|
|
|
|
cmdline[-1] += '=' + v
|
|
|
|
except TypeError:
|
|
|
|
pass
|
|
|
|
cmdline = [util.shellquote(arg) for arg in cmdline]
|
|
|
|
cmdline += ['<', util.nulldev]
|
2007-11-10 19:09:56 +03:00
|
|
|
cmdline = ' '.join(cmdline)
|
2007-11-08 04:40:39 +03:00
|
|
|
self.ui.debug(cmdline, '\n')
|
2008-01-11 06:07:43 +03:00
|
|
|
return cmdline
|
2007-11-08 04:40:39 +03:00
|
|
|
|
2008-01-11 06:07:43 +03:00
|
|
|
def _run(self, cmd, *args, **kwargs):
|
|
|
|
cmdline = self._cmdline(cmd, *args, **kwargs)
|
2007-11-08 04:40:39 +03:00
|
|
|
self.prerun()
|
|
|
|
try:
|
|
|
|
return util.popen(cmdline)
|
|
|
|
finally:
|
|
|
|
self.postrun()
|
|
|
|
|
|
|
|
def run(self, cmd, *args, **kwargs):
|
|
|
|
fp = self._run(cmd, *args, **kwargs)
|
|
|
|
output = fp.read()
|
|
|
|
self.ui.debug(output)
|
|
|
|
return output, fp.close()
|
|
|
|
|
|
|
|
def checkexit(self, status, output=''):
|
|
|
|
if status:
|
|
|
|
if output:
|
|
|
|
self.ui.warn(_('%s error:\n') % self.command)
|
|
|
|
self.ui.warn(output)
|
|
|
|
msg = util.explain_exit(status)[0]
|
|
|
|
raise util.Abort(_('%s %s') % (self.command, msg))
|
|
|
|
|
|
|
|
def run0(self, cmd, *args, **kwargs):
|
|
|
|
output, status = self.run(cmd, *args, **kwargs)
|
|
|
|
self.checkexit(status, output)
|
|
|
|
return output
|
|
|
|
|
2008-01-11 06:07:43 +03:00
|
|
|
def getargmax(self):
|
|
|
|
if '_argmax' in self.__dict__:
|
|
|
|
return self._argmax
|
|
|
|
|
|
|
|
# POSIX requires at least 4096 bytes for ARG_MAX
|
|
|
|
self._argmax = 4096
|
|
|
|
try:
|
|
|
|
self._argmax = os.sysconf("SC_ARG_MAX")
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
# Windows shells impose their own limits on command line length,
|
|
|
|
# down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
|
|
|
|
# for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
|
|
|
|
# details about cmd.exe limitations.
|
|
|
|
|
|
|
|
# Since ARG_MAX is for command line _and_ environment, lower our limit
|
|
|
|
# (and make happy Windows shells while doing this).
|
|
|
|
|
|
|
|
self._argmax = self._argmax/2 - 1
|
|
|
|
return self._argmax
|
|
|
|
|
|
|
|
def limit_arglist(self, arglist, cmd, *args, **kwargs):
|
|
|
|
limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
|
|
|
|
bytes = 0
|
|
|
|
fl = []
|
|
|
|
for fn in arglist:
|
|
|
|
b = len(fn) + 3
|
|
|
|
if bytes + b < limit or len(fl) == 0:
|
|
|
|
fl.append(fn)
|
|
|
|
bytes += b
|
|
|
|
else:
|
|
|
|
yield fl
|
|
|
|
fl = [fn]
|
|
|
|
bytes = b
|
|
|
|
if fl:
|
|
|
|
yield fl
|
|
|
|
|
|
|
|
def xargs(self, arglist, cmd, *args, **kwargs):
|
|
|
|
for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
|
|
|
|
self.run0(cmd, *(list(args) + l), **kwargs)
|
2007-11-08 04:06:02 +03:00
|
|
|
|
|
|
|
class mapfile(dict):
|
|
|
|
def __init__(self, ui, path):
|
|
|
|
super(mapfile, self).__init__()
|
|
|
|
self.ui = ui
|
|
|
|
self.path = path
|
|
|
|
self.fp = None
|
|
|
|
self.order = []
|
|
|
|
self._read()
|
|
|
|
|
|
|
|
def _read(self):
|
|
|
|
try:
|
|
|
|
fp = open(self.path, 'r')
|
|
|
|
except IOError, err:
|
|
|
|
if err.errno != errno.ENOENT:
|
|
|
|
raise
|
|
|
|
return
|
|
|
|
for line in fp:
|
|
|
|
key, value = line[:-1].split(' ', 1)
|
|
|
|
if key not in self:
|
|
|
|
self.order.append(key)
|
|
|
|
super(mapfile, self).__setitem__(key, value)
|
|
|
|
fp.close()
|
2007-12-29 21:49:48 +03:00
|
|
|
|
2007-11-08 04:06:02 +03:00
|
|
|
def __setitem__(self, key, value):
|
|
|
|
if self.fp is None:
|
|
|
|
try:
|
|
|
|
self.fp = open(self.path, 'a')
|
|
|
|
except IOError, err:
|
|
|
|
raise util.Abort(_('could not open map file %r: %s') %
|
|
|
|
(self.path, err.strerror))
|
|
|
|
self.fp.write('%s %s\n' % (key, value))
|
|
|
|
self.fp.flush()
|
|
|
|
super(mapfile, self).__setitem__(key, value)
|
|
|
|
|
|
|
|
def close(self):
|
2007-11-08 04:40:39 +03:00
|
|
|
if self.fp:
|
|
|
|
self.fp.close()
|
|
|
|
self.fp = None
|