hgext/convert/__init__.py
changeset 5631 96e16af92f2b
parent 5556 61fdf2558c0a
child 5632 fe2e81229819
equal deleted inserted replaced
5630:44482de04767 5631:96e16af92f2b
     3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
     3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
     4 #
     4 #
     5 # This software may be used and distributed according to the terms
     5 # This software may be used and distributed according to the terms
     6 # of the GNU General Public License, incorporated herein by reference.
     6 # of the GNU General Public License, incorporated herein by reference.
     7 
     7 
     8 from common import NoRepo, SKIPREV, converter_source, converter_sink, mapfile
     8 import convcmd
     9 from cvs import convert_cvs
     9 from mercurial import commands
    10 from darcs import darcs_source
       
    11 from git import convert_git
       
    12 from hg import mercurial_source, mercurial_sink
       
    13 from subversion import debugsvnlog, svn_source, svn_sink
       
    14 import filemap
       
    15 
    10 
    16 import os, shutil
    11 # Commands definition was moved elsewhere to ease demandload job.
    17 from mercurial import hg, ui, util, commands
       
    18 from mercurial.i18n import _
       
    19 
       
    20 commands.norepo += " convert debugsvnlog"
       
    21 
       
    22 source_converters = [
       
    23     ('cvs', convert_cvs),
       
    24     ('git', convert_git),
       
    25     ('svn', svn_source),
       
    26     ('hg', mercurial_source),
       
    27     ('darcs', darcs_source),
       
    28     ]
       
    29 
       
    30 sink_converters = [
       
    31     ('hg', mercurial_sink),
       
    32     ('svn', svn_sink),
       
    33     ]
       
    34 
       
    35 def convertsource(ui, path, type, rev):
       
    36     exceptions = []
       
    37     for name, source in source_converters:
       
    38         try:
       
    39             if not type or name == type:
       
    40                 return source(ui, path, rev)
       
    41         except NoRepo, inst:
       
    42             exceptions.append(inst)
       
    43     if not ui.quiet:
       
    44         for inst in exceptions:
       
    45             ui.write(_("%s\n") % inst)
       
    46     raise util.Abort('%s: unknown repository type' % path)
       
    47 
       
    48 def convertsink(ui, path, type):
       
    49     for name, sink in sink_converters:
       
    50         try:
       
    51             if not type or name == type:
       
    52                 return sink(ui, path)
       
    53         except NoRepo, inst:
       
    54             ui.note(_("convert: %s\n") % inst)
       
    55     raise util.Abort('%s: unknown repository type' % path)
       
    56 
       
    57 class converter(object):
       
    58     def __init__(self, ui, source, dest, revmapfile, opts):
       
    59 
       
    60         self.source = source
       
    61         self.dest = dest
       
    62         self.ui = ui
       
    63         self.opts = opts
       
    64         self.commitcache = {}
       
    65         self.authors = {}
       
    66         self.authorfile = None
       
    67 
       
    68         self.map = mapfile(ui, revmapfile)
       
    69 
       
    70         # Read first the dst author map if any
       
    71         authorfile = self.dest.authorfile()
       
    72         if authorfile and os.path.exists(authorfile):
       
    73             self.readauthormap(authorfile)
       
    74         # Extend/Override with new author map if necessary
       
    75         if opts.get('authors'):
       
    76             self.readauthormap(opts.get('authors'))
       
    77             self.authorfile = self.dest.authorfile()
       
    78 
       
    79     def walktree(self, heads):
       
    80         '''Return a mapping that identifies the uncommitted parents of every
       
    81         uncommitted changeset.'''
       
    82         visit = heads
       
    83         known = {}
       
    84         parents = {}
       
    85         while visit:
       
    86             n = visit.pop(0)
       
    87             if n in known or n in self.map: continue
       
    88             known[n] = 1
       
    89             commit = self.cachecommit(n)
       
    90             parents[n] = []
       
    91             for p in commit.parents:
       
    92                 parents[n].append(p)
       
    93                 visit.append(p)
       
    94 
       
    95         return parents
       
    96 
       
    97     def toposort(self, parents):
       
    98         '''Return an ordering such that every uncommitted changeset is
       
    99         preceeded by all its uncommitted ancestors.'''
       
   100         visit = parents.keys()
       
   101         seen = {}
       
   102         children = {}
       
   103 
       
   104         while visit:
       
   105             n = visit.pop(0)
       
   106             if n in seen: continue
       
   107             seen[n] = 1
       
   108             # Ensure that nodes without parents are present in the 'children'
       
   109             # mapping.
       
   110             children.setdefault(n, [])
       
   111             for p in parents[n]:
       
   112                 if not p in self.map:
       
   113                     visit.append(p)
       
   114                 children.setdefault(p, []).append(n)
       
   115 
       
   116         s = []
       
   117         removed = {}
       
   118         visit = children.keys()
       
   119         while visit:
       
   120             n = visit.pop(0)
       
   121             if n in removed: continue
       
   122             dep = 0
       
   123             if n in parents:
       
   124                 for p in parents[n]:
       
   125                     if p in self.map: continue
       
   126                     if p not in removed:
       
   127                         # we're still dependent
       
   128                         visit.append(n)
       
   129                         dep = 1
       
   130                         break
       
   131 
       
   132             if not dep:
       
   133                 # all n's parents are in the list
       
   134                 removed[n] = 1
       
   135                 if n not in self.map:
       
   136                     s.append(n)
       
   137                 if n in children:
       
   138                     for c in children[n]:
       
   139                         visit.insert(0, c)
       
   140 
       
   141         if self.opts.get('datesort'):
       
   142             depth = {}
       
   143             for n in s:
       
   144                 depth[n] = 0
       
   145                 pl = [p for p in self.commitcache[n].parents
       
   146                       if p not in self.map]
       
   147                 if pl:
       
   148                     depth[n] = max([depth[p] for p in pl]) + 1
       
   149 
       
   150             s = [(depth[n], self.commitcache[n].date, n) for n in s]
       
   151             s.sort()
       
   152             s = [e[2] for e in s]
       
   153 
       
   154         return s
       
   155 
       
   156     def writeauthormap(self):
       
   157         authorfile = self.authorfile
       
   158         if authorfile:
       
   159            self.ui.status('Writing author map file %s\n' % authorfile)
       
   160            ofile = open(authorfile, 'w+')
       
   161            for author in self.authors:
       
   162                ofile.write("%s=%s\n" % (author, self.authors[author]))
       
   163            ofile.close()
       
   164 
       
   165     def readauthormap(self, authorfile):
       
   166         afile = open(authorfile, 'r')
       
   167         for line in afile:
       
   168             try:
       
   169                 srcauthor = line.split('=')[0].strip()
       
   170                 dstauthor = line.split('=')[1].strip()
       
   171                 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
       
   172                     self.ui.status(
       
   173                         'Overriding mapping for author %s, was %s, will be %s\n'
       
   174                         % (srcauthor, self.authors[srcauthor], dstauthor))
       
   175                 else:
       
   176                     self.ui.debug('Mapping author %s to %s\n'
       
   177                                   % (srcauthor, dstauthor))
       
   178                     self.authors[srcauthor] = dstauthor
       
   179             except IndexError:
       
   180                 self.ui.warn(
       
   181                     'Ignoring bad line in author file map %s: %s\n'
       
   182                     % (authorfile, line))
       
   183         afile.close()
       
   184 
       
   185     def cachecommit(self, rev):
       
   186         commit = self.source.getcommit(rev)
       
   187         commit.author = self.authors.get(commit.author, commit.author)
       
   188         self.commitcache[rev] = commit
       
   189         return commit
       
   190 
       
   191     def copy(self, rev):
       
   192         commit = self.commitcache[rev]
       
   193         do_copies = hasattr(self.dest, 'copyfile')
       
   194         filenames = []
       
   195 
       
   196         changes = self.source.getchanges(rev)
       
   197         if isinstance(changes, basestring):
       
   198             if changes == SKIPREV:
       
   199                 dest = SKIPREV
       
   200             else:
       
   201                 dest = self.map[changes]
       
   202             self.map[rev] = dest
       
   203             return
       
   204         files, copies = changes
       
   205         parents = [self.map[r] for r in commit.parents]
       
   206         if commit.parents:
       
   207             prev = commit.parents[0]
       
   208             if prev not in self.commitcache:
       
   209                 self.cachecommit(prev)
       
   210             pbranch = self.commitcache[prev].branch
       
   211         else:
       
   212             pbranch = None
       
   213         self.dest.setbranch(commit.branch, pbranch, parents)
       
   214         for f, v in files:
       
   215             filenames.append(f)
       
   216             try:
       
   217                 data = self.source.getfile(f, v)
       
   218             except IOError, inst:
       
   219                 self.dest.delfile(f)
       
   220             else:
       
   221                 e = self.source.getmode(f, v)
       
   222                 self.dest.putfile(f, e, data)
       
   223                 if do_copies:
       
   224                     if f in copies:
       
   225                         copyf = copies[f]
       
   226                         # Merely marks that a copy happened.
       
   227                         self.dest.copyfile(copyf, f)
       
   228 
       
   229         newnode = self.dest.putcommit(filenames, parents, commit)
       
   230         self.source.converted(rev, newnode)
       
   231         self.map[rev] = newnode
       
   232 
       
   233     def convert(self):
       
   234         try:
       
   235             self.source.before()
       
   236             self.dest.before()
       
   237             self.source.setrevmap(self.map)
       
   238             self.ui.status("scanning source...\n")
       
   239             heads = self.source.getheads()
       
   240             parents = self.walktree(heads)
       
   241             self.ui.status("sorting...\n")
       
   242             t = self.toposort(parents)
       
   243             num = len(t)
       
   244             c = None
       
   245 
       
   246             self.ui.status("converting...\n")
       
   247             for c in t:
       
   248                 num -= 1
       
   249                 desc = self.commitcache[c].desc
       
   250                 if "\n" in desc:
       
   251                     desc = desc.splitlines()[0]
       
   252                 self.ui.status("%d %s\n" % (num, desc))
       
   253                 self.copy(c)
       
   254 
       
   255             tags = self.source.gettags()
       
   256             ctags = {}
       
   257             for k in tags:
       
   258                 v = tags[k]
       
   259                 if self.map.get(v, SKIPREV) != SKIPREV:
       
   260                     ctags[k] = self.map[v]
       
   261 
       
   262             if c and ctags:
       
   263                 nrev = self.dest.puttags(ctags)
       
   264                 # write another hash correspondence to override the previous
       
   265                 # one so we don't end up with extra tag heads
       
   266                 if nrev:
       
   267                     self.map[c] = nrev
       
   268 
       
   269             self.writeauthormap()
       
   270         finally:
       
   271             self.cleanup()
       
   272 
       
   273     def cleanup(self):
       
   274         try:
       
   275             self.dest.after()
       
   276         finally:
       
   277             self.source.after()
       
   278         self.map.close()
       
   279 
    12 
   280 def convert(ui, src, dest=None, revmapfile=None, **opts):
    13 def convert(ui, src, dest=None, revmapfile=None, **opts):
   281     """Convert a foreign SCM repository to a Mercurial one.
    14     """Convert a foreign SCM repository to a Mercurial one.
   282 
    15 
   283     Accepted source formats:
    16     Accepted source formats:
   349     --config convert.svn.tags=tags            (directory name)
    82     --config convert.svn.tags=tags            (directory name)
   350         svn source: specify the directory containing tags
    83         svn source: specify the directory containing tags
   351     --config convert.svn.trunk=trunk          (directory name)
    84     --config convert.svn.trunk=trunk          (directory name)
   352         svn source: specify the name of the trunk branch
    85         svn source: specify the name of the trunk branch
   353     """
    86     """
       
    87     return convcmd.convert(ui, src, dest, revmapfile, **opts)
   354 
    88 
   355     util._encoding = 'UTF-8'
    89 def debugsvnlog(ui, **opts):
       
    90     return convcmd.debugsvnlog(ui, **opts)
   356 
    91 
   357     if not dest:
    92 commands.norepo += " convert debugsvnlog"
   358         dest = hg.defaultdest(src) + "-hg"
       
   359         ui.status("assuming destination %s\n" % dest)
       
   360 
       
   361     destc = convertsink(ui, dest, opts.get('dest_type'))
       
   362 
       
   363     try:
       
   364         srcc = convertsource(ui, src, opts.get('source_type'),
       
   365                              opts.get('rev'))
       
   366     except Exception:
       
   367         for path in destc.created:
       
   368             shutil.rmtree(path, True)
       
   369         raise
       
   370 
       
   371     fmap = opts.get('filemap')
       
   372     if fmap:
       
   373         srcc = filemap.filemap_source(ui, srcc, fmap)
       
   374         destc.setfilemapmode(True)
       
   375 
       
   376     if not revmapfile:
       
   377         try:
       
   378             revmapfile = destc.revmapfile()
       
   379         except:
       
   380             revmapfile = os.path.join(destc, "map")
       
   381 
       
   382     c = converter(ui, srcc, destc, revmapfile, opts)
       
   383     c.convert()
       
   384 
       
   385 
    93 
   386 cmdtable = {
    94 cmdtable = {
   387     "convert":
    95     "convert":
   388         (convert,
    96         (convert,
   389          [('A', 'authors', '', 'username mapping filename'),
    97          [('A', 'authors', '', 'username mapping filename'),
   396     "debugsvnlog":
   104     "debugsvnlog":
   397         (debugsvnlog,
   105         (debugsvnlog,
   398          [],
   106          [],
   399          'hg debugsvnlog'),
   107          'hg debugsvnlog'),
   400 }
   108 }
   401