changeset 5443:58496354773f

Merge with crew Those crew folks are getting lazy about pulling from upstream before committing.
author Matt Mackall <mpm@selenic.com>
date Thu, 11 Oct 2007 00:46:40 -0500
parents be015f9b7405 (current diff) a19167001251 (diff)
children a0952e4e52eb
files
diffstat 8 files changed, 162 insertions(+), 58 deletions(-) [+]
line wrap: on
line diff
--- 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