##// END OF EJS Templates
merge default into stable for 3.9 code freeze
merge default into stable for 3.9 code freeze

File last commit:

r29205:a0939666 default
r29605:519bb4f9 merge 3.9-rc stable
Show More
filemap.py
463 lines | 16.9 KiB | text/x-python | PythonLexer
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 # Copyright 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376 #
Martin Geisler
updated license to be explicit about GPL version 2
r8225 # This software may be used and distributed according to the terms of the
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
timeless
convert: filemap use absolute_import
r28367 from __future__ import absolute_import
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376
Huayang
convert: normalize paths in filemaps (issue3612)...
r17797 import posixpath
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376 import shlex
Yuya Nishihara
py3: move up symbol imports to enforce import-checker rules...
r29205
from mercurial.i18n import _
timeless
convert: filemap use absolute_import
r28367 from mercurial import (
error,
)
from . import common
SKIPREV = common.SKIPREV
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376
Mads Kiilerich
convert: readability and test of rpairs function
r20048 def rpairs(path):
'''Yield tuples with path split at '/', starting with the full path.
No leading, trailing or double '/', please.
>>> for x in rpairs('foo/bar/baz'): print x
('foo/bar/baz', '')
('foo/bar', 'baz')
('foo', 'bar/baz')
('.', 'foo/bar/baz')
'''
i = len(path)
while i != -1:
yield path[:i], path[i + 1:]
i = path.rfind('/', 0, i)
yield '.', path
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376
Huayang
convert: normalize paths in filemaps (issue3612)...
r17797 def normalize(path):
''' We use posixpath.normpath to support cross-platform path format.
However, it doesn't handle None input. So we wrap it up. '''
if path is None:
return None
return posixpath.normpath(path)
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376 class filemapper(object):
'''Map and filter filenames when importing.
A name can be mapped to itself, a new name, or None (omit from new
repository).'''
def __init__(self, ui, path=None):
self.ui = ui
self.include = {}
self.exclude = {}
self.rename = {}
Durham Goode
convert: implements targetfilebelongstosource for filemap source...
r26036 self.targetprefixes = None
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376 if path:
if self.parse(path):
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('errors in filemap'))
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376
def parse(self, path):
errs = 0
def check(name, mapping, listname):
Mads Kiilerich
convert: warn on superfluous / in paths...
r11680 if not name:
self.ui.warn(_('%s:%d: path to %s is missing\n') %
(lex.infile, lex.lineno, listname))
return 1
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376 if name in mapping:
self.ui.warn(_('%s:%d: %r already in %s list\n') %
(lex.infile, lex.lineno, name, listname))
return 1
Mads Kiilerich
convert: warn on superfluous / in paths...
r11680 if (name.startswith('/') or
name.endswith('/') or
'//' in name):
self.ui.warn(_('%s:%d: superfluous / in %s %r\n') %
(lex.infile, lex.lineno, listname, name))
return 1
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376 return 0
lex = shlex.shlex(open(path), path, True)
lex.wordchars += '!@#$%^&*()-=+[]{}|;:,./<>?'
cmd = lex.get_token()
while cmd:
if cmd == 'include':
Huayang
convert: normalize paths in filemaps (issue3612)...
r17797 name = normalize(lex.get_token())
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376 errs += check(name, self.exclude, 'exclude')
self.include[name] = name
elif cmd == 'exclude':
Huayang
convert: normalize paths in filemaps (issue3612)...
r17797 name = normalize(lex.get_token())
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376 errs += check(name, self.include, 'include')
errs += check(name, self.rename, 'rename')
self.exclude[name] = name
elif cmd == 'rename':
Huayang
convert: normalize paths in filemaps (issue3612)...
r17797 src = normalize(lex.get_token())
dest = normalize(lex.get_token())
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376 errs += check(src, self.exclude, 'exclude')
self.rename[src] = dest
elif cmd == 'source':
Huayang
convert: normalize paths in filemaps (issue3612)...
r17797 errs += self.parse(normalize(lex.get_token()))
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376 else:
self.ui.warn(_('%s:%d: unknown directive %r\n') %
(lex.infile, lex.lineno, cmd))
errs += 1
cmd = lex.get_token()
return errs
def lookup(self, name, mapping):
Huayang
convert: normalize paths in filemaps (issue3612)...
r17797 name = normalize(name)
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376 for pre, suf in rpairs(name):
try:
return mapping[pre], pre, suf
Peter Arrenbrecht
cleanup: drop unused assignments
r7875 except KeyError:
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376 pass
return '', name, ''
Durham Goode
convert: implements targetfilebelongstosource for filemap source...
r26036 def istargetfile(self, filename):
"""Return true if the given target filename is covered as a destination
of the filemap. This is useful for identifying what parts of the target
repo belong to the source repo and what parts don't."""
if self.targetprefixes is None:
self.targetprefixes = set()
for before, after in self.rename.iteritems():
self.targetprefixes.add(after)
# If "." is a target, then all target files are considered from the
# source.
if not self.targetprefixes or '.' in self.targetprefixes:
return True
filename = normalize(filename)
for pre, suf in rpairs(filename):
# This check is imperfect since it doesn't account for the
# include/exclude list, but it should work in filemaps that don't
# apply include/exclude to the same source directories they are
# renaming.
if pre in self.targetprefixes:
return True
return False
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376 def __call__(self, name):
if self.include:
inc = self.lookup(name, self.include)[0]
else:
inc = name
if self.exclude:
exc = self.lookup(name, self.exclude)[0]
else:
exc = ''
Stefan Simek
convert: make filemap favor most specific filtering rule...
r9884 if (not self.include and exc) or (len(inc) <= len(exc)):
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376 return None
newpre, pre, suf = self.lookup(name, self.rename)
if newpre:
if newpre == '.':
return suf
if suf:
Matt Mackall
convert: handle trailing slashes in filemap better (issue3124)
r15565 if newpre.endswith('/'):
return newpre + suf
Alexis S. L. Carvalho
convert: move filemapper class to a separate file
r5376 return newpre + '/' + suf
return newpre
return name
def active(self):
return bool(self.include or self.exclude or self.rename)
Alexis S. L. Carvalho
convert: readd --filemap...
r5377
# This class does two additional things compared to a regular source:
#
# - Filter and rename files. This is mostly wrapped by the filemapper
# class above. We hide the original filename in the revision that is
Patrick Mezard
convert: merge sources getmode() into getfile()
r11134 # returned by getchanges to be able to find things later in getfile.
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 #
# - Return only revisions that matter for the files we're interested in.
# This involves rewriting the parents of the original revision to
# create a graph that is restricted to those revisions.
#
# This set of revisions includes not only revisions that directly
# touch files we're interested in, but also merges that merge two
# or more interesting revisions.
timeless
convert: filemap use absolute_import
r28367 class filemap_source(common.converter_source):
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 def __init__(self, ui, baseconverter, filemap):
Bryan O'Sullivan
convert: some tidyups, doc improvements, and test fixes...
r5556 super(filemap_source, self).__init__(ui)
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 self.base = baseconverter
self.filemapper = filemapper(ui, filemap)
self.commits = {}
# if a revision rev has parent p in the original revision graph, then
# rev will have parent self.parentmap[p] in the restricted graph.
self.parentmap = {}
# self.wantedancestors[rev] is the set of all ancestors of rev that
# are in the restricted graph.
self.wantedancestors = {}
self.convertedorder = None
self._rebuilt = False
self.origparents = {}
Alexis S. L. Carvalho
convert --filemap: reduce memory usage
r5401 self.children = {}
self.seenchildren = {}
Durham Goode
convert: add config option for disabling ancestor parent checks...
r25742 # experimental config: convert.ignoreancestorcheck
self.ignoreancestorcheck = self.ui.configbool('convert',
'ignoreancestorcheck')
Alexis S. L. Carvalho
convert: readd --filemap...
r5377
Patrick Mezard
convert: filemap must call base converter before()/after() actions
r5799 def before(self):
self.base.before()
def after(self):
self.base.after()
Bryan O'Sullivan
convert: abstract map files into a class
r5510 def setrevmap(self, revmap):
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 # rebuild our state to make things restartable
#
# To avoid calling getcommit for every revision that has already
# been converted, we rebuild only the parentmap, delaying the
# rebuild of wantedancestors until we need it (i.e. until a
# merge).
#
# We assume the order argument lists the revisions in
# topological order, so that we can infer which revisions were
# wanted by previous runs.
self._rebuilt = not revmap
seen = {SKIPREV: SKIPREV}
Martin Geisler
util: use built-in set and frozenset...
r8150 dummyset = set()
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 converted = []
Bryan O'Sullivan
convert: abstract map files into a class
r5510 for rev in revmap.order:
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 mapped = revmap[rev]
wanted = mapped not in seen
if wanted:
seen[mapped] = rev
self.parentmap[rev] = rev
else:
self.parentmap[rev] = seen[mapped]
self.wantedancestors[rev] = dummyset
arg = seen[mapped]
if arg == SKIPREV:
arg = None
converted.append((rev, wanted, arg))
self.convertedorder = converted
Bryan O'Sullivan
convert: abstract map files into a class
r5510 return self.base.setrevmap(revmap)
Alexis S. L. Carvalho
convert: readd --filemap...
r5377
def rebuild(self):
if self._rebuilt:
return True
self._rebuilt = True
self.parentmap.clear()
self.wantedancestors.clear()
Alexis S. L. Carvalho
convert --filemap: reduce memory usage
r5401 self.seenchildren.clear()
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 for rev, wanted, arg in self.convertedorder:
Alexis S. L. Carvalho
convert --filemap: reduce memory usage
r5401 if rev not in self.origparents:
Mads Kiilerich
convert: fix crash when existing converted revision didn't come from source...
r19863 try:
self.origparents[rev] = self.getcommit(rev).parents
except error.RepoLookupError:
self.ui.debug("unknown revmap source: %s\n" % rev)
continue
Alexis S. L. Carvalho
convert --filemap: reduce memory usage
r5401 if arg is not None:
self.children[arg] = self.children.get(arg, 0) + 1
for rev, wanted, arg in self.convertedorder:
Mads Kiilerich
convert: fix crash when existing converted revision didn't come from source...
r19863 try:
parents = self.origparents[rev]
except KeyError:
continue # unknown revmap source
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 if wanted:
self.mark_wanted(rev, parents)
else:
self.mark_not_wanted(rev, arg)
Alexis S. L. Carvalho
convert --filemap: reduce memory usage
r5401 self._discard(arg, *parents)
Alexis S. L. Carvalho
convert: readd --filemap...
r5377
return True
def getheads(self):
return self.base.getheads()
def getcommit(self, rev):
# We want to save a reference to the commit objects to be able
# to rewrite their parents later on.
Alexis S. L. Carvalho
convert --filemap: reduce memory usage
r5401 c = self.commits[rev] = self.base.getcommit(rev)
for p in c.parents:
self.children[p] = self.children.get(p, 0) + 1
return c
Patrick Mezard
convert: make filemap prune useless branch closing revs (issue2774)...
r13968 def _cachedcommit(self, rev):
if rev in self.commits:
return self.commits[rev]
return self.base.getcommit(rev)
Alexis S. L. Carvalho
convert --filemap: reduce memory usage
r5401 def _discard(self, *revs):
for r in revs:
if r is None:
continue
self.seenchildren[r] = self.seenchildren.get(r, 0) + 1
if self.seenchildren[r] == self.children[r]:
Mads Kiilerich
convert: fix crash when filemap filtering is changed
r19862 self.wantedancestors.pop(r, None)
self.parentmap.pop(r, None)
Alexis S. L. Carvalho
convert --filemap: reduce memory usage
r5401 del self.seenchildren[r]
if self._rebuilt:
del self.children[r]
Alexis S. L. Carvalho
convert: readd --filemap...
r5377
def wanted(self, rev, i):
# Return True if we're directly interested in rev.
#
# i is an index selecting one of the parents of rev (if rev
# has no parents, i is None). getchangedfiles will give us
# the list of files that are different in rev and in the parent
# indicated by i. If we're interested in any of these files,
# we're interested in rev.
try:
files = self.base.getchangedfiles(rev, i)
except NotImplementedError:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_("source repository doesn't support --filemap"))
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 for f in files:
if self.filemapper(f):
return True
return False
def mark_not_wanted(self, rev, p):
# Mark rev as not interesting and update data structures.
if p is None:
# A root revision. Use SKIPREV to indicate that it doesn't
# map to any revision in the restricted graph. Put SKIPREV
# in the set of wanted ancestors to simplify code elsewhere
self.parentmap[rev] = SKIPREV
Martin Geisler
util: use built-in set and frozenset...
r8150 self.wantedancestors[rev] = set((SKIPREV,))
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 return
# Reuse the data from our parent.
self.parentmap[rev] = self.parentmap[p]
self.wantedancestors[rev] = self.wantedancestors[p]
def mark_wanted(self, rev, parents):
# Mark rev ss wanted and update data structures.
# rev will be in the restricted graph, so children of rev in
# the original graph should still have rev as a parent in the
# restricted graph.
self.parentmap[rev] = rev
# The set of wanted ancestors of rev is the union of the sets
# of wanted ancestors of its parents. Plus rev itself.
Martin Geisler
util: use built-in set and frozenset...
r8150 wrev = set()
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 for p in parents:
Mads Kiilerich
convert: fix crash when filemap filtering is changed
r19862 if p in self.wantedancestors:
wrev.update(self.wantedancestors[p])
else:
self.ui.warn(_('warning: %s parent %s is missing\n') %
(rev, p))
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 wrev.add(rev)
self.wantedancestors[rev] = wrev
Mads Kiilerich
convert: introduce --full for converting all files...
r22300 def getchanges(self, rev, full):
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 parents = self.commits[rev].parents
Durham Goode
convert: add config option for disabling ancestor parent checks...
r25742 if len(parents) > 1 and not self.ignoreancestorcheck:
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 self.rebuild()
# To decide whether we're interested in rev we:
#
# - calculate what parents rev will have if it turns out we're
# interested in it. If it's going to have more than 1 parent,
# we're interested in it.
#
# - otherwise, we'll compare it with the single parent we found.
# If any of the files we're interested in is different in the
# the two revisions, we're interested in rev.
# A parent p is interesting if its mapped version (self.parentmap[p]):
# - is not SKIPREV
# - is still not in the list of parents (we don't want duplicates)
Patrick Mezard
convert: keep branch switching merges with ancestors (issue3340)...
r17103 # - is not an ancestor of the mapped versions of the other parents or
# there is no parent in the same branch than the current revision.
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 mparents = []
Patrick Mezard
convert: keep branch switching merges with ancestors (issue3340)...
r17103 knownparents = set()
branch = self.commits[rev].branch
hasbranchparent = False
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 for i, p1 in enumerate(parents):
mp1 = self.parentmap[p1]
Patrick Mezard
convert: keep branch switching merges with ancestors (issue3340)...
r17103 if mp1 == SKIPREV or mp1 in knownparents:
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 continue
Durham Goode
convert: add config option for disabling ancestor parent checks...
r25742
isancestor = (not self.ignoreancestorcheck and
any(p2 for p2 in parents
if p1 != p2 and mp1 != self.parentmap[p2]
and mp1 in self.wantedancestors[p2]))
Patrick Mezard
convert: keep branch switching merges with ancestors (issue3340)...
r17103 if not isancestor and not hasbranchparent and len(parents) > 1:
# This could be expensive, avoid unnecessary calls.
if self._cachedcommit(p1).branch == branch:
hasbranchparent = True
mparents.append((p1, mp1, i, isancestor))
knownparents.add(mp1)
# Discard parents ancestors of other parents if there is a
# non-ancestor one on the same branch than current revision.
if hasbranchparent:
mparents = [p for p in mparents if not p[3]]
wp = None
if mparents:
wp = max(p[2] for p in mparents)
mparents = [p[1] for p in mparents]
elif parents:
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 wp = 0
self.origparents[rev] = parents
Patrick Mezard
convert: make filemap prune useless branch closing revs (issue2774)...
r13968 closed = False
if 'close' in self.commits[rev].extra:
# A branch closing revision is only useful if one of its
# parents belong to the branch being closed
pbranches = [self._cachedcommit(p).branch for p in mparents]
if branch in pbranches:
closed = True
Matt Mackall
convert: handle closed branch heads in hg-hg conversion (issue2185)
r11673
if len(mparents) < 2 and not closed and not self.wanted(rev, wp):
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 # We don't want this revision.
# Update our state and tell the convert process to map this
# revision to the same revision its parent as mapped to.
p = None
if parents:
p = parents[wp]
self.mark_not_wanted(rev, p)
self.convertedorder.append((rev, False, p))
Alexis S. L. Carvalho
convert --filemap: reduce memory usage
r5401 self._discard(*parents)
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 return self.parentmap[rev]
# We want this revision.
# Rewrite the parents of the commit object
self.commits[rev].parents = mparents
self.mark_wanted(rev, parents)
self.convertedorder.append((rev, True, None))
Alexis S. L. Carvalho
convert --filemap: reduce memory usage
r5401 self._discard(*parents)
Alexis S. L. Carvalho
convert: readd --filemap...
r5377
Patrick Mezard
convert: merge sources getmode() into getfile()
r11134 # Get the real changes and do the filtering/mapping. To be
# able to get the files later on in getfile, we hide the
# original filename in the rev part of the return value.
Mads Kiilerich
convert: optimize convert of files that are unmodified from p2 in merges...
r24395 changes, copies, cleanp2 = self.base.getchanges(rev, full)
Wagner Bruna
convert: make filemap renames consistently override revision renames...
r17174 files = {}
Mads Kiilerich
convert: optimize convert of files that are unmodified from p2 in merges...
r24395 ncleanp2 = set(cleanp2)
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 for f, r in changes:
newf = self.filemapper(f)
Wagner Bruna
convert: make filemap renames consistently override revision renames...
r17174 if newf and (newf != f or newf not in files):
files[newf] = (f, r)
Mads Kiilerich
convert: optimize convert of files that are unmodified from p2 in merges...
r24395 if newf != f:
ncleanp2.discard(f)
Wagner Bruna
convert: make filemap renames consistently override revision renames...
r17174 files = sorted(files.items())
Alexis S. L. Carvalho
convert: readd --filemap...
r5377
ncopies = {}
for c in copies:
newc = self.filemapper(c)
if newc:
newsource = self.filemapper(copies[c])
if newsource:
ncopies[newc] = newsource
Mads Kiilerich
convert: optimize convert of files that are unmodified from p2 in merges...
r24395 return files, ncopies, ncleanp2
Alexis S. L. Carvalho
convert: readd --filemap...
r5377
Durham Goode
convert: implements targetfilebelongstosource for filemap source...
r26036 def targetfilebelongstosource(self, targetfilename):
return self.filemapper.istargetfile(targetfilename)
Alexis S. L. Carvalho
convert: readd --filemap...
r5377 def getfile(self, name, rev):
realname, realrev = rev
return self.base.getfile(realname, realrev)
def gettags(self):
return self.base.gettags()
Patrick Mezard
convert: fail fast if source does not support --sourcesort
r8691
def hasnativeorder(self):
return self.base.hasnativeorder()
Patrick Mezard
convert: rewrite tags when converting from hg to hg
r8693
def lookuprev(self, rev):
return self.base.lookuprev(rev)
etienne
convert: added bookmarks support in filemap...
r15107
def getbookmarks(self):
return self.base.getbookmarks()
Mads Kiilerich
convert: update source shamap when using filemap, just as when not using filemap...
r19892
def converted(self, rev, sinkrev):
self.base.converted(rev, sinkrev)