# HG changeset patch # User Augie Fackler # Date 1418269638 18000 # Node ID c791efb7082a01ec9596d65cae47b9fdd499eb6a # Parent 2ae577a4cd56e80f67fbb57f03345ff92ecdbdf5# Parent b5520673f6f243a511070d45e6c63ead2f3e135c Merge with stable. diff --git a/hgsubversion/layouts/__init__.py b/hgsubversion/layouts/__init__.py --- a/hgsubversion/layouts/__init__.py +++ b/hgsubversion/layouts/__init__.py @@ -13,14 +13,12 @@ from mercurial import util as hgutil import custom import detect -import persist import single import standard __all__ = [ "detect", "layout_from_name", - "persist", ] # This is the authoritative store of what layouts are available. @@ -33,7 +31,7 @@ NAME_TO_CLASS = { } -def layout_from_name(name, ui): +def layout_from_name(name, meta): """Returns a layout module given the layout name You should use one of the layout.detect.* functions to get the @@ -42,5 +40,5 @@ def layout_from_name(name, ui): """ if name not in NAME_TO_CLASS: - raise hgutil.Abort('Unknown hgsubversion layout: %s' %name) - return NAME_TO_CLASS[name](ui) + raise hgutil.Abort('Unknown hgsubversion layout: %s' % name) + return NAME_TO_CLASS[name](meta) diff --git a/hgsubversion/layouts/base.py b/hgsubversion/layouts/base.py --- a/hgsubversion/layouts/base.py +++ b/hgsubversion/layouts/base.py @@ -9,8 +9,8 @@ from mercurial import util as hgutil class BaseLayout(object): - def __init__(self, ui): - self.ui = ui + def __init__(self, meta): + self.meta = meta def __unimplemented(self, method_name): raise NotImplementedError( diff --git a/hgsubversion/layouts/custom.py b/hgsubversion/layouts/custom.py --- a/hgsubversion/layouts/custom.py +++ b/hgsubversion/layouts/custom.py @@ -12,13 +12,13 @@ import base class CustomLayout(base.BaseLayout): - def __init__(self, ui): - base.BaseLayout.__init__(self, ui) + def __init__(self, meta): + base.BaseLayout.__init__(self, meta) self.svn_to_hg = {} self.hg_to_svn = {} - for hg_branch, svn_path in ui.configitems('hgsubversionbranch'): + for hg_branch, svn_path in meta.ui.configitems('hgsubversionbranch'): hg_branch = hg_branch.strip() if hg_branch == 'default' or not hg_branch: diff --git a/hgsubversion/layouts/detect.py b/hgsubversion/layouts/detect.py --- a/hgsubversion/layouts/detect.py +++ b/hgsubversion/layouts/detect.py @@ -12,7 +12,7 @@ from mercurial import util as hgutil import __init__ as layouts -def layout_from_subversion(svn, revision=None, ui=None): +def layout_from_subversion(svn, revision=None, meta=None): """ Guess what layout to use based on directories under the svn root. This is intended for use during bootstrapping. It guesses which @@ -39,10 +39,10 @@ def layout_from_subversion(svn, revision layout = 'standard' else: layout = 'single' - ui.setconfig('hgsubversion', 'layout', layout) + meta.ui.setconfig('hgsubversion', 'layout', layout) return layout -def layout_from_config(ui, allow_auto=False): +def layout_from_config(meta, allow_auto=False): """ Load the layout we are using based on config We will read the config from the ui object. Pass allow_auto=True @@ -51,32 +51,29 @@ def layout_from_config(ui, allow_auto=Fa detect the layout as auto. """ - layout = ui.config('hgsubversion', 'layout', default='auto') + layout = meta.ui.config('hgsubversion', 'layout', default='auto') if layout == 'auto' and not allow_auto: raise hgutil.Abort('layout not yet determined') elif layout not in layouts.NAME_TO_CLASS and layout != 'auto': raise hgutil.Abort("unknown layout '%s'" % layout) return layout -def layout_from_file(metapath, ui=None): +def layout_from_file(meta): """ Load the layout in use from the metadata file. - - If you pass the ui arg, we will also write the layout to the - config for that ui. - """ - layout = None - layoutfile = os.path.join(metapath, 'layout') - if os.path.exists(layoutfile): - f = open(layoutfile) - layout = f.read().strip() - f.close() - if ui: - ui.setconfig('hgsubversion', 'layout', layout) + # import late to avoid trouble when running the test suite + try: + from hgext_hgsubversion import util + except ImportError: + from hgsubversion import util + + layout = util.load(meta.layout_file) + if layout: + meta.ui.setconfig('hgsubversion', 'layout', layout) return layout -def layout_from_commit(subdir, revpath, branch, ui): +def layout_from_commit(subdir, revpath, branch, meta): """ Guess what the layout is based existing commit info Specifically, this compares the subdir for the repository and the @@ -93,7 +90,7 @@ def layout_from_commit(subdir, revpath, candidates = set() for layout in layouts.NAME_TO_CLASS: - layoutobj = layouts.layout_from_name(layout, ui) + layoutobj = layouts.layout_from_name(layout, meta) try: remotepath = layoutobj.remotepath(branch, subdir) except KeyError: @@ -104,7 +101,7 @@ def layout_from_commit(subdir, revpath, if len(candidates) == 1: return candidates.pop() elif candidates: - config_layout = layout_from_config(ui, allow_auto=True) + config_layout = layout_from_config(meta, allow_auto=True) if config_layout in candidates: return config_layout diff --git a/hgsubversion/layouts/persist.py b/hgsubversion/layouts/persist.py deleted file mode 100644 --- a/hgsubversion/layouts/persist.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Code for persisting the layout config in various locations. - -Basically, if you want to save the layout, this is where you should go -to do it. - -""" - -import os.path - -def layout_to_file(metapath, layout): - """Save the given layout to a file under the given metapath""" - - layoutfile = os.path.join(metapath, 'layout') - f = open(layoutfile, 'w') - f.write(layout) - f.close() diff --git a/hgsubversion/layouts/standard.py b/hgsubversion/layouts/standard.py --- a/hgsubversion/layouts/standard.py +++ b/hgsubversion/layouts/standard.py @@ -5,52 +5,60 @@ import base class StandardLayout(base.BaseLayout): """The standard trunk, branches, tags layout""" - def __init__(self, ui): - base.BaseLayout.__init__(self, ui) + def __init__(self, meta): + base.BaseLayout.__init__(self, meta) self._tag_locations = None - self._branch_dir = ui.config('hgsubversion', 'branchdir', 'branches') - if self._branch_dir[0] == '/': - self._branch_dir = self._branch_dir[1:] - if self._branch_dir[-1] != '/': - self._branch_dir += '/' - - self._infix = ui.config('hgsubversion', 'infix', '').strip('/') - if self._infix: - self._infix = '/' + self._infix - - self._trunk = 'trunk%s' % self._infix + # branchdir is expected to be stripped of leading slashes but retain + # its last slash + meta._gen_cachedconfig('branchdir', 'branches', + pre=lambda x: '/'.join(p for p in x.split('/') + if p) + '/') + + # infix is expected to be stripped of trailing slashes but retain + # its first slash + def _infix_transform(x): + x = '/'.join(p for p in x.split('/') if p) + if x: + x = '/' + x + return x + meta._gen_cachedconfig('infix', '', pre=_infix_transform) + + @property + def trunk(self): + return 'trunk' + self.meta.infix def localname(self, path): - if path == self._trunk: + if path == self.trunk: return None - elif path.startswith(self._branch_dir) and path.endswith(self._infix): - path = path[len(self._branch_dir):] - if self._infix: - path = path[:-len(self._infix)] + elif path.startswith(self.meta.branchdir) and path.endswith(self.meta.infix): + path = path[len(self.meta.branchdir):] + if self.meta.infix: + path = path[:-len(self.meta.infix)] return path return '../%s' % path def remotename(self, branch): if branch == 'default' or branch is None: - path = self._trunk + path = self.trunk elif branch.startswith('../'): path = branch[3:] else: - path = ''.join((self._branch_dir, branch, self._infix)) + path = ''.join((self.meta.branchdir, branch, self.meta.infix)) return path def remotepath(self, branch, subdir='/'): if subdir == '/': subdir = '' - branchpath = self._trunk + branchpath = self.trunk if branch and branch != 'default': if branch.startswith('../'): branchpath = branch[3:] else: - branchpath = ''.join((self._branch_dir, branch, self._infix)) + branchpath = ''.join((self.meta.branchdir, branch, + self.meta.infix)) return '%s/%s' % (subdir or '', branchpath) @@ -69,9 +77,9 @@ class StandardLayout(base.BaseLayout): self._tag_locations = util.load(tag_locations_file) if not self._tag_locations: - self._tag_locations = self.ui.configlist('hgsubversion', - 'tagpaths', - ['tags']) + self._tag_locations = self.meta.ui.configlist('hgsubversion', + 'tagpaths', + ['tags']) util.dump(self._tag_locations, tag_locations_file) # ensure nested paths are handled properly @@ -109,22 +117,22 @@ class StandardLayout(base.BaseLayout): return candidate, '/'.join(components) if path == 'trunk' or path.startswith('trunk/'): - return self._trunk, path[len(self._trunk) + 1:] + return self.trunk, path[len(self.trunk) + 1:] - if path.startswith(self._branch_dir): - path = path[len(self._branch_dir):] + if path.startswith(self.meta.branchdir): + path = path[len(self.meta.branchdir):] components = path.split('/', 1) - branch_path = ''.join((self._branch_dir, components[0])) + branch_path = ''.join((self.meta.branchdir, components[0])) if len(components) == 1: local_path = '' else: local_path = components[1] if local_path == '': - branch_path += self._infix - elif local_path.startswith(self._infix[1:] + '/'): - branch_path += self._infix - local_path = local_path[len(self._infix):] + branch_path += self.meta.infix + elif local_path.startswith(self.meta.infix[1:] + '/'): + branch_path += self.meta.infix + local_path = local_path[len(self.meta.infix):] return branch_path, local_path components = path.split('/') diff --git a/hgsubversion/stupid.py b/hgsubversion/stupid.py --- a/hgsubversion/stupid.py +++ b/hgsubversion/stupid.py @@ -171,61 +171,23 @@ def filteriterhunks(meta): yield data return filterhunks -def patchrepoold(ui, meta, parentctx, patchfp): - files = {} - try: - oldpatchfile = patch.patchfile - olditerhunks = patch.iterhunks - patch.patchfile = mempatchproxy(parentctx, files) - patch.iterhunks = filteriterhunks(meta) - try: - # We can safely ignore the changed list since we are - # handling non-git patches. Touched files are known - # by our memory patcher. - patch_st = patch.applydiff(ui, patchfp, {}, strip=0) - finally: - patch.patchfile = oldpatchfile - patch.iterhunks = olditerhunks - except patch.PatchError: - # TODO: this happens if the svn server has the wrong mime - # type stored and doesn't know a file is binary. It would - # be better to do one file at a time and only do a - # full fetch on files that had problems. - raise BadPatchApply('patching failed') - # if this patch didn't apply right, fall back to exporting the - # entire rev. - if patch_st == -1: - assert False, ('This should only happen on case-insensitive' - ' volumes.') - elif patch_st == 1: - # When converting Django, I saw fuzz on .po files that was - # causing revisions to end up failing verification. If that - # can be fixed, maybe this won't ever be reached. - raise BadPatchApply('patching succeeded with fuzz') - return files - -try: - class svnbackend(patch.repobackend): - def getfile(self, fname): - # In Mercurial >= 3.2, if fname is missing, data will be None and we - # should return None, None in that case. Earlier versions will raise - # an IOError which we let propagate up the stack. - f = super(svnbackend, self).getfile(fname) - if f is None: - return None, None - data, flags = f - if data is None: - return None, None - islink, isexec = flags - if islink: - data = 'link ' + data - return data, (islink, isexec) -except AttributeError: - svnbackend = None +class svnbackend(patch.repobackend): + def getfile(self, fname): + # In Mercurial >= 3.2, if fname is missing, data will be None and we + # should return None, None in that case. Earlier versions will raise + # an IOError which we let propagate up the stack. + f = super(svnbackend, self).getfile(fname) + if f is None: + return None, None + data, flags = f + if data is None: + return None, None + islink, isexec = flags + if islink: + data = 'link ' + data + return data, (islink, isexec) def patchrepo(ui, meta, parentctx, patchfp): - if not svnbackend: - return patchrepoold(ui, meta, parentctx, patchfp) store = patch.filestore(util.getfilestoresize(ui)) try: touched = set() diff --git a/hgsubversion/svncommands.py b/hgsubversion/svncommands.py --- a/hgsubversion/svncommands.py +++ b/hgsubversion/svncommands.py @@ -189,11 +189,11 @@ def _buildmeta(ui, repo, args, partial=F if layout is None: layout = layouts.detect.layout_from_commit(subdir, revpath, - ctx.branch(), ui) - existing_layout = layouts.detect.layout_from_file(meta.metapath) + ctx.branch(), meta) + existing_layout = layouts.detect.layout_from_file(meta) if layout != existing_layout: - layouts.persist.layout_to_file(meta.metapath, layout) - layoutobj = layouts.layout_from_name(layout, ui) + util.dump(layout, meta.layout_file) + layoutobj = layouts.layout_from_name(layout, meta) elif layout == 'single': assert (subdir or '/') == revpath, ('Possible layout detection' ' defect in replay') diff --git a/hgsubversion/svnmeta.py b/hgsubversion/svnmeta.py --- a/hgsubversion/svnmeta.py +++ b/hgsubversion/svnmeta.py @@ -22,44 +22,46 @@ class SVNMeta(object): subdir is the subdirectory of the edits *on the svn server*. It is needed for stripping paths off in certain cases. """ + # simple and public variables self.ui = repo.ui self.repo = repo self.path = os.path.normpath(repo.join('..')) + self.firstpulled = 0 + self.lastdate = '1970-01-01 00:00:00 -0000' + self.addedtags = {} + self.deletedtags = {} + + # private variables self._skiperror = skiperrorcheck + self._tags = None + self._layoutobj = None + self._revmap = None + self._authors = None + self._branchmap = None + self._tagmap = None + self._filemap = None + # create .hg/svn folder if it doesn't exist if not os.path.isdir(self.metapath): os.makedirs(self.metapath) + + # properties that need .hg/svn to exist self.uuid = uuid self.subdir = subdir - self._revmap = None - self.firstpulled = 0 + # generated properties that have a persistent file stored on disk self._gen_cachedconfig('lastpulled', 0, configname=False) self._gen_cachedconfig('defaultauthors', True) self._gen_cachedconfig('caseignoreauthors', False) self._gen_cachedconfig('defaulthost', self.uuid) self._gen_cachedconfig('usebranchnames', True) + # misc self.branches = util.load(self.branch_info_file) or {} self.prevbranches = dict(self.branches) - self._tags = None - self._layout = layouts.detect.layout_from_file(self.metapath, - ui=self.repo.ui) - self._layoutobj = None - - self._authors = None - - self._branchmap = None - - self._tagmap = None - - self._filemap = None - - self.lastdate = '1970-01-01 00:00:00 -0000' - self.addedtags = {} - self.deletedtags = {} + self._layout = layouts.detect.layout_from_file(self) - def _get_cachedconfig(self, name, filename, configname, default): + def _get_cachedconfig(self, name, filename, configname, default, pre): """Return a cached value for a config option. If the cache is uninitialized then try to read its value from disk. Option can be overridden by the commandline. @@ -67,6 +69,7 @@ class SVNMeta(object): filename: name of file in .hg/svn configname: commandline option name default: default value + pre: transformation to apply to a value before caching it. """ varname = '_' + name if getattr(self, varname) is None: @@ -97,6 +100,10 @@ class SVNMeta(object): if c is not None and c != val and c != default: val = c + # apply transformation if necessary + if pre: + val = pre(val) + # set the value as the one from disk (or default if not found) setattr(self, varname, val) @@ -112,7 +119,7 @@ class SVNMeta(object): util.dump(value, f) def _gen_cachedconfig(self, name, default=None, filename=None, - configname=None): + configname=None, pre=None): """Generate an attribute for reading (and caching) config data. This method constructs a new attribute on self with the given name. @@ -125,29 +132,34 @@ class SVNMeta(object): filename = name if configname is None: configname = name - prop = property(lambda x: x._get_cachedconfig(name, - filename, - configname, - default), - lambda x, y: x._set_cachedconfig(y, - name, - filename)) + prop = property(lambda x: self._get_cachedconfig(name, + filename, + configname, + default, + pre=pre), + lambda x, y: self._set_cachedconfig(y, + name, + filename)) setattr(SVNMeta, name, prop) + @property + def layout_file(self): + return os.path.join(self.metapath, 'layout') + @property def layout(self): # this method can't determine the layout, but it needs to be # resolved into something other than auto before this ever # gets called if not self._layout or self._layout == 'auto': - self._layout = layouts.detect.layout_from_config(self.repo.ui) - layouts.persist.layout_to_file(self.metapath, self._layout) + self._layout = layouts.detect.layout_from_config(self) + util.dump(self._layout, self.layout_file) return self._layout @property def layoutobj(self): if not self._layoutobj: - self._layoutobj = layouts.layout_from_name(self.layout, self.ui) + self._layoutobj = layouts.layout_from_name(self.layout, self) return self._layoutobj @property diff --git a/hgsubversion/wrappers.py b/hgsubversion/wrappers.py --- a/hgsubversion/wrappers.py +++ b/hgsubversion/wrappers.py @@ -398,11 +398,11 @@ def pull(repo, source, heads=[], force=F stopat_rev = util.parse_revnum(svn, checkout) - layout = layouts.detect.layout_from_config(repo.ui, allow_auto=True) + layout = layouts.detect.layout_from_config(meta, allow_auto=True) if layout == 'auto': layout = layouts.detect.layout_from_subversion(svn, (stopat_rev or None), - repo.ui) + meta) repo.ui.note('using %s layout\n' % layout) branch = repo.ui.config('hgsubversion', 'branch')