eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/hgext/convert/filemap.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
       
     2 # Copyright 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
       
     3 #
       
     4 # This software may be used and distributed according to the terms of the
       
     5 # GNU General Public License version 2 or any later version.
       
     6 
       
     7 import shlex
       
     8 from mercurial.i18n import _
       
     9 from mercurial import util
       
    10 from common import SKIPREV, converter_source
       
    11 
       
    12 def rpairs(name):
       
    13     e = len(name)
       
    14     while e != -1:
       
    15         yield name[:e], name[e + 1:]
       
    16         e = name.rfind('/', 0, e)
       
    17     yield '.', name
       
    18 
       
    19 class filemapper(object):
       
    20     '''Map and filter filenames when importing.
       
    21     A name can be mapped to itself, a new name, or None (omit from new
       
    22     repository).'''
       
    23 
       
    24     def __init__(self, ui, path=None):
       
    25         self.ui = ui
       
    26         self.include = {}
       
    27         self.exclude = {}
       
    28         self.rename = {}
       
    29         if path:
       
    30             if self.parse(path):
       
    31                 raise util.Abort(_('errors in filemap'))
       
    32 
       
    33     def parse(self, path):
       
    34         errs = 0
       
    35         def check(name, mapping, listname):
       
    36             if not name:
       
    37                 self.ui.warn(_('%s:%d: path to %s is missing\n') %
       
    38                              (lex.infile, lex.lineno, listname))
       
    39                 return 1
       
    40             if name in mapping:
       
    41                 self.ui.warn(_('%s:%d: %r already in %s list\n') %
       
    42                              (lex.infile, lex.lineno, name, listname))
       
    43                 return 1
       
    44             if (name.startswith('/') or
       
    45                 name.endswith('/') or
       
    46                 '//' in name):
       
    47                 self.ui.warn(_('%s:%d: superfluous / in %s %r\n') %
       
    48                              (lex.infile, lex.lineno, listname, name))
       
    49                 return 1
       
    50             return 0
       
    51         lex = shlex.shlex(open(path), path, True)
       
    52         lex.wordchars += '!@#$%^&*()-=+[]{}|;:,./<>?'
       
    53         cmd = lex.get_token()
       
    54         while cmd:
       
    55             if cmd == 'include':
       
    56                 name = lex.get_token()
       
    57                 errs += check(name, self.exclude, 'exclude')
       
    58                 self.include[name] = name
       
    59             elif cmd == 'exclude':
       
    60                 name = lex.get_token()
       
    61                 errs += check(name, self.include, 'include')
       
    62                 errs += check(name, self.rename, 'rename')
       
    63                 self.exclude[name] = name
       
    64             elif cmd == 'rename':
       
    65                 src = lex.get_token()
       
    66                 dest = lex.get_token()
       
    67                 errs += check(src, self.exclude, 'exclude')
       
    68                 self.rename[src] = dest
       
    69             elif cmd == 'source':
       
    70                 errs += self.parse(lex.get_token())
       
    71             else:
       
    72                 self.ui.warn(_('%s:%d: unknown directive %r\n') %
       
    73                              (lex.infile, lex.lineno, cmd))
       
    74                 errs += 1
       
    75             cmd = lex.get_token()
       
    76         return errs
       
    77 
       
    78     def lookup(self, name, mapping):
       
    79         for pre, suf in rpairs(name):
       
    80             try:
       
    81                 return mapping[pre], pre, suf
       
    82             except KeyError:
       
    83                 pass
       
    84         return '', name, ''
       
    85 
       
    86     def __call__(self, name):
       
    87         if self.include:
       
    88             inc = self.lookup(name, self.include)[0]
       
    89         else:
       
    90             inc = name
       
    91         if self.exclude:
       
    92             exc = self.lookup(name, self.exclude)[0]
       
    93         else:
       
    94             exc = ''
       
    95         if (not self.include and exc) or (len(inc) <= len(exc)):
       
    96             return None
       
    97         newpre, pre, suf = self.lookup(name, self.rename)
       
    98         if newpre:
       
    99             if newpre == '.':
       
   100                 return suf
       
   101             if suf:
       
   102                 return newpre + '/' + suf
       
   103             return newpre
       
   104         return name
       
   105 
       
   106     def active(self):
       
   107         return bool(self.include or self.exclude or self.rename)
       
   108 
       
   109 # This class does two additional things compared to a regular source:
       
   110 #
       
   111 # - Filter and rename files.  This is mostly wrapped by the filemapper
       
   112 #   class above. We hide the original filename in the revision that is
       
   113 #   returned by getchanges to be able to find things later in getfile.
       
   114 #
       
   115 # - Return only revisions that matter for the files we're interested in.
       
   116 #   This involves rewriting the parents of the original revision to
       
   117 #   create a graph that is restricted to those revisions.
       
   118 #
       
   119 #   This set of revisions includes not only revisions that directly
       
   120 #   touch files we're interested in, but also merges that merge two
       
   121 #   or more interesting revisions.
       
   122 
       
   123 class filemap_source(converter_source):
       
   124     def __init__(self, ui, baseconverter, filemap):
       
   125         super(filemap_source, self).__init__(ui)
       
   126         self.base = baseconverter
       
   127         self.filemapper = filemapper(ui, filemap)
       
   128         self.commits = {}
       
   129         # if a revision rev has parent p in the original revision graph, then
       
   130         # rev will have parent self.parentmap[p] in the restricted graph.
       
   131         self.parentmap = {}
       
   132         # self.wantedancestors[rev] is the set of all ancestors of rev that
       
   133         # are in the restricted graph.
       
   134         self.wantedancestors = {}
       
   135         self.convertedorder = None
       
   136         self._rebuilt = False
       
   137         self.origparents = {}
       
   138         self.children = {}
       
   139         self.seenchildren = {}
       
   140 
       
   141     def before(self):
       
   142         self.base.before()
       
   143 
       
   144     def after(self):
       
   145         self.base.after()
       
   146 
       
   147     def setrevmap(self, revmap):
       
   148         # rebuild our state to make things restartable
       
   149         #
       
   150         # To avoid calling getcommit for every revision that has already
       
   151         # been converted, we rebuild only the parentmap, delaying the
       
   152         # rebuild of wantedancestors until we need it (i.e. until a
       
   153         # merge).
       
   154         #
       
   155         # We assume the order argument lists the revisions in
       
   156         # topological order, so that we can infer which revisions were
       
   157         # wanted by previous runs.
       
   158         self._rebuilt = not revmap
       
   159         seen = {SKIPREV: SKIPREV}
       
   160         dummyset = set()
       
   161         converted = []
       
   162         for rev in revmap.order:
       
   163             mapped = revmap[rev]
       
   164             wanted = mapped not in seen
       
   165             if wanted:
       
   166                 seen[mapped] = rev
       
   167                 self.parentmap[rev] = rev
       
   168             else:
       
   169                 self.parentmap[rev] = seen[mapped]
       
   170             self.wantedancestors[rev] = dummyset
       
   171             arg = seen[mapped]
       
   172             if arg == SKIPREV:
       
   173                 arg = None
       
   174             converted.append((rev, wanted, arg))
       
   175         self.convertedorder = converted
       
   176         return self.base.setrevmap(revmap)
       
   177 
       
   178     def rebuild(self):
       
   179         if self._rebuilt:
       
   180             return True
       
   181         self._rebuilt = True
       
   182         self.parentmap.clear()
       
   183         self.wantedancestors.clear()
       
   184         self.seenchildren.clear()
       
   185         for rev, wanted, arg in self.convertedorder:
       
   186             if rev not in self.origparents:
       
   187                 self.origparents[rev] = self.getcommit(rev).parents
       
   188             if arg is not None:
       
   189                 self.children[arg] = self.children.get(arg, 0) + 1
       
   190 
       
   191         for rev, wanted, arg in self.convertedorder:
       
   192             parents = self.origparents[rev]
       
   193             if wanted:
       
   194                 self.mark_wanted(rev, parents)
       
   195             else:
       
   196                 self.mark_not_wanted(rev, arg)
       
   197             self._discard(arg, *parents)
       
   198 
       
   199         return True
       
   200 
       
   201     def getheads(self):
       
   202         return self.base.getheads()
       
   203 
       
   204     def getcommit(self, rev):
       
   205         # We want to save a reference to the commit objects to be able
       
   206         # to rewrite their parents later on.
       
   207         c = self.commits[rev] = self.base.getcommit(rev)
       
   208         for p in c.parents:
       
   209             self.children[p] = self.children.get(p, 0) + 1
       
   210         return c
       
   211 
       
   212     def _discard(self, *revs):
       
   213         for r in revs:
       
   214             if r is None:
       
   215                 continue
       
   216             self.seenchildren[r] = self.seenchildren.get(r, 0) + 1
       
   217             if self.seenchildren[r] == self.children[r]:
       
   218                 del self.wantedancestors[r]
       
   219                 del self.parentmap[r]
       
   220                 del self.seenchildren[r]
       
   221                 if self._rebuilt:
       
   222                     del self.children[r]
       
   223 
       
   224     def wanted(self, rev, i):
       
   225         # Return True if we're directly interested in rev.
       
   226         #
       
   227         # i is an index selecting one of the parents of rev (if rev
       
   228         # has no parents, i is None).  getchangedfiles will give us
       
   229         # the list of files that are different in rev and in the parent
       
   230         # indicated by i.  If we're interested in any of these files,
       
   231         # we're interested in rev.
       
   232         try:
       
   233             files = self.base.getchangedfiles(rev, i)
       
   234         except NotImplementedError:
       
   235             raise util.Abort(_("source repository doesn't support --filemap"))
       
   236         for f in files:
       
   237             if self.filemapper(f):
       
   238                 return True
       
   239         return False
       
   240 
       
   241     def mark_not_wanted(self, rev, p):
       
   242         # Mark rev as not interesting and update data structures.
       
   243 
       
   244         if p is None:
       
   245             # A root revision. Use SKIPREV to indicate that it doesn't
       
   246             # map to any revision in the restricted graph.  Put SKIPREV
       
   247             # in the set of wanted ancestors to simplify code elsewhere
       
   248             self.parentmap[rev] = SKIPREV
       
   249             self.wantedancestors[rev] = set((SKIPREV,))
       
   250             return
       
   251 
       
   252         # Reuse the data from our parent.
       
   253         self.parentmap[rev] = self.parentmap[p]
       
   254         self.wantedancestors[rev] = self.wantedancestors[p]
       
   255 
       
   256     def mark_wanted(self, rev, parents):
       
   257         # Mark rev ss wanted and update data structures.
       
   258 
       
   259         # rev will be in the restricted graph, so children of rev in
       
   260         # the original graph should still have rev as a parent in the
       
   261         # restricted graph.
       
   262         self.parentmap[rev] = rev
       
   263 
       
   264         # The set of wanted ancestors of rev is the union of the sets
       
   265         # of wanted ancestors of its parents. Plus rev itself.
       
   266         wrev = set()
       
   267         for p in parents:
       
   268             wrev.update(self.wantedancestors[p])
       
   269         wrev.add(rev)
       
   270         self.wantedancestors[rev] = wrev
       
   271 
       
   272     def getchanges(self, rev):
       
   273         parents = self.commits[rev].parents
       
   274         if len(parents) > 1:
       
   275             self.rebuild()
       
   276 
       
   277         # To decide whether we're interested in rev we:
       
   278         #
       
   279         # - calculate what parents rev will have if it turns out we're
       
   280         #   interested in it.  If it's going to have more than 1 parent,
       
   281         #   we're interested in it.
       
   282         #
       
   283         # - otherwise, we'll compare it with the single parent we found.
       
   284         #   If any of the files we're interested in is different in the
       
   285         #   the two revisions, we're interested in rev.
       
   286 
       
   287         # A parent p is interesting if its mapped version (self.parentmap[p]):
       
   288         # - is not SKIPREV
       
   289         # - is still not in the list of parents (we don't want duplicates)
       
   290         # - is not an ancestor of the mapped versions of the other parents
       
   291         mparents = []
       
   292         wp = None
       
   293         for i, p1 in enumerate(parents):
       
   294             mp1 = self.parentmap[p1]
       
   295             if mp1 == SKIPREV or mp1 in mparents:
       
   296                 continue
       
   297             for p2 in parents:
       
   298                 if p1 == p2 or mp1 == self.parentmap[p2]:
       
   299                     continue
       
   300                 if mp1 in self.wantedancestors[p2]:
       
   301                     break
       
   302             else:
       
   303                 mparents.append(mp1)
       
   304                 wp = i
       
   305 
       
   306         if wp is None and parents:
       
   307             wp = 0
       
   308 
       
   309         self.origparents[rev] = parents
       
   310 
       
   311         closed = 'close' in self.commits[rev].extra
       
   312 
       
   313         if len(mparents) < 2 and not closed and not self.wanted(rev, wp):
       
   314             # We don't want this revision.
       
   315             # Update our state and tell the convert process to map this
       
   316             # revision to the same revision its parent as mapped to.
       
   317             p = None
       
   318             if parents:
       
   319                 p = parents[wp]
       
   320             self.mark_not_wanted(rev, p)
       
   321             self.convertedorder.append((rev, False, p))
       
   322             self._discard(*parents)
       
   323             return self.parentmap[rev]
       
   324 
       
   325         # We want this revision.
       
   326         # Rewrite the parents of the commit object
       
   327         self.commits[rev].parents = mparents
       
   328         self.mark_wanted(rev, parents)
       
   329         self.convertedorder.append((rev, True, None))
       
   330         self._discard(*parents)
       
   331 
       
   332         # Get the real changes and do the filtering/mapping. To be
       
   333         # able to get the files later on in getfile, we hide the
       
   334         # original filename in the rev part of the return value.
       
   335         changes, copies = self.base.getchanges(rev)
       
   336         newnames = {}
       
   337         files = []
       
   338         for f, r in changes:
       
   339             newf = self.filemapper(f)
       
   340             if newf:
       
   341                 files.append((newf, (f, r)))
       
   342                 newnames[f] = newf
       
   343 
       
   344         ncopies = {}
       
   345         for c in copies:
       
   346             newc = self.filemapper(c)
       
   347             if newc:
       
   348                 newsource = self.filemapper(copies[c])
       
   349                 if newsource:
       
   350                     ncopies[newc] = newsource
       
   351 
       
   352         return files, ncopies
       
   353 
       
   354     def getfile(self, name, rev):
       
   355         realname, realrev = rev
       
   356         return self.base.getfile(realname, realrev)
       
   357 
       
   358     def gettags(self):
       
   359         return self.base.gettags()
       
   360 
       
   361     def hasnativeorder(self):
       
   362         return self.base.hasnativeorder()
       
   363 
       
   364     def lookuprev(self, rev):
       
   365         return self.base.lookuprev(rev)