hgext/convert/filemap.py
changeset 5377 756a43a30e34
parent 5376 d60a067227a5
child 5401 4c555dd167dd
equal deleted inserted replaced
5376:d60a067227a5 5377:756a43a30e34
     1 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
     1 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
       
     2 # Copyright 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
     2 #
     3 #
     3 # This software may be used and distributed according to the terms of
     4 # This software may be used and distributed according to the terms of
     4 # the GNU General Public License, incorporated herein by reference.
     5 # the GNU General Public License, incorporated herein by reference.
     5 
     6 
     6 import shlex
     7 import shlex
     7 from mercurial.i18n import _
     8 from mercurial.i18n import _
     8 from mercurial import util
     9 from mercurial import util
       
    10 from common import SKIPREV
     9 
    11 
    10 def rpairs(name):
    12 def rpairs(name):
    11     e = len(name)
    13     e = len(name)
    12     while e != -1:
    14     while e != -1:
    13         yield name[:e], name[e+1:]
    15         yield name[:e], name[e+1:]
    90             return newpre
    92             return newpre
    91         return name
    93         return name
    92 
    94 
    93     def active(self):
    95     def active(self):
    94         return bool(self.include or self.exclude or self.rename)
    96         return bool(self.include or self.exclude or self.rename)
       
    97 
       
    98 # This class does two additional things compared to a regular source:
       
    99 #
       
   100 # - Filter and rename files.  This is mostly wrapped by the filemapper
       
   101 #   class above. We hide the original filename in the revision that is
       
   102 #   returned by getchanges to be able to find things later in getfile
       
   103 #   and getmode.
       
   104 #
       
   105 # - Return only revisions that matter for the files we're interested in.
       
   106 #   This involves rewriting the parents of the original revision to
       
   107 #   create a graph that is restricted to those revisions.
       
   108 #
       
   109 #   This set of revisions includes not only revisions that directly
       
   110 #   touch files we're interested in, but also merges that merge two
       
   111 #   or more interesting revisions.
       
   112 
       
   113 class filemap_source(object):
       
   114     def __init__(self, ui, baseconverter, filemap):
       
   115         self.ui = ui
       
   116         self.base = baseconverter
       
   117         self.filemapper = filemapper(ui, filemap)
       
   118         self.commits = {}
       
   119         # if a revision rev has parent p in the original revision graph, then
       
   120         # rev will have parent self.parentmap[p] in the restricted graph.
       
   121         self.parentmap = {}
       
   122         # self.wantedancestors[rev] is the set of all ancestors of rev that
       
   123         # are in the restricted graph.
       
   124         self.wantedancestors = {}
       
   125         self.convertedorder = None
       
   126         self._rebuilt = False
       
   127         self.origparents = {}
       
   128 
       
   129     def setrevmap(self, revmap, order):
       
   130         # rebuild our state to make things restartable
       
   131         #
       
   132         # To avoid calling getcommit for every revision that has already
       
   133         # been converted, we rebuild only the parentmap, delaying the
       
   134         # rebuild of wantedancestors until we need it (i.e. until a
       
   135         # merge).
       
   136         #
       
   137         # We assume the order argument lists the revisions in
       
   138         # topological order, so that we can infer which revisions were
       
   139         # wanted by previous runs.
       
   140         self._rebuilt = not revmap
       
   141         seen = {SKIPREV: SKIPREV}
       
   142         dummyset = util.set()
       
   143         converted = []
       
   144         for rev in order:
       
   145             mapped = revmap[rev]
       
   146             wanted = mapped not in seen
       
   147             if wanted:
       
   148                 seen[mapped] = rev
       
   149                 self.parentmap[rev] = rev
       
   150             else:
       
   151                 self.parentmap[rev] = seen[mapped]
       
   152             self.wantedancestors[rev] = dummyset
       
   153             arg = seen[mapped]
       
   154             if arg == SKIPREV:
       
   155                 arg = None
       
   156             converted.append((rev, wanted, arg))
       
   157         self.convertedorder = converted
       
   158         return self.base.setrevmap(revmap, order)
       
   159 
       
   160     def rebuild(self):
       
   161         if self._rebuilt:
       
   162             return True
       
   163         self._rebuilt = True
       
   164         pmap = self.parentmap.copy()
       
   165         self.parentmap.clear()
       
   166         self.wantedancestors.clear()
       
   167         for rev, wanted, arg in self.convertedorder:
       
   168             parents = self.origparents.get(rev)
       
   169             if parents is None:
       
   170                 parents = self.base.getcommit(rev).parents
       
   171             if wanted:
       
   172                 self.mark_wanted(rev, parents)
       
   173             else:
       
   174                 self.mark_not_wanted(rev, arg)
       
   175 
       
   176         assert pmap == self.parentmap
       
   177         return True
       
   178 
       
   179     def getheads(self):
       
   180         return self.base.getheads()
       
   181 
       
   182     def getcommit(self, rev):
       
   183         # We want to save a reference to the commit objects to be able
       
   184         # to rewrite their parents later on.
       
   185         self.commits[rev] = self.base.getcommit(rev)
       
   186         return self.commits[rev]
       
   187 
       
   188     def wanted(self, rev, i):
       
   189         # Return True if we're directly interested in rev.
       
   190         #
       
   191         # i is an index selecting one of the parents of rev (if rev
       
   192         # has no parents, i is None).  getchangedfiles will give us
       
   193         # the list of files that are different in rev and in the parent
       
   194         # indicated by i.  If we're interested in any of these files,
       
   195         # we're interested in rev.
       
   196         try:
       
   197             files = self.base.getchangedfiles(rev, i)
       
   198         except NotImplementedError:
       
   199             raise util.Abort(_("source repository doesn't support --filemap"))
       
   200         for f in files:
       
   201             if self.filemapper(f):
       
   202                 return True
       
   203         return False
       
   204 
       
   205     def mark_not_wanted(self, rev, p):
       
   206         # Mark rev as not interesting and update data structures.
       
   207 
       
   208         if p is None:
       
   209             # A root revision. Use SKIPREV to indicate that it doesn't
       
   210             # map to any revision in the restricted graph.  Put SKIPREV
       
   211             # in the set of wanted ancestors to simplify code elsewhere
       
   212             self.parentmap[rev] = SKIPREV
       
   213             self.wantedancestors[rev] = util.set((SKIPREV,))
       
   214             return
       
   215 
       
   216         # Reuse the data from our parent.
       
   217         self.parentmap[rev] = self.parentmap[p]
       
   218         self.wantedancestors[rev] = self.wantedancestors[p]
       
   219 
       
   220     def mark_wanted(self, rev, parents):
       
   221         # Mark rev ss wanted and update data structures.
       
   222 
       
   223         # rev will be in the restricted graph, so children of rev in
       
   224         # the original graph should still have rev as a parent in the
       
   225         # restricted graph.
       
   226         self.parentmap[rev] = rev
       
   227 
       
   228         # The set of wanted ancestors of rev is the union of the sets
       
   229         # of wanted ancestors of its parents. Plus rev itself.
       
   230         wrev = util.set()
       
   231         for p in parents:
       
   232             wrev.update(self.wantedancestors[p])
       
   233         wrev.add(rev)
       
   234         self.wantedancestors[rev] = wrev
       
   235 
       
   236     def getchanges(self, rev):
       
   237         parents = self.commits[rev].parents
       
   238         if len(parents) > 1:
       
   239             self.rebuild()
       
   240 
       
   241         # To decide whether we're interested in rev we:
       
   242         #
       
   243         # - calculate what parents rev will have if it turns out we're
       
   244         #   interested in it.  If it's going to have more than 1 parent,
       
   245         #   we're interested in it.
       
   246         #
       
   247         # - otherwise, we'll compare it with the single parent we found.
       
   248         #   If any of the files we're interested in is different in the
       
   249         #   the two revisions, we're interested in rev.
       
   250 
       
   251         # A parent p is interesting if its mapped version (self.parentmap[p]):
       
   252         # - is not SKIPREV
       
   253         # - is still not in the list of parents (we don't want duplicates)
       
   254         # - is not an ancestor of the mapped versions of the other parents
       
   255         mparents = []
       
   256         wp = None
       
   257         for i, p1 in enumerate(parents):
       
   258             mp1 = self.parentmap[p1]
       
   259             if mp1 == SKIPREV or mp1 in mparents:
       
   260                 continue
       
   261             for p2 in parents:
       
   262                 if p1 == p2 or mp1 == self.parentmap[p2]:
       
   263                     continue
       
   264                 if mp1 in self.wantedancestors[p2]:
       
   265                     break
       
   266             else:
       
   267                 mparents.append(mp1)
       
   268                 wp = i
       
   269 
       
   270         if wp is None and parents:
       
   271             wp = 0
       
   272 
       
   273         self.origparents[rev] = parents
       
   274 
       
   275         if len(mparents) < 2 and not self.wanted(rev, wp):
       
   276             # We don't want this revision.
       
   277             # Update our state and tell the convert process to map this
       
   278             # revision to the same revision its parent as mapped to.
       
   279             p = None
       
   280             if parents:
       
   281                 p = parents[wp]
       
   282             self.mark_not_wanted(rev, p)
       
   283             self.convertedorder.append((rev, False, p))
       
   284             return self.parentmap[rev]
       
   285 
       
   286         # We want this revision.
       
   287         # Rewrite the parents of the commit object
       
   288         self.commits[rev].parents = mparents
       
   289         self.mark_wanted(rev, parents)
       
   290         self.convertedorder.append((rev, True, None))
       
   291 
       
   292         # Get the real changes and do the filtering/mapping.
       
   293         # To be able to get the files later on in getfile and getmode,
       
   294         # we hide the original filename in the rev part of the return
       
   295         # value.
       
   296         changes, copies = self.base.getchanges(rev)
       
   297         newnames = {}
       
   298         files = []
       
   299         for f, r in changes:
       
   300             newf = self.filemapper(f)
       
   301             if newf:
       
   302                 files.append((newf, (f, r)))
       
   303                 newnames[f] = newf
       
   304 
       
   305         ncopies = {}
       
   306         for c in copies:
       
   307             newc = self.filemapper(c)
       
   308             if newc:
       
   309                 newsource = self.filemapper(copies[c])
       
   310                 if newsource:
       
   311                     ncopies[newc] = newsource
       
   312 
       
   313         return files, ncopies
       
   314 
       
   315     def getfile(self, name, rev):
       
   316         realname, realrev = rev
       
   317         return self.base.getfile(realname, realrev)
       
   318 
       
   319     def getmode(self, name, rev):
       
   320         realname, realrev = rev
       
   321         return self.base.getmode(realname, realrev)
       
   322 
       
   323     def gettags(self):
       
   324         return self.base.gettags()
       
   325 
       
   326     def before(self):
       
   327         pass
       
   328 
       
   329     def after(self):
       
   330         pass