eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/hgext/convert/hg.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # hg.py - hg backend for convert extension
       
     2 #
       
     3 #  Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
       
     4 #
       
     5 # This software may be used and distributed according to the terms of the
       
     6 # GNU General Public License version 2 or any later version.
       
     7 
       
     8 # Notes for hg->hg conversion:
       
     9 #
       
    10 # * Old versions of Mercurial didn't trim the whitespace from the ends
       
    11 #   of commit messages, but new versions do.  Changesets created by
       
    12 #   those older versions, then converted, may thus have different
       
    13 #   hashes for changesets that are otherwise identical.
       
    14 #
       
    15 # * Using "--config convert.hg.saverev=true" will make the source
       
    16 #   identifier to be stored in the converted revision. This will cause
       
    17 #   the converted revision to have a different identity than the
       
    18 #   source.
       
    19 
       
    20 
       
    21 import os, time, cStringIO
       
    22 from mercurial.i18n import _
       
    23 from mercurial.node import bin, hex, nullid
       
    24 from mercurial import hg, util, context, error
       
    25 
       
    26 from common import NoRepo, commit, converter_source, converter_sink
       
    27 
       
    28 class mercurial_sink(converter_sink):
       
    29     def __init__(self, ui, path):
       
    30         converter_sink.__init__(self, ui, path)
       
    31         self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
       
    32         self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
       
    33         self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
       
    34         self.lastbranch = None
       
    35         if os.path.isdir(path) and len(os.listdir(path)) > 0:
       
    36             try:
       
    37                 self.repo = hg.repository(self.ui, path)
       
    38                 if not self.repo.local():
       
    39                     raise NoRepo(_('%s is not a local Mercurial repository')
       
    40                                  % path)
       
    41             except error.RepoError, err:
       
    42                 ui.traceback()
       
    43                 raise NoRepo(err.args[0])
       
    44         else:
       
    45             try:
       
    46                 ui.status(_('initializing destination %s repository\n') % path)
       
    47                 self.repo = hg.repository(self.ui, path, create=True)
       
    48                 if not self.repo.local():
       
    49                     raise NoRepo(_('%s is not a local Mercurial repository')
       
    50                                  % path)
       
    51                 self.created.append(path)
       
    52             except error.RepoError:
       
    53                 ui.traceback()
       
    54                 raise NoRepo(_("could not create hg repository %s as sink")
       
    55                              % path)
       
    56         self.lock = None
       
    57         self.wlock = None
       
    58         self.filemapmode = False
       
    59 
       
    60     def before(self):
       
    61         self.ui.debug('run hg sink pre-conversion action\n')
       
    62         self.wlock = self.repo.wlock()
       
    63         self.lock = self.repo.lock()
       
    64 
       
    65     def after(self):
       
    66         self.ui.debug('run hg sink post-conversion action\n')
       
    67         if self.lock:
       
    68             self.lock.release()
       
    69         if self.wlock:
       
    70             self.wlock.release()
       
    71 
       
    72     def revmapfile(self):
       
    73         return os.path.join(self.path, ".hg", "shamap")
       
    74 
       
    75     def authorfile(self):
       
    76         return os.path.join(self.path, ".hg", "authormap")
       
    77 
       
    78     def getheads(self):
       
    79         h = self.repo.changelog.heads()
       
    80         return [hex(x) for x in h]
       
    81 
       
    82     def setbranch(self, branch, pbranches):
       
    83         if not self.clonebranches:
       
    84             return
       
    85 
       
    86         setbranch = (branch != self.lastbranch)
       
    87         self.lastbranch = branch
       
    88         if not branch:
       
    89             branch = 'default'
       
    90         pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
       
    91         pbranch = pbranches and pbranches[0][1] or 'default'
       
    92 
       
    93         branchpath = os.path.join(self.path, branch)
       
    94         if setbranch:
       
    95             self.after()
       
    96             try:
       
    97                 self.repo = hg.repository(self.ui, branchpath)
       
    98             except:
       
    99                 self.repo = hg.repository(self.ui, branchpath, create=True)
       
   100             self.before()
       
   101 
       
   102         # pbranches may bring revisions from other branches (merge parents)
       
   103         # Make sure we have them, or pull them.
       
   104         missings = {}
       
   105         for b in pbranches:
       
   106             try:
       
   107                 self.repo.lookup(b[0])
       
   108             except:
       
   109                 missings.setdefault(b[1], []).append(b[0])
       
   110 
       
   111         if missings:
       
   112             self.after()
       
   113             for pbranch, heads in missings.iteritems():
       
   114                 pbranchpath = os.path.join(self.path, pbranch)
       
   115                 prepo = hg.repository(self.ui, pbranchpath)
       
   116                 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
       
   117                 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
       
   118             self.before()
       
   119 
       
   120     def _rewritetags(self, source, revmap, data):
       
   121         fp = cStringIO.StringIO()
       
   122         for line in data.splitlines():
       
   123             s = line.split(' ', 1)
       
   124             if len(s) != 2:
       
   125                 continue
       
   126             revid = revmap.get(source.lookuprev(s[0]))
       
   127             if not revid:
       
   128                 continue
       
   129             fp.write('%s %s\n' % (revid, s[1]))
       
   130         return fp.getvalue()
       
   131 
       
   132     def putcommit(self, files, copies, parents, commit, source, revmap):
       
   133 
       
   134         files = dict(files)
       
   135         def getfilectx(repo, memctx, f):
       
   136             v = files[f]
       
   137             data, mode = source.getfile(f, v)
       
   138             if f == '.hgtags':
       
   139                 data = self._rewritetags(source, revmap, data)
       
   140             return context.memfilectx(f, data, 'l' in mode, 'x' in mode,
       
   141                                       copies.get(f))
       
   142 
       
   143         pl = []
       
   144         for p in parents:
       
   145             if p not in pl:
       
   146                 pl.append(p)
       
   147         parents = pl
       
   148         nparents = len(parents)
       
   149         if self.filemapmode and nparents == 1:
       
   150             m1node = self.repo.changelog.read(bin(parents[0]))[0]
       
   151             parent = parents[0]
       
   152 
       
   153         if len(parents) < 2:
       
   154             parents.append(nullid)
       
   155         if len(parents) < 2:
       
   156             parents.append(nullid)
       
   157         p2 = parents.pop(0)
       
   158 
       
   159         text = commit.desc
       
   160         extra = commit.extra.copy()
       
   161         if self.branchnames and commit.branch:
       
   162             extra['branch'] = commit.branch
       
   163         if commit.rev:
       
   164             extra['convert_revision'] = commit.rev
       
   165 
       
   166         while parents:
       
   167             p1 = p2
       
   168             p2 = parents.pop(0)
       
   169             ctx = context.memctx(self.repo, (p1, p2), text, files.keys(),
       
   170                                  getfilectx, commit.author, commit.date, extra)
       
   171             self.repo.commitctx(ctx)
       
   172             text = "(octopus merge fixup)\n"
       
   173             p2 = hex(self.repo.changelog.tip())
       
   174 
       
   175         if self.filemapmode and nparents == 1:
       
   176             man = self.repo.manifest
       
   177             mnode = self.repo.changelog.read(bin(p2))[0]
       
   178             closed = 'close' in commit.extra
       
   179             if not closed and not man.cmp(m1node, man.revision(mnode)):
       
   180                 self.ui.status(_("filtering out empty revision\n"))
       
   181                 self.repo.rollback()
       
   182                 return parent
       
   183         return p2
       
   184 
       
   185     def puttags(self, tags):
       
   186         try:
       
   187             parentctx = self.repo[self.tagsbranch]
       
   188             tagparent = parentctx.node()
       
   189         except error.RepoError:
       
   190             parentctx = None
       
   191             tagparent = nullid
       
   192 
       
   193         try:
       
   194             oldlines = sorted(parentctx['.hgtags'].data().splitlines(True))
       
   195         except:
       
   196             oldlines = []
       
   197 
       
   198         newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
       
   199         if newlines == oldlines:
       
   200             return None, None
       
   201         data = "".join(newlines)
       
   202         def getfilectx(repo, memctx, f):
       
   203             return context.memfilectx(f, data, False, False, None)
       
   204 
       
   205         self.ui.status(_("updating tags\n"))
       
   206         date = "%s 0" % int(time.mktime(time.gmtime()))
       
   207         extra = {'branch': self.tagsbranch}
       
   208         ctx = context.memctx(self.repo, (tagparent, None), "update tags",
       
   209                              [".hgtags"], getfilectx, "convert-repo", date,
       
   210                              extra)
       
   211         self.repo.commitctx(ctx)
       
   212         return hex(self.repo.changelog.tip()), hex(tagparent)
       
   213 
       
   214     def setfilemapmode(self, active):
       
   215         self.filemapmode = active
       
   216 
       
   217 class mercurial_source(converter_source):
       
   218     def __init__(self, ui, path, rev=None):
       
   219         converter_source.__init__(self, ui, path, rev)
       
   220         self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
       
   221         self.ignored = set()
       
   222         self.saverev = ui.configbool('convert', 'hg.saverev', False)
       
   223         try:
       
   224             self.repo = hg.repository(self.ui, path)
       
   225             # try to provoke an exception if this isn't really a hg
       
   226             # repo, but some other bogus compatible-looking url
       
   227             if not self.repo.local():
       
   228                 raise error.RepoError()
       
   229         except error.RepoError:
       
   230             ui.traceback()
       
   231             raise NoRepo(_("%s is not a local Mercurial repository") % path)
       
   232         self.lastrev = None
       
   233         self.lastctx = None
       
   234         self._changescache = None
       
   235         self.convertfp = None
       
   236         # Restrict converted revisions to startrev descendants
       
   237         startnode = ui.config('convert', 'hg.startrev')
       
   238         if startnode is not None:
       
   239             try:
       
   240                 startnode = self.repo.lookup(startnode)
       
   241             except error.RepoError:
       
   242                 raise util.Abort(_('%s is not a valid start revision')
       
   243                                  % startnode)
       
   244             startrev = self.repo.changelog.rev(startnode)
       
   245             children = {startnode: 1}
       
   246             for rev in self.repo.changelog.descendants(startrev):
       
   247                 children[self.repo.changelog.node(rev)] = 1
       
   248             self.keep = children.__contains__
       
   249         else:
       
   250             self.keep = util.always
       
   251 
       
   252     def changectx(self, rev):
       
   253         if self.lastrev != rev:
       
   254             self.lastctx = self.repo[rev]
       
   255             self.lastrev = rev
       
   256         return self.lastctx
       
   257 
       
   258     def parents(self, ctx):
       
   259         return [p for p in ctx.parents() if p and self.keep(p.node())]
       
   260 
       
   261     def getheads(self):
       
   262         if self.rev:
       
   263             heads = [self.repo[self.rev].node()]
       
   264         else:
       
   265             heads = self.repo.heads()
       
   266         return [hex(h) for h in heads if self.keep(h)]
       
   267 
       
   268     def getfile(self, name, rev):
       
   269         try:
       
   270             fctx = self.changectx(rev)[name]
       
   271             return fctx.data(), fctx.flags()
       
   272         except error.LookupError, err:
       
   273             raise IOError(err)
       
   274 
       
   275     def getchanges(self, rev):
       
   276         ctx = self.changectx(rev)
       
   277         parents = self.parents(ctx)
       
   278         if not parents:
       
   279             files = sorted(ctx.manifest())
       
   280             if self.ignoreerrors:
       
   281                 # calling getcopies() is a simple way to detect missing
       
   282                 # revlogs and populate self.ignored
       
   283                 self.getcopies(ctx, parents, files)
       
   284             return [(f, rev) for f in files if f not in self.ignored], {}
       
   285         if self._changescache and self._changescache[0] == rev:
       
   286             m, a, r = self._changescache[1]
       
   287         else:
       
   288             m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
       
   289         # getcopies() detects missing revlogs early, run it before
       
   290         # filtering the changes.
       
   291         copies = self.getcopies(ctx, parents, m + a)
       
   292         changes = [(name, rev) for name in m + a + r
       
   293                    if name not in self.ignored]
       
   294         return sorted(changes), copies
       
   295 
       
   296     def getcopies(self, ctx, parents, files):
       
   297         copies = {}
       
   298         for name in files:
       
   299             if name in self.ignored:
       
   300                 continue
       
   301             try:
       
   302                 copysource, copynode = ctx.filectx(name).renamed()
       
   303                 if copysource in self.ignored or not self.keep(copynode):
       
   304                     continue
       
   305                 # Ignore copy sources not in parent revisions
       
   306                 found = False
       
   307                 for p in parents:
       
   308                     if copysource in p:
       
   309                         found = True
       
   310                         break
       
   311                 if not found:
       
   312                     continue
       
   313                 copies[name] = copysource
       
   314             except TypeError:
       
   315                 pass
       
   316             except error.LookupError, e:
       
   317                 if not self.ignoreerrors:
       
   318                     raise
       
   319                 self.ignored.add(name)
       
   320                 self.ui.warn(_('ignoring: %s\n') % e)
       
   321         return copies
       
   322 
       
   323     def getcommit(self, rev):
       
   324         ctx = self.changectx(rev)
       
   325         parents = [p.hex() for p in self.parents(ctx)]
       
   326         if self.saverev:
       
   327             crev = rev
       
   328         else:
       
   329             crev = None
       
   330         return commit(author=ctx.user(), date=util.datestr(ctx.date()),
       
   331                       desc=ctx.description(), rev=crev, parents=parents,
       
   332                       branch=ctx.branch(), extra=ctx.extra(),
       
   333                       sortkey=ctx.rev())
       
   334 
       
   335     def gettags(self):
       
   336         tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
       
   337         return dict([(name, hex(node)) for name, node in tags
       
   338                      if self.keep(node)])
       
   339 
       
   340     def getchangedfiles(self, rev, i):
       
   341         ctx = self.changectx(rev)
       
   342         parents = self.parents(ctx)
       
   343         if not parents and i is None:
       
   344             i = 0
       
   345             changes = [], ctx.manifest().keys(), []
       
   346         else:
       
   347             i = i or 0
       
   348             changes = self.repo.status(parents[i].node(), ctx.node())[:3]
       
   349         changes = [[f for f in l if f not in self.ignored] for l in changes]
       
   350 
       
   351         if i == 0:
       
   352             self._changescache = (rev, changes)
       
   353 
       
   354         return changes[0] + changes[1] + changes[2]
       
   355 
       
   356     def converted(self, rev, destrev):
       
   357         if self.convertfp is None:
       
   358             self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'),
       
   359                                   'a')
       
   360         self.convertfp.write('%s %s\n' % (destrev, rev))
       
   361         self.convertfp.flush()
       
   362 
       
   363     def before(self):
       
   364         self.ui.debug('run hg source pre-conversion action\n')
       
   365 
       
   366     def after(self):
       
   367         self.ui.debug('run hg source post-conversion action\n')
       
   368 
       
   369     def hasnativeorder(self):
       
   370         return True
       
   371 
       
   372     def lookuprev(self, rev):
       
   373         try:
       
   374             return hex(self.repo.lookup(rev))
       
   375         except error.RepoError:
       
   376             return None