hgext/convert/convcmd.py
changeset 5621 badbefa55972
parent 5521 03496d4fa509
child 5632 fe2e81229819
child 5934 e495f3f35b2d
equal deleted inserted replaced
5617:924fd86f0579 5621:badbefa55972
       
     1 # convcmd - convert extension commands definition
       
     2 #
       
     3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
       
     4 #
       
     5 # This software may be used and distributed according to the terms
       
     6 # of the GNU General Public License, incorporated herein by reference.
       
     7 
       
     8 from common import NoRepo, SKIPREV, converter_source, converter_sink
       
     9 from cvs import convert_cvs
       
    10 from darcs import darcs_source
       
    11 from git import convert_git
       
    12 from hg import mercurial_source, mercurial_sink
       
    13 from subversion import svn_source, debugsvnlog
       
    14 import filemap
       
    15 
       
    16 import os, shutil
       
    17 from mercurial import hg, util
       
    18 from mercurial.i18n import _
       
    19 
       
    20 source_converters = [
       
    21     ('cvs', convert_cvs),
       
    22     ('git', convert_git),
       
    23     ('svn', svn_source),
       
    24     ('hg', mercurial_source),
       
    25     ('darcs', darcs_source),
       
    26     ]
       
    27 
       
    28 sink_converters = [
       
    29     ('hg', mercurial_sink),
       
    30     ]
       
    31 
       
    32 def convertsource(ui, path, type, rev):
       
    33     exceptions = []
       
    34     for name, source in source_converters:
       
    35         try:
       
    36             if not type or name == type:
       
    37                 return source(ui, path, rev)
       
    38         except NoRepo, inst:
       
    39             exceptions.append(inst)
       
    40     if not ui.quiet:
       
    41         for inst in exceptions:
       
    42             ui.write(_("%s\n") % inst)
       
    43     raise util.Abort('%s: unknown repository type' % path)
       
    44 
       
    45 def convertsink(ui, path, type):
       
    46     for name, sink in sink_converters:
       
    47         try:
       
    48             if not type or name == type:
       
    49                 return sink(ui, path)
       
    50         except NoRepo, inst:
       
    51             ui.note(_("convert: %s\n") % inst)
       
    52     raise util.Abort('%s: unknown repository type' % path)
       
    53 
       
    54 class converter(object):
       
    55     def __init__(self, ui, source, dest, revmapfile, opts):
       
    56 
       
    57         self.source = source
       
    58         self.dest = dest
       
    59         self.ui = ui
       
    60         self.opts = opts
       
    61         self.commitcache = {}
       
    62         self.revmapfile = revmapfile
       
    63         self.revmapfilefd = None
       
    64         self.authors = {}
       
    65         self.authorfile = None
       
    66 
       
    67         self.maporder = []
       
    68         self.map = {}
       
    69         try:
       
    70             origrevmapfile = open(self.revmapfile, 'r')
       
    71             for l in origrevmapfile:
       
    72                 sv, dv = l[:-1].split()
       
    73                 if sv not in self.map:
       
    74                     self.maporder.append(sv)
       
    75                 self.map[sv] = dv
       
    76             origrevmapfile.close()
       
    77         except IOError:
       
    78             pass
       
    79 
       
    80         # Read first the dst author map if any
       
    81         authorfile = self.dest.authorfile()
       
    82         if authorfile and os.path.exists(authorfile):
       
    83             self.readauthormap(authorfile)
       
    84         # Extend/Override with new author map if necessary
       
    85         if opts.get('authors'):
       
    86             self.readauthormap(opts.get('authors'))
       
    87             self.authorfile = self.dest.authorfile()
       
    88 
       
    89     def walktree(self, heads):
       
    90         '''Return a mapping that identifies the uncommitted parents of every
       
    91         uncommitted changeset.'''
       
    92         visit = heads
       
    93         known = {}
       
    94         parents = {}
       
    95         while visit:
       
    96             n = visit.pop(0)
       
    97             if n in known or n in self.map: continue
       
    98             known[n] = 1
       
    99             commit = self.cachecommit(n)
       
   100             parents[n] = []
       
   101             for p in commit.parents:
       
   102                 parents[n].append(p)
       
   103                 visit.append(p)
       
   104 
       
   105         return parents
       
   106 
       
   107     def toposort(self, parents):
       
   108         '''Return an ordering such that every uncommitted changeset is
       
   109         preceeded by all its uncommitted ancestors.'''
       
   110         visit = parents.keys()
       
   111         seen = {}
       
   112         children = {}
       
   113 
       
   114         while visit:
       
   115             n = visit.pop(0)
       
   116             if n in seen: continue
       
   117             seen[n] = 1
       
   118             # Ensure that nodes without parents are present in the 'children'
       
   119             # mapping.
       
   120             children.setdefault(n, [])
       
   121             for p in parents[n]:
       
   122                 if not p in self.map:
       
   123                     visit.append(p)
       
   124                 children.setdefault(p, []).append(n)
       
   125 
       
   126         s = []
       
   127         removed = {}
       
   128         visit = children.keys()
       
   129         while visit:
       
   130             n = visit.pop(0)
       
   131             if n in removed: continue
       
   132             dep = 0
       
   133             if n in parents:
       
   134                 for p in parents[n]:
       
   135                     if p in self.map: continue
       
   136                     if p not in removed:
       
   137                         # we're still dependent
       
   138                         visit.append(n)
       
   139                         dep = 1
       
   140                         break
       
   141 
       
   142             if not dep:
       
   143                 # all n's parents are in the list
       
   144                 removed[n] = 1
       
   145                 if n not in self.map:
       
   146                     s.append(n)
       
   147                 if n in children:
       
   148                     for c in children[n]:
       
   149                         visit.insert(0, c)
       
   150 
       
   151         if self.opts.get('datesort'):
       
   152             depth = {}
       
   153             for n in s:
       
   154                 depth[n] = 0
       
   155                 pl = [p for p in self.commitcache[n].parents
       
   156                       if p not in self.map]
       
   157                 if pl:
       
   158                     depth[n] = max([depth[p] for p in pl]) + 1
       
   159 
       
   160             s = [(depth[n], self.commitcache[n].date, n) for n in s]
       
   161             s.sort()
       
   162             s = [e[2] for e in s]
       
   163 
       
   164         return s
       
   165 
       
   166     def mapentry(self, src, dst):
       
   167         if self.revmapfilefd is None:
       
   168             try:
       
   169                 self.revmapfilefd = open(self.revmapfile, "a")
       
   170             except IOError, (errno, strerror):
       
   171                 raise util.Abort("Could not open map file %s: %s, %s\n" % (self.revmapfile, errno, strerror))
       
   172         self.map[src] = dst
       
   173         self.revmapfilefd.write("%s %s\n" % (src, dst))
       
   174         self.revmapfilefd.flush()
       
   175 
       
   176     def writeauthormap(self):
       
   177         authorfile = self.authorfile
       
   178         if authorfile:
       
   179            self.ui.status('Writing author map file %s\n' % authorfile)
       
   180            ofile = open(authorfile, 'w+')
       
   181            for author in self.authors:
       
   182                ofile.write("%s=%s\n" % (author, self.authors[author]))
       
   183            ofile.close()
       
   184 
       
   185     def readauthormap(self, authorfile):
       
   186         afile = open(authorfile, 'r')
       
   187         for line in afile:
       
   188             try:
       
   189                 srcauthor = line.split('=')[0].strip()
       
   190                 dstauthor = line.split('=')[1].strip()
       
   191                 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
       
   192                     self.ui.status(
       
   193                         'Overriding mapping for author %s, was %s, will be %s\n'
       
   194                         % (srcauthor, self.authors[srcauthor], dstauthor))
       
   195                 else:
       
   196                     self.ui.debug('Mapping author %s to %s\n'
       
   197                                   % (srcauthor, dstauthor))
       
   198                     self.authors[srcauthor] = dstauthor
       
   199             except IndexError:
       
   200                 self.ui.warn(
       
   201                     'Ignoring bad line in author file map %s: %s\n'
       
   202                     % (authorfile, line))
       
   203         afile.close()
       
   204 
       
   205     def cachecommit(self, rev):
       
   206         commit = self.source.getcommit(rev)
       
   207         commit.author = self.authors.get(commit.author, commit.author)
       
   208         self.commitcache[rev] = commit
       
   209         return commit
       
   210 
       
   211     def copy(self, rev):
       
   212         commit = self.commitcache[rev]
       
   213         do_copies = hasattr(self.dest, 'copyfile')
       
   214         filenames = []
       
   215 
       
   216         changes = self.source.getchanges(rev)
       
   217         if isinstance(changes, basestring):
       
   218             if changes == SKIPREV:
       
   219                 dest = SKIPREV
       
   220             else:
       
   221                 dest = self.map[changes]
       
   222             self.mapentry(rev, dest)
       
   223             return
       
   224         files, copies = changes
       
   225         parents = [self.map[r] for r in commit.parents]
       
   226         if commit.parents:
       
   227             prev = commit.parents[0]
       
   228             if prev not in self.commitcache:
       
   229                 self.cachecommit(prev)
       
   230             pbranch = self.commitcache[prev].branch
       
   231         else:
       
   232             pbranch = None
       
   233         self.dest.setbranch(commit.branch, pbranch, parents)
       
   234         for f, v in files:
       
   235             filenames.append(f)
       
   236             try:
       
   237                 data = self.source.getfile(f, v)
       
   238             except IOError, inst:
       
   239                 self.dest.delfile(f)
       
   240             else:
       
   241                 e = self.source.getmode(f, v)
       
   242                 self.dest.putfile(f, e, data)
       
   243                 if do_copies:
       
   244                     if f in copies:
       
   245                         copyf = copies[f]
       
   246                         # Merely marks that a copy happened.
       
   247                         self.dest.copyfile(copyf, f)
       
   248 
       
   249         newnode = self.dest.putcommit(filenames, parents, commit)
       
   250         self.mapentry(rev, newnode)
       
   251 
       
   252     def convert(self):
       
   253         try:
       
   254             self.source.before()
       
   255             self.dest.before()
       
   256             self.source.setrevmap(self.map, self.maporder)
       
   257             self.ui.status("scanning source...\n")
       
   258             heads = self.source.getheads()
       
   259             parents = self.walktree(heads)
       
   260             self.ui.status("sorting...\n")
       
   261             t = self.toposort(parents)
       
   262             num = len(t)
       
   263             c = None
       
   264 
       
   265             self.ui.status("converting...\n")
       
   266             for c in t:
       
   267                 num -= 1
       
   268                 desc = self.commitcache[c].desc
       
   269                 if "\n" in desc:
       
   270                     desc = desc.splitlines()[0]
       
   271                 self.ui.status("%d %s\n" % (num, desc))
       
   272                 self.copy(c)
       
   273 
       
   274             tags = self.source.gettags()
       
   275             ctags = {}
       
   276             for k in tags:
       
   277                 v = tags[k]
       
   278                 if self.map.get(v, SKIPREV) != SKIPREV:
       
   279                     ctags[k] = self.map[v]
       
   280 
       
   281             if c and ctags:
       
   282                 nrev = self.dest.puttags(ctags)
       
   283                 # write another hash correspondence to override the previous
       
   284                 # one so we don't end up with extra tag heads
       
   285                 if nrev:
       
   286                     self.mapentry(c, nrev)
       
   287 
       
   288             self.writeauthormap()
       
   289         finally:
       
   290             self.cleanup()
       
   291 
       
   292     def cleanup(self):
       
   293         try:
       
   294             self.dest.after()
       
   295         finally:
       
   296             self.source.after()
       
   297         if self.revmapfilefd:
       
   298             self.revmapfilefd.close()
       
   299 
       
   300 def convert(ui, src, dest=None, revmapfile=None, **opts):
       
   301     util._encoding = 'UTF-8'
       
   302 
       
   303     if not dest:
       
   304         dest = hg.defaultdest(src) + "-hg"
       
   305         ui.status("assuming destination %s\n" % dest)
       
   306 
       
   307     destc = convertsink(ui, dest, opts.get('dest_type'))
       
   308 
       
   309     try:
       
   310         srcc = convertsource(ui, src, opts.get('source_type'),
       
   311                              opts.get('rev'))
       
   312     except Exception:
       
   313         for path in destc.created:
       
   314             shutil.rmtree(path, True)
       
   315         raise
       
   316 
       
   317     fmap = opts.get('filemap')
       
   318     if fmap:
       
   319         srcc = filemap.filemap_source(ui, srcc, fmap)
       
   320         destc.setfilemapmode(True)
       
   321 
       
   322     if not revmapfile:
       
   323         try:
       
   324             revmapfile = destc.revmapfile()
       
   325         except:
       
   326             revmapfile = os.path.join(destc, "map")
       
   327 
       
   328     c = converter(ui, srcc, destc, revmapfile, opts)
       
   329     c.convert()
       
   330