Merge with crew
authorMatt Mackall <mpm@selenic.com>
Thu, 11 Oct 2007 00:46:40 -0500
changeset 5443 58496354773f
parent 5442 be015f9b7405 (current diff)
parent 5438 a19167001251 (diff)
child 5444 a0952e4e52eb
Merge with crew Those crew folks are getting lazy about pulling from upstream before committing.
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -10,7 +10,7 @@ from cvs import convert_cvs
 from darcs import darcs_source
 from git import convert_git
 from hg import mercurial_source, mercurial_sink
-from subversion import convert_svn, debugsvnlog
+from subversion import svn_source, debugsvnlog
 import filemap
 
 import os, shutil
@@ -19,27 +19,32 @@ from mercurial.i18n import _
 
 commands.norepo += " convert debugsvnlog"
 
-sink_converters = [mercurial_sink]
-source_converters = [convert_cvs, convert_git, convert_svn,
-                     mercurial_source, darcs_source]
-def convertsource(ui, path, **opts):
-    for c in source_converters:
+source_converters = [
+    ('cvs', convert_cvs),
+    ('git', convert_git),
+    ('svn', svn_source),
+    ('hg', mercurial_source),
+    ('darcs', darcs_source),
+    ]
+
+sink_converters = [
+    ('hg', mercurial_sink),
+    ]
+
+def convertsource(ui, path, type, rev):
+    for name, source in source_converters:
         try:
-            return c.getcommit and c(ui, path, **opts)
-        except AttributeError:
-            pass
+            if not type or name == type:
+                return source(ui, path, rev)
         except NoRepo, inst:
             ui.note(_("convert: %s\n") % inst)
     raise util.Abort('%s: unknown repository type' % path)
 
-def convertsink(ui, path):
-    if not os.path.isdir(path):
-        raise util.Abort("%s: not a directory" % path)
-    for c in sink_converters:
+def convertsink(ui, path, type):
+    for name, sink in sink_converters:
         try:
-            return c.putcommit and c(ui, path)
-        except AttributeError:
-            pass
+            if not type or name == type:
+                return sink(ui, path)
         except NoRepo, inst:
             ui.note(_("convert: %s\n") % inst)
     raise util.Abort('%s: unknown repository type' % path)
@@ -350,37 +355,14 @@ def convert(ui, src, dest=None, revmapfi
         dest = hg.defaultdest(src) + "-hg"
         ui.status("assuming destination %s\n" % dest)
 
-    # Try to be smart and initalize things when required
-    created = False
-    if os.path.isdir(dest):
-        if len(os.listdir(dest)) > 0:
-            try:
-                hg.repository(ui, dest)
-                ui.status("destination %s is a Mercurial repository\n" % dest)
-            except hg.RepoError:
-                raise util.Abort(
-                    "destination directory %s is not empty.\n"
-                    "Please specify an empty directory to be initialized\n"
-                    "or an already initialized mercurial repository"
-                    % dest)
-        else:
-            ui.status("initializing destination %s repository\n" % dest)
-            hg.repository(ui, dest, create=True)
-            created = True
-    elif os.path.exists(dest):
-        raise util.Abort("destination %s exists and is not a directory" % dest)
-    else:
-        ui.status("initializing destination %s repository\n" % dest)
-        hg.repository(ui, dest, create=True)
-        created = True
-
-    destc = convertsink(ui, dest)
+    destc = convertsink(ui, dest, opts.get('dest_type'))
 
     try:
-        srcc = convertsource(ui, src, rev=opts.get('rev'))
+        srcc = convertsource(ui, src, opts.get('source_type'),
+                             opts.get('rev'))
     except Exception:
-        if created:
-            shutil.rmtree(dest, True)
+        for path in destc.created:
+            shutil.rmtree(path, True)
         raise
 
     fmap = opts.get('filemap')
@@ -402,8 +384,10 @@ cmdtable = {
     "convert":
         (convert,
          [('A', 'authors', '', 'username mapping filename'),
+          ('d', 'dest-type', '', 'destination repository type'),
           ('', 'filemap', '', 'remap file names using contents of file'),
           ('r', 'rev', '', 'import up to target revision REV'),
+          ('s', 'source-type', '', 'source repository type'),
           ('', 'datesort', None, 'try to sort changesets by date')],
          'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
     "debugsvnlog":
--- a/hgext/convert/common.py
+++ b/hgext/convert/common.py
@@ -20,13 +20,15 @@ class NoRepo(Exception): pass
 SKIPREV = 'SKIP'
 
 class commit(object):
-    def __init__(self, author, date, desc, parents, branch=None, rev=None):
+    def __init__(self, author, date, desc, parents, branch=None, rev=None,
+                 extra={}):
         self.author = author
         self.date = date
         self.desc = desc
         self.parents = parents
         self.branch = branch
         self.rev = rev
+        self.extra = extra
 
 class converter_source(object):
     """Conversion source interface"""
@@ -114,8 +116,13 @@ class converter_sink(object):
 
     def __init__(self, ui, path):
         """Initialize conversion sink (or raise NoRepo("message")
-        exception if path is not a valid repository)"""
-        raise NotImplementedError()
+        exception if path is not a valid repository)
+
+        created is a list of paths to remove if a fatal error occurs
+        later"""
+        self.ui = ui
+        self.path = path
+        self.created = []
 
     def getheads(self):
         """Return a list of this repository's heads"""
--- a/hgext/convert/hg.py
+++ b/hgext/convert/hg.py
@@ -16,16 +16,27 @@ from common import NoRepo, commit, conve
 
 class mercurial_sink(converter_sink):
     def __init__(self, ui, path):
-        self.path = path
-        self.ui = ui
+        converter_sink.__init__(self, ui, path)
         self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
         self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
         self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
         self.lastbranch = None
-        try:
-            self.repo = hg.repository(self.ui, path)
-        except:
-            raise NoRepo("could not open hg repo %s as sink" % path)
+        if os.path.isdir(path) and len(os.listdir(path)) > 0:
+            try:
+                self.repo = hg.repository(self.ui, path)
+                ui.status(_('destination %s is a Mercurial repository\n') %
+                          path)
+            except hg.RepoError, err:
+                ui.print_exc()
+                raise NoRepo(err.args[0])
+        else:
+            try:
+                ui.status(_('initializing destination %s repository\n') % path)
+                self.repo = hg.repository(self.ui, path, create=True)
+                self.created.append(path)
+            except hg.RepoError, err:
+                ui.print_exc()
+                raise NoRepo("could not create hg repo %s as sink" % path)
         self.lock = None
         self.wlock = None
         self.filemapmode = False
@@ -108,7 +119,7 @@ class mercurial_sink(converter_sink):
         p2 = parents.pop(0)
 
         text = commit.desc
-        extra = {}
+        extra = commit.extra.copy()
         if self.branchnames and commit.branch:
             extra['branch'] = commit.branch
         if commit.rev:
@@ -174,7 +185,11 @@ class mercurial_source(converter_source)
         converter_source.__init__(self, ui, path, rev)
         try:
             self.repo = hg.repository(self.ui, path)
-        except:
+            # try to provoke an exception if this isn't really a hg
+            # repo, but some other bogus compatible-looking url
+            self.repo.heads()
+        except hg.RepoError:
+            ui.print_exc()
             raise NoRepo("could not open hg repo %s as source" % path)
         self.lastrev = None
         self.lastctx = None
@@ -226,7 +241,7 @@ class mercurial_source(converter_source)
         parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
         return commit(author=ctx.user(), date=util.datestr(ctx.date()),
                       desc=ctx.description(), parents=parents,
-                      branch=ctx.branch())
+                      branch=ctx.branch(), extra=ctx.extra())
 
     def gettags(self):
         tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
--- a/hgext/convert/subversion.py
+++ b/hgext/convert/subversion.py
@@ -93,9 +93,9 @@ def debugsvnlog(ui, **opts):
     get_log_child(sys.stdout, *args)
 
 # SVN conversion code stolen from bzr-svn and tailor
-class convert_svn(converter_source):
+class svn_source(converter_source):
     def __init__(self, ui, url, rev=None):
-        super(convert_svn, self).__init__(ui, url, rev=rev)
+        super(svn_source, self).__init__(ui, url, rev=rev)
 
         try:
             SubversionException
@@ -128,6 +128,7 @@ class convert_svn(converter_source):
             self.paths = {}
             self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
         except SubversionException, e:
+            ui.print_exc()
             raise NoRepo("couldn't open SVN repo %s" % self.url)
 
         if rev:
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -82,6 +82,7 @@ class changectx(object):
     def files(self): return self._changeset[3]
     def description(self): return self._changeset[4]
     def branch(self): return self._changeset[5].get("branch")
+    def extra(self): return self._changeset[5]
     def tags(self): return self._repo.nodetags(self._node)
 
     def parents(self):
--- a/tests/test-convert
+++ b/tests/test-convert
@@ -3,6 +3,8 @@
 echo "[extensions]" >> $HGRCPATH
 echo "convert=" >> $HGRCPATH
 
+hg help convert
+
 hg init a
 cd a
 echo a > a
@@ -19,3 +21,17 @@ hg ci -d'4 0' -me
 cd ..
 hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
 hg --cwd a-hg pull ../a
+
+touch bogusfile
+echo % should fail
+hg convert a bogusfile
+
+mkdir bogusdir
+chmod 000 bogusdir
+
+echo % should fail
+hg convert a bogusdir
+
+echo % should succeed
+chmod 700 bogusdir
+hg convert a bogusdir
--- a/tests/test-convert-darcs
+++ b/tests/test-convert-darcs
@@ -7,6 +7,7 @@ echo "convert=" >> $HGRCPATH
 echo 'hgext.graphlog =' >> $HGRCPATH
 
 DARCS_EMAIL='test@example.org'; export DARCS_EMAIL
+HOME=do_not_use_HOME_darcs; export HOME
 
 echo % initialize darcs repo
 mkdir darcs-repo
--- a/tests/test-convert.out
+++ b/tests/test-convert.out
@@ -1,3 +1,67 @@
+hg convert [OPTION]... SOURCE [DEST [MAPFILE]]
+
+Convert a foreign SCM repository to a Mercurial one.
+
+    Accepted source formats:
+    - CVS
+    - Darcs
+    - git
+    - Subversion
+
+    Accepted destination formats:
+    - Mercurial
+
+    If no revision is given, all revisions will be converted. Otherwise,
+    convert will only import up to the named revision (given in a format
+    understood by the source).
+
+    If no destination directory name is specified, it defaults to the
+    basename of the source with '-hg' appended.  If the destination
+    repository doesn't exist, it will be created.
+
+    If <revmapfile> isn't given, it will be put in a default location
+    (<dest>/.hg/shamap by default).  The <revmapfile> is a simple text
+    file that maps each source commit ID to the destination ID for
+    that revision, like so:
+    <source ID> <destination ID>
+
+    If the file doesn't exist, it's automatically created.  It's updated
+    on each commit copied, so convert-repo can be interrupted and can
+    be run repeatedly to copy new commits.
+
+    The [username mapping] file is a simple text file that maps each source
+    commit author to a destination commit author. It is handy for source SCMs
+    that use unix logins to identify authors (eg: CVS). One line per author
+    mapping and the line format is:
+    srcauthor=whatever string you want
+
+    The filemap is a file that allows filtering and remapping of files
+    and directories.  Comment lines start with '#'.  Each line can
+    contain one of the following directives:
+
+      include path/to/file
+
+      exclude path/to/file
+
+      rename from/file to/file
+    
+    The 'include' directive causes a file, or all files under a
+    directory, to be included in the destination repository.  The
+    'exclude' directive causes files or directories to be omitted.
+    The 'rename' directive renames a file or directory.  To rename
+    from a subdirectory into the root of the repository, use '.' as
+    the path to rename to.
+
+options:
+
+ -A --authors      username mapping filename
+ -d --dest-type    destination repository type
+    --filemap      remap file names using contents of file
+ -r --rev          import up to target revision REV
+ -s --source-type  source repository type
+    --datesort     try to sort changesets by date
+
+use "hg -v help convert" to show global options
 adding a
 assuming destination a-hg
 initializing destination a-hg repository
@@ -12,3 +76,18 @@ 0 e
 pulling from ../a
 searching for changes
 no changes found
+% should fail
+initializing destination bogusfile repository
+abort: cannot create new bundle repository
+% should fail
+abort: Permission denied: bogusdir
+% should succeed
+initializing destination bogusdir repository
+scanning source...
+sorting...
+converting...
+4 a
+3 b
+2 c
+1 d
+0 e