# HG changeset patch # User Augie Fackler # Date 1415209739 18000 # Node ID 139a44a63090b2c0f8e05d7d1104d3d9c9e26f57 # Parent 807c443928d4b033eb7254100de1f48a47e93e4a# Parent d07ccad28b1a8f8fdef4144812e1b9af75ac7f6b Merge default into stable for a release. diff --git a/.hgtags b/.hgtags --- a/.hgtags +++ b/.hgtags @@ -9,6 +9,7 @@ 0cbf9fd89672e73165e1bb4db1ec8f7f65b95c94 07234759a3f750029ccaa001837d42fa12dd33ee 1.4 77b22e5b4ea6c248e079afd0f1e544cb5690ce20 1.5 d0f3a5c2cb56ce65d9ef1c611c8bfbebdc3bef34 1.5.1 +7d47a0f731354505ed9ae8d60d2a6996e8c3294f 1.6 8caf1226adecb322e90ddb3817c604fa2fe8a66d 1.6.1 36f6d51b4edc31f1f9ce2d0d02965a85dd26a455 1.6.2 46523cdfd3b0cee0bf1366ab587686bb65211747 1.6.3 diff --git a/hgsubversion/__init__.py b/hgsubversion/__init__.py --- a/hgsubversion/__init__.py +++ b/hgsubversion/__init__.py @@ -20,9 +20,16 @@ import sys import traceback from mercurial import commands +try: + from mercurial import exchange + exchange.push # existed in first iteration of this file +except ImportError: + # We only *use* the exchange module in hg 3.2+, so this is safe + pass from mercurial import extensions from mercurial import help from mercurial import hg +from mercurial import localrepo from mercurial import util as hgutil from mercurial import demandimport demandimport.ignore.extend([ @@ -33,27 +40,9 @@ demandimport.ignore.extend([ 'svn.ra', ]) -try: - from mercurial import templatekw - # force demandimport to load templatekw - templatekw.keywords -except ImportError: - templatekw = None - -try: - from mercurial import revset - # force demandimport to load revset - revset.methods -except ImportError: - revset = None - -try: - from mercurial import subrepo - # require svnsubrepo and hg >= 1.7.1 - subrepo.svnsubrepo - hgutil.checknlink -except (ImportError, AttributeError), e: - subrepo = None +from mercurial import templatekw +from mercurial import revset +from mercurial import subrepo import svncommands import util @@ -124,9 +113,8 @@ except AttributeError: except ImportError: pass -def extsetup(): +def extsetup(ui): """insert command wrappers for a bunch of commands""" - # add the ui argument to this function once we drop support for 1.3 docvals = {'extension': 'hgsubversion'} for cmd, (generic, target, fixdoc, ppopts, opts) in wrapcmds.iteritems(): @@ -155,6 +143,13 @@ def extsetup(): except: pass + if not hgutil.safehasattr(localrepo.localrepository, 'push'): + # Mercurial >= 3.2 + extensions.wrapfunction(exchange, 'push', wrappers.exchangepush) + if not hgutil.safehasattr(localrepo.localrepository, 'pull'): + # Mercurial >= 3.2 + extensions.wrapfunction(exchange, 'pull', wrappers.exchangepull) + helpdir = os.path.join(os.path.dirname(__file__), 'help') entries = ( @@ -163,20 +158,13 @@ def extsetup(): lambda: open(os.path.join(helpdir, 'subversion.rst')).read()), ) - # in 1.6 and earler the help table is a tuple - if getattr(help.helptable, 'extend', None): - help.helptable.extend(entries) - else: - help.helptable = help.helptable + entries + help.helptable.extend(entries) - if templatekw: - templatekw.keywords.update(util.templatekeywords) + templatekw.keywords.update(util.templatekeywords) - if revset: - revset.symbols.update(util.revsets) + revset.symbols.update(util.revsets) - if subrepo: - subrepo.types['hgsubversion'] = svnexternals.svnsubrepo + subrepo.types['hgsubversion'] = svnexternals.svnsubrepo def reposetup(ui, repo): if repo.local(): @@ -184,7 +172,7 @@ def reposetup(ui, repo): for tunnel in ui.configlist('hgsubversion', 'tunnels'): hg.schemes['svn+' + tunnel] = svnrepo - if revset and ui.configbool('hgsubversion', 'nativerevs'): + if ui.configbool('hgsubversion', 'nativerevs'): extensions.wrapfunction(revset, 'stringset', util.revset_stringset) _old_local = hg.schemes['file'] diff --git a/hgsubversion/compathacks.py b/hgsubversion/compathacks.py --- a/hgsubversion/compathacks.py +++ b/hgsubversion/compathacks.py @@ -1,4 +1,7 @@ -"""Functions to work around API changes inside Mercurial.""" +"""Functions to work around API changes.""" + +import errno +import sys def branchset(repo): """Return the set of branches present in a repo. @@ -25,3 +28,42 @@ def makememfilectx(repo, path, data, isl return context.memfilectx(repo, path, data, islink, isexec, copied) except TypeError: return context.memfilectx(path, data, islink, isexec, copied) + +def filectxfn_deleted(memctx, path): + """ + Return None or raise an IOError as necessary if path is deleted. + + Call as: + + if path_missing: + return compathacks.filectxfn_deleted(memctx, path) + + Works around filectxfn's contract changing between 3.1 and 3.2: 3.2 onwards, + for deleted files, filectxfn should return None rather than returning + IOError. + """ + if getattr(memctx, '_returnnoneformissingfiles', False): + return None + raise IOError(errno.ENOENT, '%s is deleted' % path) + +def filectxfn_deleted_reraise(memctx): + """ + Return None or reraise exc as necessary. + + Call as: + + try: + # code that raises IOError if the path is missing + except IOError: + return compathacks.filectxfn_deleted_reraise(memctx) + + Works around filectxfn's contract changing between 3.1 and 3.2: 3.2 onwards, + for deleted files, filectxfn should return None rather than returning + IOError. + """ + exc_info = sys.exc_info() + if (exc_info[1].errno == errno.ENOENT and + getattr(memctx, '_returnnoneformissingfiles', False)): + return None + # preserve traceback info + raise exc_info[0], exc_info[1], exc_info[2] diff --git a/hgsubversion/editor.py b/hgsubversion/editor.py --- a/hgsubversion/editor.py +++ b/hgsubversion/editor.py @@ -31,6 +31,13 @@ class FileStore(object): if fname in self._popped: raise EditingError('trying to set a popped file %s' % fname) + if fname in self._data: + self._size -= len(self._data[fname]) + del self._data[fname] + + if fname in self._files: + del self._files[fname] + if self._maxsize < 0 or (len(data) + self._size) <= self._maxsize: self._data[fname] = data self._size += len(data) @@ -239,6 +246,13 @@ class HgEditor(svnwrap.Editor): else: # Resolve missing directories content immediately so the # missing files maybe processed by delete actions. + # we remove the missing directory entries to deal with the case + # where a directory is replaced from e.g. a closed branch + # this will show up as a delete and then a copy + # we process deletes after missing, so we can handle a directory + # copy plus delete of file in that directory. This means that we + # need to be sure that only things whose final disposition is + # deletion remain in self._deleted at the end of the editing process. rev = self.current.rev.revnum path = path + '/' parentdir = path[len(root):] @@ -248,6 +262,7 @@ class HgEditor(svnwrap.Editor): f = parentdir + f if not self.meta.is_path_valid(f, False): continue + self._deleted.discard(f) self._missing.add(f) @svnwrap.ieditor @@ -365,7 +380,10 @@ class HgEditor(svnwrap.Editor): fctx = ctx.filectx(from_file) flags = fctx.flags() - self.current.set(path, fctx.data(), 'x' in flags, 'l' in flags) + base = fctx.data() + if 'l' in flags: + base = 'link ' + base + self.current.set(path, base, 'x' in flags, 'l' in flags) copypath = None if from_branch == branch: parentid = self.meta.get_parent_revision( @@ -374,7 +392,7 @@ class HgEditor(svnwrap.Editor): parentctx = self._getctx(parentid) if util.issamefile(parentctx, ctx, from_file): copypath = from_file - return self._openfile(path, fctx.data(), 'x' in flags, 'l' in flags, + return self._openfile(path, base, 'x' in flags, 'l' in flags, copypath, create=True) @svnwrap.ieditor @@ -431,7 +449,7 @@ class HgEditor(svnwrap.Editor): source_rev = copyfrom_revision frompath, source_branch = self.meta.split_branch_path(copyfrom_path)[:2] new_hash = self.meta.get_parent_revision(source_rev + 1, source_branch, True) - if new_hash == node.nullid: + if frompath is None or new_hash == node.nullid: self.addmissing(path, isdir=True) return baton fromctx = self._getctx(new_hash) @@ -636,7 +654,7 @@ class HgEditor(svnwrap.Editor): svn.init_ra_and_client() i += 1 data, mode = svn.get_file(f, rev) - self.current.set(f, data, 'x' in mode, 'l' in mode) + self.current.set(root + f, data, 'x' in mode, 'l' in mode) if not self.ui.debugflag: self.ui.note('\n') diff --git a/hgsubversion/help/subversion.rst b/hgsubversion/help/subversion.rst --- a/hgsubversion/help/subversion.rst +++ b/hgsubversion/help/subversion.rst @@ -45,8 +45,8 @@ issue ``hg clone http://python-nose.goog works with any directory with a Subversion repository, and is known as a single directory clone. Normally, converted changesets will be marked as belonging to the ``default`` branch, but this can be changed by using the ``-b/--branch`` -option when using Mercurial 1.5 or later. To force single directory clone, use -hgsubversion.layout option (see below for detailed help) :: +option. To force single directory clone, use hgsubversion.layout option (see +below for detailed help) :: $ hg clone --layout single svn+http://python-nose.googlecode.com/svn nose-hg @@ -85,8 +85,6 @@ An example:: $ hg log --template='{rev}:{node|short} {author|user}\nsvn: {svnrev}\n' -The template keywords are available when using Mercurial 1.5 or later. - For finding changesets from Subversion, hgsubversion extends revsets to provide two new selectors: @@ -100,9 +98,7 @@ For example:: $ hg log -r 'fromsvn()' $ hg log -r 'svnrev(500)' -Revsets are available when using Mercurial 1.6 or later and are -accepted by several Mercurial commands for specifying revisions. See -``hg help revsets`` for details. +See ``hg help revsets`` for details. Support for externals --------------------- @@ -146,7 +142,7 @@ related Subversion repository. Alternatively, one can use the ``hgsubversion.externals`` in hgrc to specify ``subrepos`` as the externals mode. In this mode, ``.hgsub`` and ``.hgsubstate`` files will be used instead of -``.hgsvnexternals``. This feature requires Mercurial 1.7.1 or later. +``.hgsvnexternals``. Using Subrepositories @@ -183,8 +179,6 @@ with the revision identifier replaced wi This mode has the following limitations: -* Require Mercurial >= 1.7.1 to work correctly on all platforms. - * "hgsubversion" subrepositories require hgsubversion extension to be available. To operate transparently on ``svn:externals`` we have to stay as close as possible to their original property @@ -365,10 +359,10 @@ settings: when necessary. ``hgsubversion.externals`` - Set to ``subrepos`` to switch to subrepos-based externals support - (requires Mercurial 1.7.1 or later.) Default is ``svnexternals``, - which uses a custom hgsubversion-specific format and works on - older versions of Mercurial. Use ``ignore`` to avoid converting externals. + Set to ``subrepos`` to switch to subrepos-based externals support. Default + is ``svnexternals``, which uses a custom hgsubversion-specific format and + works on older versions of Mercurial. Use ``ignore`` to avoid converting + externals. The following options only have an effect on the initial clone of a repository: diff --git a/hgsubversion/layouts/base.py b/hgsubversion/layouts/base.py --- a/hgsubversion/layouts/base.py +++ b/hgsubversion/layouts/base.py @@ -48,7 +48,7 @@ class BaseLayout(object): """ self.__unimplemented('remotepath') - def taglocations(self, meta_data_dir): + def taglocations(self, metapath): """Return a list of locations within svn to search for tags Should be returned in reverse-sorted order. diff --git a/hgsubversion/layouts/custom.py b/hgsubversion/layouts/custom.py --- a/hgsubversion/layouts/custom.py +++ b/hgsubversion/layouts/custom.py @@ -64,7 +64,7 @@ class CustomLayout(base.BaseLayout): subdir += '/' return subdir + self.remotename(branch) - def taglocations(self, meta_data_dir): + def taglocations(self, metapath): return [] def get_path_tag(self, path, taglocations): diff --git a/hgsubversion/layouts/detect.py b/hgsubversion/layouts/detect.py --- a/hgsubversion/layouts/detect.py +++ b/hgsubversion/layouts/detect.py @@ -58,7 +58,7 @@ def layout_from_config(ui, allow_auto=Fa raise hgutil.Abort("unknown layout '%s'" % layout) return layout -def layout_from_file(meta_data_dir, ui=None): +def layout_from_file(metapath, ui=None): """ Load the layout in use from the metadata file. If you pass the ui arg, we will also write the layout to the @@ -67,7 +67,7 @@ def layout_from_file(meta_data_dir, ui=N """ layout = None - layoutfile = os.path.join(meta_data_dir, 'layout') + layoutfile = os.path.join(metapath, 'layout') if os.path.exists(layoutfile): f = open(layoutfile) layout = f.read().strip() diff --git a/hgsubversion/layouts/persist.py b/hgsubversion/layouts/persist.py --- a/hgsubversion/layouts/persist.py +++ b/hgsubversion/layouts/persist.py @@ -7,10 +7,10 @@ to do it. import os.path -def layout_to_file(meta_data_dir, layout): - """Save the given layout to a file under the given meta_data_dir""" +def layout_to_file(metapath, layout): + """Save the given layout to a file under the given metapath""" - layoutfile = os.path.join(meta_data_dir, 'layout') + layoutfile = os.path.join(metapath, 'layout') f = open(layoutfile, 'w') f.write(layout) f.close() diff --git a/hgsubversion/layouts/single.py b/hgsubversion/layouts/single.py --- a/hgsubversion/layouts/single.py +++ b/hgsubversion/layouts/single.py @@ -14,7 +14,7 @@ class SingleLayout(base.BaseLayout): def remotepath(self, branch, subdir='/'): return subdir or '/' - def taglocations(self, meta_data_dir): + def taglocations(self, metapath): return [] def get_path_tag(self, path, taglocations): diff --git a/hgsubversion/layouts/standard.py b/hgsubversion/layouts/standard.py --- a/hgsubversion/layouts/standard.py +++ b/hgsubversion/layouts/standard.py @@ -1,5 +1,4 @@ import os.path -import pickle import base @@ -55,7 +54,7 @@ class StandardLayout(base.BaseLayout): return '%s/%s' % (subdir or '', branchpath) - def taglocations(self, meta_data_dir): + def taglocations(self, metapath): # import late to avoid trouble when running the test suite try: # newer versions of mercurial >= 2.8 will import this because the @@ -66,17 +65,14 @@ class StandardLayout(base.BaseLayout): if self._tag_locations is None: - tag_locations_file = os.path.join(meta_data_dir, 'tag_locations') + tag_locations_file = os.path.join(metapath, 'tag_locations') + self._tag_locations = util.load(tag_locations_file) - if os.path.exists(tag_locations_file): - f = open(tag_locations_file) - self._tag_locations = pickle.load(f) - f.close() - else: + if not self._tag_locations: self._tag_locations = self.ui.configlist('hgsubversion', 'tagpaths', ['tags']) - util.pickle_atomic(self._tag_locations, tag_locations_file) + util.dump(self._tag_locations, tag_locations_file) # ensure nested paths are handled properly self._tag_locations.sort() diff --git a/hgsubversion/maps.py b/hgsubversion/maps.py --- a/hgsubversion/maps.py +++ b/hgsubversion/maps.py @@ -3,7 +3,7 @@ import errno import os from mercurial import util as hgutil -from mercurial import node +from mercurial.node import bin, hex, nullid import svncommands import util @@ -14,12 +14,12 @@ class AuthorMap(dict): If the 'hgsubversion.defaultauthors' configuration option is set to false, attempting to obtain an unknown author will fail with an Abort. - + If the 'hgsubversion.caseignoreauthors' configuration option is set to true, the userid from Subversion is always compared lowercase. ''' - def __init__(self, ui, path, defaulthost=None): + def __init__(self, meta): '''Initialise a new AuthorMap. The ui argument is used to print diagnostic messages. @@ -27,17 +27,19 @@ class AuthorMap(dict): The path argument is the location of the backing store, typically .hg/svn/authors. ''' - self.ui = ui - self.path = path - self.use_defaultauthors = self.ui.configbool('hgsubversion', 'defaultauthors', True) - self.caseignoreauthors = self.ui.configbool('hgsubversion', 'caseignoreauthors', False) - if defaulthost: - self.defaulthost = '@%s' % defaulthost.lstrip('@') - else: - self.defaulthost = '' + self.meta = meta + self.defaulthost = '' + if meta.defaulthost: + self.defaulthost = '@%s' % meta.defaulthost.lstrip('@') + self.super = super(AuthorMap, self) self.super.__init__() - self.load(path) + self.load(self.meta.authors_file) + + # append authors specified from the commandline + clmap = util.configpath(self.meta.ui, 'authormap') + if clmap: + self.load(clmap) def load(self, path): ''' Load mappings from a file at the specified path. ''' @@ -47,10 +49,10 @@ class AuthorMap(dict): return writing = False - if path != self.path: - writing = open(self.path, 'a') + if path != self.meta.authors_file: + writing = open(self.meta.authors_file, 'a') - self.ui.debug('reading authormap from %s\n' % path) + self.meta.ui.debug('reading authormap from %s\n' % path) f = open(path, 'r') for number, line_org in enumerate(f): @@ -62,21 +64,21 @@ class AuthorMap(dict): src, dst = line.split('=', 1) except (IndexError, ValueError): msg = 'ignoring line %i in author map %s: %s\n' - self.ui.status(msg % (number, path, line.rstrip())) + self.meta.ui.status(msg % (number, path, line.rstrip())) continue src = src.strip() dst = dst.strip() - if self.caseignoreauthors: + if self.meta.caseignoreauthors: src = src.lower() if writing: if not src in self: - self.ui.debug('adding author %s to author map\n' % src) + self.meta.ui.debug('adding author %s to author map\n' % src) elif dst != self[src]: msg = 'overriding author: "%s" to "%s" (%s)\n' - self.ui.status(msg % (self[src], dst, src)) + self.meta.ui.status(msg % (self[src], dst, src)) writing.write(line_org) self[src] = dst @@ -92,21 +94,20 @@ class AuthorMap(dict): if author is None: author = '(no author)' - if self.caseignoreauthors: + search_author = author + if self.meta.caseignoreauthors: search_author = author.lower() - else: - search_author = author if search_author in self: result = self.super.__getitem__(search_author) - elif self.use_defaultauthors: + elif self.meta.defaultauthors: self[author] = result = '%s%s' % (author, self.defaulthost) msg = 'substituting author "%s" for default "%s"\n' - self.ui.debug(msg % (author, result)) + self.meta.ui.debug(msg % (author, result)) else: msg = 'author %s has no entry in the author map!' raise hgutil.Abort(msg % author) - self.ui.debug('mapping author "%s" to "%s"\n' % (author, result)) + self.meta.ui.debug('mapping author "%s" to "%s"\n' % (author, result)) return result def reverselookup(self, author): @@ -126,27 +127,23 @@ class Tags(dict): """ VERSION = 2 - @classmethod - def filepath(cls, repo): - return os.path.join(repo.path, 'svn', 'tagmap') - - def __init__(self, repo, endrev=None): + def __init__(self, meta, endrev=None): dict.__init__(self) - self.path = self.filepath(repo) + self.meta = meta self.endrev = endrev - if os.path.isfile(self.path): - self._load(repo) + if os.path.isfile(self.meta.tagfile): + self._load() else: self._write() - def _load(self, repo): - f = open(self.path) + def _load(self): + f = open(self.meta.tagfile) ver = int(f.readline()) if ver < self.VERSION: - repo.ui.status('tag map outdated, running rebuildmeta...\n') + self.meta.ui.status('tag map outdated, running rebuildmeta...\n') f.close() - os.unlink(self.path) - svncommands.rebuildmeta(repo.ui, repo, ()) + os.unlink(self.meta.tagfile) + svncommands.rebuildmeta(self.meta.ui, self.meta.repo, ()) return elif ver != self.VERSION: raise hgutil.Abort('tagmap too new -- please upgrade') @@ -158,12 +155,12 @@ class Tags(dict): break if not tag: continue - dict.__setitem__(self, tag, node.bin(ha)) + dict.__setitem__(self, tag, bin(ha)) f.close() def _write(self): assert self.endrev is None - f = open(self.path, 'w') + f = open(self.meta.tagfile, 'w') f.write('%s\n' % self.VERSION) f.close() @@ -173,7 +170,7 @@ class Tags(dict): def __contains__(self, tag): return (tag and dict.__contains__(self, tag) - and dict.__getitem__(self, tag) != node.nullid) + and dict.__getitem__(self, tag) != nullid) def __getitem__(self, tag): if tag and tag in self: @@ -184,8 +181,8 @@ class Tags(dict): if not tag: raise hgutil.Abort('tag cannot be empty') ha, revision = info - f = open(self.path, 'a') - f.write('%s %s %s\n' % (node.hex(ha), revision, tag)) + f = open(self.meta.tagfile, 'a') + f.write('%s %s %s\n' % (hex(ha), revision, tag)) f.close() dict.__setitem__(self, tag, ha) @@ -194,32 +191,15 @@ class RevMap(dict): VERSION = 1 - def __init__(self, repo): + def __init__(self, meta): dict.__init__(self) - self.path = self.mappath(repo) - self.repo = repo - self.ypath = os.path.join(repo.path, 'svn', 'lastpulled') - # TODO(durin42): Consider moving management of the youngest - # file to svnmeta itself rather than leaving it here. - # must load youngest file first, or else self._load() can - # clobber the info - _yonngest_str = util.load_string(self.ypath, '0') - self._youngest = int(_yonngest_str.strip()) - self.oldest = 0 - if os.path.isfile(self.path): + self.meta = meta + + if os.path.isfile(self.meta.revmap_file): self._load() else: self._write() - def _set_youngest(self, rev): - self._youngest = max(self._youngest, rev) - util.save_string(self.ypath, str(self._youngest) + '\n') - - def _get_youngest(self): - return self._youngest - - youngest = property(_get_youngest, _set_youngest) - def hashes(self): return dict((v, k) for (k, v) in self.iteritems()) @@ -227,14 +207,10 @@ class RevMap(dict): check = lambda x: x[0][1] == branch and x[0][0] < rev.revnum return sorted(filter(check, self.iteritems()), reverse=True) - @staticmethod - def mappath(repo): - return os.path.join(repo.path, 'svn', 'rev_map') - @classmethod - def readmapfile(cls, repo, missingok=True): + def readmapfile(cls, path, missingok=True): try: - f = open(cls.mappath(repo)) + f = open(path) except IOError, err: if not missingok or err.errno != errno.ENOENT: raise @@ -244,35 +220,41 @@ class RevMap(dict): raise hgutil.Abort('revmap too new -- please upgrade') return f + @util.gcdisable def _load(self): - for l in self.readmapfile(self.repo): + lastpulled = self.meta.lastpulled + firstpulled = self.meta.firstpulled + setitem = dict.__setitem__ + for l in self.readmapfile(self.meta.revmap_file): revnum, ha, branch = l.split(' ', 2) if branch == '\n': branch = None else: branch = branch[:-1] revnum = int(revnum) - if revnum > self.youngest or not self.youngest: - self.youngest = revnum - if revnum < self.oldest or not self.oldest: - self.oldest = revnum - dict.__setitem__(self, (revnum, branch), node.bin(ha)) + if revnum > lastpulled or not lastpulled: + lastpulled = revnum + if revnum < firstpulled or not firstpulled: + firstpulled = revnum + setitem(self, (revnum, branch), bin(ha)) + self.meta.lastpulled = lastpulled + self.meta.firstpulled = firstpulled def _write(self): - f = open(self.path, 'w') + f = open(self.meta.revmap_file, 'w') f.write('%s\n' % self.VERSION) f.close() def __setitem__(self, key, ha): revnum, branch = key - f = open(self.path, 'a') + f = open(self.meta.revmap_file, 'a') b = branch or '' - f.write(str(revnum) + ' ' + node.hex(ha) + ' ' + b + '\n') + f.write(str(revnum) + ' ' + hex(ha) + ' ' + b + '\n') f.close() - if revnum > self.youngest or not self.youngest: - self.youngest = revnum - if revnum < self.oldest or not self.oldest: - self.oldest = revnum + if revnum > self.meta.lastpulled or not self.meta.lastpulled: + self.meta.lastpulled = revnum + if revnum < self.meta.firstpulled or not self.meta.firstpulled: + self.meta.firstpulled = revnum dict.__setitem__(self, (revnum, branch), ha) @@ -280,7 +262,7 @@ class FileMap(object): VERSION = 1 - def __init__(self, ui, path): + def __init__(self, meta): '''Initialise a new FileMap. The ui argument is used to print diagnostic messages. @@ -288,15 +270,19 @@ class FileMap(object): The path argument is the location of the backing store, typically .hg/svn/filemap. ''' - self.ui = ui - self.path = path + self.meta = meta self.include = {} self.exclude = {} - if os.path.isfile(self.path): + if os.path.isfile(self.meta.filemap_file): self._load() else: self._write() + # append file mapping specified from the commandline + clmap = util.configpath(self.meta.ui, 'filemap') + if clmap: + self.load(clmap) + def _rpairs(self, name): e = len(name) while e != -1: @@ -335,19 +321,19 @@ class FileMap(object): mapping = getattr(self, m) if path in mapping: msg = 'duplicate %s entry in %s: "%s"\n' - self.ui.status(msg % (m, fn, path)) + self.meta.ui.status(msg % (m, fn, path)) return bits = m.rstrip('e'), path - self.ui.debug('%sing %s\n' % bits) + self.meta.ui.debug('%sing %s\n' % bits) # respect rule order mapping[path] = len(self) - if fn != self.path: - f = open(self.path, 'a') + if fn != self.meta.filemap_file: + f = open(self.meta.filemap_file, 'a') f.write(m + ' ' + path + '\n') f.close() def load(self, fn): - self.ui.debug('reading file map from %s\n' % fn) + self.meta.ui.debug('reading file map from %s\n' % fn) f = open(fn, 'r') self.load_fd(f, fn) f.close() @@ -363,22 +349,22 @@ class FileMap(object): if cmd in ('include', 'exclude'): self.add(fn, cmd, path) continue - self.ui.warn('unknown filemap command %s\n' % cmd) + self.meta.ui.warn('unknown filemap command %s\n' % cmd) except IndexError: msg = 'ignoring bad line in filemap %s: %s\n' - self.ui.warn(msg % (fn, line.rstrip())) + self.meta.ui.warn(msg % (fn, line.rstrip())) def _load(self): - self.ui.debug('reading in-repo file map from %s\n' % self.path) - f = open(self.path) + self.meta.ui.debug('reading in-repo file map from %s\n' % self.meta.filemap_file) + f = open(self.meta.filemap_file) ver = int(f.readline()) if ver != self.VERSION: raise hgutil.Abort('filemap too new -- please upgrade') - self.load_fd(f, self.path) + self.load_fd(f, self.meta.filemap_file) f.close() def _write(self): - f = open(self.path, 'w') + f = open(self.meta.filemap_file, 'w') f.write('%s\n' % self.VERSION) f.close() @@ -392,12 +378,16 @@ class BranchMap(dict): changes on other will now be on default (have no branch name set). ''' - def __init__(self, ui, path): - self.ui = ui - self.path = path + def __init__(self, meta): + self.meta = meta self.super = super(BranchMap, self) self.super.__init__() - self.load(path) + self.load(self.meta.branchmap_file) + + # append branch mapping specified from the commandline + clmap = util.configpath(self.meta.ui, 'branchmap') + if clmap: + self.load(clmap) def load(self, path): '''Load mappings from a file at the specified path.''' @@ -405,10 +395,10 @@ class BranchMap(dict): return writing = False - if path != self.path: - writing = open(self.path, 'a') + if path != self.meta.branchmap_file: + writing = open(self.meta.branchmap_file, 'a') - self.ui.debug('reading branchmap from %s\n' % path) + self.meta.ui.debug('reading branchmap from %s\n' % path) f = open(path, 'r') for number, line in enumerate(f): @@ -423,12 +413,12 @@ class BranchMap(dict): src, dst = line.split('=', 1) except (IndexError, ValueError): msg = 'ignoring line %i in branch map %s: %s\n' - self.ui.status(msg % (number, path, line.rstrip())) + self.meta.ui.status(msg % (number, path, line.rstrip())) continue src = src.strip() dst = dst.strip() - self.ui.debug('adding branch %s to branch map\n' % src) + self.meta.ui.debug('adding branch %s to branch map\n' % src) if not dst: # prevent people from assuming such lines are valid @@ -437,7 +427,7 @@ class BranchMap(dict): % (number, path)) elif src in self and dst != self[src]: msg = 'overriding branch: "%s" to "%s" (%s)\n' - self.ui.status(msg % (self[src], dst, src)) + self.meta.ui.status(msg % (self[src], dst, src)) self[src] = dst f.close() @@ -454,12 +444,16 @@ class TagMap(dict): the other tag will not be reflected in the hg repository. ''' - def __init__(self, ui, path): - self.ui = ui - self.path = path + def __init__(self, meta): + self.meta = meta self.super = super(TagMap, self) self.super.__init__() - self.load(path) + self.load(self.meta.tagmap_file) + + # append tag mapping specified from the commandline + clmap = util.configpath(self.meta.ui, 'tagmap') + if clmap: + self.load(clmap) def load(self, path): '''Load mappings from a file at the specified path.''' @@ -467,10 +461,10 @@ class TagMap(dict): return writing = False - if path != self.path: - writing = open(self.path, 'a') + if path != self.meta.tagmap_file: + writing = open(self.meta.tagmap_file, 'a') - self.ui.debug('reading tag renames from %s\n' % path) + self.meta.ui.debug('reading tag renames from %s\n' % path) f = open(path, 'r') for number, line in enumerate(f): @@ -485,16 +479,16 @@ class TagMap(dict): src, dst = line.split('=', 1) except (IndexError, ValueError): msg = 'ignoring line %i in tag renames %s: %s\n' - self.ui.status(msg % (number, path, line.rstrip())) + self.meta.ui.status(msg % (number, path, line.rstrip())) continue src = src.strip() dst = dst.strip() - self.ui.debug('adding tag %s to tag renames\n' % src) + self.meta.ui.debug('adding tag %s to tag renames\n' % src) if src in self and dst != self[src]: msg = 'overriding tag rename: "%s" to "%s" (%s)\n' - self.ui.status(msg % (self[src], dst, src)) + self.meta.ui.status(msg % (self[src], dst, src)) self[src] = dst f.close() diff --git a/hgsubversion/replay.py b/hgsubversion/replay.py --- a/hgsubversion/replay.py +++ b/hgsubversion/replay.py @@ -65,13 +65,13 @@ def _convert_rev(ui, meta, svn, r, tbdel editor.current.rev = r editor.setsvn(svn) - if firstrun and meta.revmap.oldest <= 0: + if firstrun and meta.firstpulled <= 0: # We know nothing about this project, so fetch everything before # trying to apply deltas. ui.debug('replay: fetching full revision\n') svn.get_revision(r.revnum, editor) else: - svn.get_replay(r.revnum, editor, meta.revmap.oldest) + svn.get_replay(r.revnum, editor, meta.firstpulled) editor.close() current = editor.current @@ -147,7 +147,10 @@ def _convert_rev(ui, meta, svn, r, tbdel def filectxfn(repo, memctx, path): current_file = files[path] - data, isexec, islink, copied = current.pop(current_file) + try: + data, isexec, islink, copied = current.pop(current_file) + except IOError: + return compathacks.filectxfn_deleted_reraise(memctx) if isexec is None or islink is None: flags = parentctx.flags(path) if isexec is None: diff --git a/hgsubversion/stupid.py b/hgsubversion/stupid.py --- a/hgsubversion/stupid.py +++ b/hgsubversion/stupid.py @@ -158,27 +158,8 @@ def filteriterhunks(meta): applycurrent = False # Passing False instead of textmode because we should never # be ignoring EOL type. - if iterhunks.func_code.co_argcount == 1: - # Since 1.9 (28762bb767dc) - fp = args[0] - gen = iterhunks(fp) - else: - ui, fp = args[:2] - if len(args) > 2: - sourcefile = args[2] - else: - sourcefile = kwargs.get('sourcefile', None) - if len(args) > 3: - textmode = args[3] - else: - textmode = kwargs.get('textmode', False) - if not iterhunks.func_defaults: - # Since 1.7 (cfedc529e4a1) - gen = iterhunks(ui, fp) - elif len(iterhunks.func_defaults) == 1: - gen = iterhunks(ui, fp, sourcefile) - else: - gen = iterhunks(ui, fp, sourcefile, textmode) + fp = args[0] + gen = iterhunks(fp) for data in gen: if data[0] == 'file': if data[1][1] in meta.filemap: @@ -226,7 +207,16 @@ def patchrepoold(ui, meta, parentctx, pa try: class svnbackend(patch.repobackend): def getfile(self, fname): - data, (islink, isexec) = super(svnbackend, self).getfile(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) @@ -361,13 +351,16 @@ def diff_branchrev(ui, svn, meta, branch if f.symlink is not None) def filectxfn(repo, memctx, path): if path in files_data and files_data[path] is None: - raise IOError(errno.ENOENT, '%s is deleted' % path) + return compathacks.filectxfn_deleted(memctx, path) if path in binary_files or path in unknown_files: pa = path if branchpath: pa = branchpath + '/' + path - data, mode = svn.get_file(pa, r.revnum) + try: + data, mode = svn.get_file(pa, r.revnum) + except IOError: + return compathacks.filectxfn_deleted_reraise(memctx) isexe = 'x' in mode islink = 'l' in mode else: @@ -587,7 +580,10 @@ def fetch_branchrev(svn, meta, branch, b svnpath = path if branchpath: svnpath = branchpath + '/' + path - data, mode = svn.get_file(svnpath, r.revnum) + try: + data, mode = svn.get_file(svnpath, r.revnum) + except IOError: + return compathacks.filectxfn_deleted_reraise(memctx) isexec = 'x' in mode islink = 'l' in mode copied = copies.get(path) @@ -736,7 +732,7 @@ def convert_rev(ui, meta, svn, r, tbdelt # path does not support this case with svn >= 1.7. We can fix # it, or we can force the existing fetch_branchrev() path. Do # the latter for now. - incremental = (meta.revmap.oldest > 0 and + incremental = (meta.firstpulled > 0 and parentctx.rev() != node.nullrev and not firstrun) diff --git a/hgsubversion/svncommands.py b/hgsubversion/svncommands.py --- a/hgsubversion/svncommands.py +++ b/hgsubversion/svncommands.py @@ -1,6 +1,5 @@ import os import posixpath -import cPickle as pickle import sys import traceback import urlparse @@ -19,6 +18,7 @@ import svnrepo import util import svnexternals import verify +import svnmeta def updatemeta(ui, repo, args, **opts): @@ -39,22 +39,6 @@ def rebuildmeta(ui, repo, args, unsafe_s return _buildmeta(ui, repo, args, partial=False, skipuuid=unsafe_skip_uuid_check) -def read_if_exists(path): - try: - fp = open(path, 'rb') - d = fp.read() - fp.close() - return d - except IOError, err: - if err.errno != errno.ENOENT: - raise - -def write_if_needed(path, content): - if read_if_exists(path) != content: - fp = open(path, 'wb') - fp.write(content) - fp.close() - def _buildmeta(ui, repo, args, partial=False, skipuuid=False): if repo is None: @@ -70,19 +54,13 @@ def _buildmeta(ui, repo, args, partial=F raise hgutil.Abort('rebuildmeta takes 1 or no arguments') url = repo.ui.expandpath(dest or repo.ui.config('paths', 'default-push') or repo.ui.config('paths', 'default') or '') - svnmetadir = os.path.join(repo.path, 'svn') - if not os.path.exists(svnmetadir): - os.makedirs(svnmetadir) - uuidpath = os.path.join(svnmetadir, 'uuid') - uuid = read_if_exists(uuidpath) - - subdirpath = os.path.join(svnmetadir, 'subdir') - subdir = read_if_exists(subdirpath) + + meta = svnmeta.SVNMeta(repo, skiperrorcheck=True) + svn = None - if subdir is None: + if meta.subdir is None: svn = svnrepo.svnremoterepo(ui, url).svn - subdir = svn.subdir - open(subdirpath, 'wb').write(subdir.strip('/')) + meta.subdir = svn.subdir youngest = 0 startrev = 0 @@ -90,16 +68,18 @@ def _buildmeta(ui, repo, args, partial=F branchinfo = {} if partial: try: - youngestpath = os.path.join(svnmetadir, 'lastpulled') + # we can't use meta.lastpulled here because we are bootstraping the + # lastpulled and want to keep the cached value on disk during a + # partial rebuild foundpartialinfo = False + youngestpath = os.path.join(meta.metapath, 'lastpulled') if os.path.exists(youngestpath): - youngest = int(util.load_string(youngestpath).strip()) - sofar = list(maps.RevMap.readmapfile(repo)) + youngest = util.load(youngestpath) + sofar = list(maps.RevMap.readmapfile(meta.revmap_file)) if sofar and len(sofar[-1].split(' ', 2)) > 1: lasthash = sofar[-1].split(' ', 2)[1] startrev = repo[lasthash].rev() + 1 - branchinfo = pickle.load(open(os.path.join(svnmetadir, - 'branch_info'))) + branchinfo = util.load(meta.branch_info_file) foundpartialinfo = True if not foundpartialinfo: ui.status('missing some metadata -- doing a full rebuild\n') @@ -111,16 +91,12 @@ def _buildmeta(ui, repo, args, partial=F except AttributeError: ui.status('no metadata available -- doing a full rebuild\n') - - lastpulled = open(os.path.join(svnmetadir, 'lastpulled'), 'wb') - revmap = open(os.path.join(svnmetadir, 'rev_map'), 'w') - revmap.write('1\n') + revmap = open(meta.revmap_file, 'w') + revmap.write('%d\n' % maps.RevMap.VERSION) revmap.writelines(sofar) last_rev = -1 - tagfile = os.path.join(svnmetadir, 'tagmap') - if not partial and os.path.exists(maps.Tags.filepath(repo)) : - os.unlink(maps.Tags.filepath(repo)) - tags = maps.Tags(repo) + if not partial and os.path.exists(meta.tagfile): + os.unlink(meta.tagfile) layout = None layoutobj = None @@ -164,7 +140,7 @@ def _buildmeta(ui, repo, args, partial=F else: closed.add(parentctx.rev()) - lastpulled.write(str(youngest) + '\n') + meta.lastpulled = youngest ui.progress('prepare', None, total=numrevs) for rev in xrange(startrev, len(repo)): @@ -197,11 +173,13 @@ def _buildmeta(ui, repo, args, partial=F # number. tagging = int(convinfo[40:].split('@')[1]) tagrev = max(tagged, tagging) - tags[tag] = node.bin(ha), tagrev + meta.tags[tag] = node.bin(ha), tagrev # check that the conversion metadata matches expectations assert convinfo.startswith('svn:') revpath, revision = convinfo[40:].split('@') + # use tmp variable for testing + subdir = meta.subdir if subdir and subdir[0] != '/': subdir = '/' + subdir if subdir and subdir[-1] == '/': @@ -212,16 +190,16 @@ 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(svnmetadir) + existing_layout = layouts.detect.layout_from_file(meta.metapath) if layout != existing_layout: - layouts.persist.layout_to_file(svnmetadir, layout) + layouts.persist.layout_to_file(meta.metapath, layout) layoutobj = layouts.layout_from_name(layout, ui) elif layout == 'single': assert (subdir or '/') == revpath, ('Possible layout detection' ' defect in replay') # write repository uuid if required - if uuid is None or validateuuid: + if meta.uuid is None or validateuuid: validateuuid = False uuid = convinfo[4:40] if not skipuuid: @@ -230,7 +208,7 @@ def _buildmeta(ui, repo, args, partial=F if uuid != svn.uuid: raise hgutil.Abort('remote svn repository identifier ' 'does not match') - write_if_needed(uuidpath, uuid) + meta.uuid = uuid # don't reflect closed branches if (ctx.extra().get('close') and not ctx.files() or @@ -241,7 +219,7 @@ def _buildmeta(ui, repo, args, partial=F # find commitpath, write to revmap commitpath = revpath[len(subdir)+1:] - tag_locations = layoutobj.taglocations(svnmetadir) + tag_locations = layoutobj.taglocations(meta.metapath) found_tag = False for location in tag_locations: if commitpath.startswith(location + '/'): @@ -304,9 +282,7 @@ def _buildmeta(ui, repo, args, partial=F ui.progress('rebuild', None, total=numrevs) # save off branch info - branchinfofile = open(os.path.join(svnmetadir, 'branch_info'), 'w') - pickle.dump(branchinfo, branchinfofile) - branchinfofile.close() + util.dump(branchinfo, meta.branch_info_file) def help_(ui, args=None, **opts): diff --git a/hgsubversion/svnexternals.py b/hgsubversion/svnexternals.py --- a/hgsubversion/svnexternals.py +++ b/hgsubversion/svnexternals.py @@ -3,26 +3,14 @@ import cStringIO import os, re, shutil, stat, subprocess from mercurial import util as hgutil from mercurial.i18n import _ +from mercurial import subrepo try: - from mercurial import subrepo - # require svnsubrepo and hg >= 1.7.1 - subrepo.svnsubrepo - hgutil.checknlink -except (ImportError, AttributeError), e: - subrepo = None - -passpegrev = True # see svnsubrepo below -try: - canonpath = hgutil.canonpath + from mercurial import scmutil + canonpath = scmutil.canonpath except (ImportError, AttributeError): - passpegrev = False - try: - from mercurial import scmutil - canonpath = scmutil.canonpath - except (ImportError, AttributeError): - from mercurial import pathutil - canonpath = pathutil.canonpath + from mercurial import pathutil + canonpath = pathutil.canonpath import util @@ -398,58 +386,53 @@ def parse(ui, ctx): raise hgutil.Abort(_('unknown externals modes: %s') % mode) return external -if subrepo: - class svnsubrepo(subrepo.svnsubrepo): - def __init__(self, ctx, path, state): - state = (state[0].split(':', 1)[1], state[1]) - super(svnsubrepo, self).__init__(ctx, path, state) - - def get(self, state, *args, **kwargs): - # Resolve source first - line = state[0].split(':', 1)[1] - source, pegrev = parsedefinition(line)[2:4] - try: - # Getting the root SVN repository URL is expensive. - # Assume the externals is absolute. - source = resolvesource(self._ui, None, source) - except RelativeSourceError: - svnurl = self._ctx._repo.ui.expandpath('default') - svnroot = getsvninfo(util.normalize_url(svnurl))[1] - source = resolvesource(self._ui, svnroot, source) - # hg < 1.9 svnsubrepo calls "svn checkout" with --rev - # only, so peg revisions are correctly used. 1.9 and - # higher, append the rev as a peg revision to the source - # URL, so we cannot add our own. We assume that "-r10 - # url@2" will be similar to "url@10" most of the time. - if pegrev is not None and passpegrev: - source = source + '@' + pegrev - state = (source, state[1]) - # hg-1.7.4-c19b9282d3a7 introduced the overwrite argument - return super(svnsubrepo, self).get(state, *args, **kwargs) - - def dirty(self, ignoreupdate=False): - # You cannot compare anything with HEAD. Just accept it - # can be anything. - if hasattr(self, '_wcrevs'): - wcrevs = self._wcrevs() - else: - wcrev = self._wcrev() - wcrevs = (wcrev, wcrev) - if (('HEAD' in wcrevs or self._state[1] == 'HEAD' or - self._state[1] in wcrevs or ignoreupdate) - and not self._wcchanged()[0]): - return False - return True - - def commit(self, text, user, date): - rev = super(svnsubrepo, self).commit(text, user, date) - # Keep unversioned externals unversioned - if self._state[1] == 'HEAD': - rev = 'HEAD' - return rev - - def basestate(self): - # basestate() was introduced by bcb973abcc0b in 2.2 - if self._state[1] == 'HEAD': - return 'HEAD' - return super(svnsubrepo, self).basestate() +class svnsubrepo(subrepo.svnsubrepo): + def __init__(self, ctx, path, state): + state = (state[0].split(':', 1)[1], state[1]) + super(svnsubrepo, self).__init__(ctx, path, state) + + def get(self, state, *args, **kwargs): + # Resolve source first + line = state[0].split(':', 1)[1] + source, pegrev = parsedefinition(line)[2:4] + try: + # Getting the root SVN repository URL is expensive. + # Assume the externals is absolute. + source = resolvesource(self._ui, None, source) + except RelativeSourceError: + svnurl = self._ctx._repo.ui.expandpath('default') + svnroot = getsvninfo(util.normalize_url(svnurl))[1] + source = resolvesource(self._ui, svnroot, source) + # hg 1.9 and higher, append the rev as a peg revision to + # the source URL, so we cannot add our own. We assume + # that "-r10 url@2" will be similar to "url@10" most of + # the time. + state = (source, state[1]) + return super(svnsubrepo, self).get(state, *args, **kwargs) + + def dirty(self, ignoreupdate=False): + # You cannot compare anything with HEAD. Just accept it + # can be anything. + if hasattr(self, '_wcrevs'): + wcrevs = self._wcrevs() + else: + wcrev = self._wcrev() + wcrevs = (wcrev, wcrev) + if (('HEAD' in wcrevs or self._state[1] == 'HEAD' or + self._state[1] in wcrevs or ignoreupdate) + and not self._wcchanged()[0]): + return False + return True + + def commit(self, text, user, date): + rev = super(svnsubrepo, self).commit(text, user, date) + # Keep unversioned externals unversioned + if self._state[1] == 'HEAD': + rev = 'HEAD' + return rev + + def basestate(self): + # basestate() was introduced by bcb973abcc0b in 2.2 + if self._state[1] == 'HEAD': + return 'HEAD' + return super(svnsubrepo, self).basestate() diff --git a/hgsubversion/svnmeta.py b/hgsubversion/svnmeta.py --- a/hgsubversion/svnmeta.py +++ b/hgsubversion/svnmeta.py @@ -1,4 +1,3 @@ -import cPickle as pickle import posixpath import os import tempfile @@ -17,7 +16,7 @@ import editor class SVNMeta(object): - def __init__(self, repo, uuid=None, subdir=None): + def __init__(self, repo, uuid=None, subdir=None, skiperrorcheck=False): """path is the path to the target hg repo. subdir is the subdirectory of the edits *on the svn server*. @@ -26,52 +25,115 @@ class SVNMeta(object): self.ui = repo.ui self.repo = repo self.path = os.path.normpath(repo.join('..')) + self._skiperror = skiperrorcheck - if not os.path.isdir(self.meta_data_dir): - os.makedirs(self.meta_data_dir) + if not os.path.isdir(self.metapath): + os.makedirs(self.metapath) self.uuid = uuid self.subdir = subdir - self.revmap = maps.RevMap(repo) - - author_host = self.ui.config('hgsubversion', 'defaulthost', uuid) - authors = util.configpath(self.ui, 'authormap') - self.usebranchnames = self.ui.configbool('hgsubversion', - 'usebranchnames', True) - branchmap = util.configpath(self.ui, 'branchmap') - tagmap = util.configpath(self.ui, 'tagmap') - filemap = util.configpath(self.ui, 'filemap') - - self.branches = {} - if os.path.exists(self.branch_info_file): - f = open(self.branch_info_file) - self.branches = pickle.load(f) - f.close() + self._revmap = None + self.firstpulled = 0 + + 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) + + self.branches = util.load(self.branch_info_file) or {} self.prevbranches = dict(self.branches) - self.tags = maps.Tags(repo) - self._layout = layouts.detect.layout_from_file(self.meta_data_dir, + self._tags = None + self._layout = layouts.detect.layout_from_file(self.metapath, ui=self.repo.ui) self._layoutobj = None - self.authors = maps.AuthorMap(self.ui, self.authors_file, - defaulthost=author_host) - if authors: self.authors.load(authors) + self._authors = None - self.branchmap = maps.BranchMap(self.ui, self.branchmapfile) - if branchmap: - self.branchmap.load(branchmap) + self._branchmap = None - self.tagmap = maps.TagMap(self.ui, self.tagmapfile) - if tagmap: - self.tagmap.load(tagmap) + self._tagmap = None - self.filemap = maps.FileMap(self.ui, self.filemap_file) - if filemap: - self.filemap.load(filemap) + self._filemap = None self.lastdate = '1970-01-01 00:00:00 -0000' self.addedtags = {} self.deletedtags = {} + def _get_cachedconfig(self, name, filename, configname, default): + """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. + name: property name, e.g. 'lastpulled' + filename: name of file in .hg/svn + configname: commandline option name + default: default value + """ + varname = '_' + name + if getattr(self, varname) is None: + # construct the file path from metapath (e.g. .hg/svn) plus the + # filename + f = os.path.join(self.metapath, filename) + + # load the config property (i.e. command-line or .hgrc) + c = None + if configname: + # a little awkward but we need to convert the option from a + # string to whatever type the default value is, so we use the + # type of `default` to determine with ui.config method to call + c = None + if isinstance(default, bool): + c = self.ui.configbool('hgsubversion', configname, default) + elif isinstance(default, int): + c = self.ui.configint('hgsubversion', configname, default) + elif isinstance(default, list): + c = self.ui.configlist('hgsubversion', configname, default) + else: + c = self.ui.config('hgsubversion', configname, default) + + # load the value from disk + val = util.load(f, default=default) + + # prefer the non-default, and the one sent from command-line + if c is not None and c != val and c != default: + val = c + + # set the value as the one from disk (or default if not found) + setattr(self, varname, val) + + # save the value to disk by using the setter property + setattr(self, name, val) + + return getattr(self, varname) + + def _set_cachedconfig(self, value, name, filename): + varname = '_' + name + f = os.path.join(self.metapath, filename) + setattr(self, varname, value) + util.dump(value, f) + + def _gen_cachedconfig(self, name, default=None, filename=None, + configname=None): + """Generate an attribute for reading (and caching) config data. + + This method constructs a new attribute on self with the given name. + The actual value from the config file will be read lazily, and then + cached once that read has occurred. No cache invalidation will happen, + so within a session these values shouldn't be required to mutate. + """ + setattr(SVNMeta, '_' + name, None) + if filename is None: + 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)) + setattr(SVNMeta, name, prop) + @property def layout(self): # this method can't determine the layout, but it needs to be @@ -79,7 +141,7 @@ class SVNMeta(object): # 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.meta_data_dir, self._layout) + layouts.persist.layout_to_file(self.metapath, self._layout) return self._layout @property @@ -101,24 +163,23 @@ class SVNMeta(object): if subdir: subdir = '/'.join(p for p in subdir.split('/') if p) - subdirfile = os.path.join(self.meta_data_dir, 'subdir') + self.__subdir = None + subdirfile = os.path.join(self.metapath, 'subdir') if os.path.isfile(subdirfile): - stored_subdir = open(subdirfile).read() + stored_subdir = util.load(subdirfile) assert stored_subdir is not None if subdir is None: self.__subdir = stored_subdir - elif subdir != stored_subdir: + elif subdir and subdir != stored_subdir: raise hgutil.Abort('unable to work on a different path in the ' 'repository') else: self.__subdir = subdir elif subdir is not None: - f = open(subdirfile, 'w') - f.write(subdir) - f.close() + util.dump(subdir, subdirfile) self.__subdir = subdir - else: + elif not self._skiperror: raise hgutil.Abort("hgsubversion metadata unavailable; " "please run 'hg svn rebuildmeta'") @@ -130,19 +191,18 @@ class SVNMeta(object): return self.__uuid def _set_uuid(self, uuid): - uuidfile = os.path.join(self.meta_data_dir, 'uuid') + self.__uuid = None + uuidfile = os.path.join(self.metapath, 'uuid') if os.path.isfile(uuidfile): - stored_uuid = open(uuidfile).read() + stored_uuid = util.load(uuidfile) assert stored_uuid if uuid and uuid != stored_uuid: raise hgutil.Abort('unable to operate on unrelated repository') self.__uuid = uuid or stored_uuid elif uuid: - f = open(uuidfile, 'w') - f.write(uuid) - f.close() + util.dump(uuid, uuidfile) self.__uuid = uuid - else: + elif not self._skiperror: raise hgutil.Abort("hgsubversion metadata unavailable; " "please run 'hg svn rebuildmeta'") @@ -150,29 +210,74 @@ class SVNMeta(object): 'Error-checked UUID of source Subversion repository.') @property - def meta_data_dir(self): + def metapath(self): return os.path.join(self.path, '.hg', 'svn') @property def branch_info_file(self): - return os.path.join(self.meta_data_dir, 'branch_info') + return os.path.join(self.metapath, 'branch_info') @property def authors_file(self): - return os.path.join(self.meta_data_dir, 'authors') + return os.path.join(self.metapath, 'authors') + + @property + def authors(self): + if self._authors is None: + self._authors = maps.AuthorMap(self) + return self._authors @property def filemap_file(self): - return os.path.join(self.meta_data_dir, 'filemap') + return os.path.join(self.metapath, 'filemap') + + @property + def filemap(self): + if self._filemap is None: + self._filemap = maps.FileMap(self) + return self._filemap + + @property + def branchmap_file(self): + return os.path.join(self.metapath, 'branchmap') + + @property + def branchmap(self): + if self._branchmap is None: + self._branchmap = maps.BranchMap(self) + return self._branchmap @property - def branchmapfile(self): - return os.path.join(self.meta_data_dir, 'branchmap') + def tagfile(self): + # called tagmap for backwards compatibility + return os.path.join(self.metapath, 'tagmap') @property - def tagmapfile(self): + def tags(self): + if self._tags is None: + self._tags = maps.Tags(self) + return self._tags + + @property + def tagmap_file(self): # called tag-renames for backwards compatibility - return os.path.join(self.meta_data_dir, 'tag-renames') + return os.path.join(self.metapath, 'tag-renames') + + @property + def tagmap(self): + if self._tagmap is None: + self._tagmap = maps.TagMap(self) + return self._tagmap + + @property + def revmap_file(self): + return os.path.join(self.metapath, 'rev_map') + + @property + def revmap(self): + if self._revmap is None: + self._revmap = maps.RevMap(self) + return self._revmap def fixdate(self, date): if date is not None: @@ -187,7 +292,7 @@ class SVNMeta(object): '''Save the Subversion metadata. This should really be called after every revision is created. ''' - util.pickle_atomic(self.branches, self.branch_info_file) + util.dump(self.branches, self.branch_info_file) def localname(self, path): """Compute the local name for a branch located at path. @@ -247,7 +352,7 @@ class SVNMeta(object): @property def taglocations(self): - return self.layoutobj.taglocations(self.meta_data_dir) + return self.layoutobj.taglocations(self.metapath) def get_path_tag(self, path): """If path could represent the path to a tag, returns the @@ -395,7 +500,7 @@ class SVNMeta(object): return node.hex(self.revmap[tagged]) tag = fromtag # Reference an existing tag - limitedtags = maps.Tags(self.repo, endrev=number - 1) + limitedtags = maps.Tags(self, endrev=number - 1) if tag in limitedtags: return limitedtags[tag] r, br = self.get_parent_svn_branch_and_rev(number - 1, branch, exact) diff --git a/hgsubversion/svnrepo.py b/hgsubversion/svnrepo.py --- a/hgsubversion/svnrepo.py +++ b/hgsubversion/svnrepo.py @@ -17,6 +17,7 @@ subclass: pull() is called on the instan import errno from mercurial import error +from mercurial import localrepo from mercurial import util as hgutil try: @@ -71,10 +72,6 @@ def generate_repo_class(ui, repo): """ original = getattr(repo, fn.__name__, None) - # remove when dropping support for hg < 1.6. - if original is None and fn.__name__ == 'findoutgoing': - return - def wrapper(self, *args, **opts): capable = getattr(args[0], 'capable', lambda x: False) if capable('subversion'): @@ -99,21 +96,25 @@ def generate_repo_class(ui, repo): self.pushkey('phases', self[hash].hex(), str(phases.draft), str(phases.public)) return hash - # TODO use newbranch to allow branch creation in Subversion? - @remotesvn - def push(self, remote, force=False, revs=None, newbranch=None): - return wrappers.push(self, remote, force, revs) + if hgutil.safehasattr(localrepo.localrepository, 'push'): + # Mercurial < 3.2 + # TODO use newbranch to allow branch creation in Subversion? + @remotesvn + def push(self, remote, force=False, revs=None, newbranch=None): + return wrappers.push(self, remote, force, revs) - @remotesvn - def pull(self, remote, heads=[], force=False): - return wrappers.pull(self, remote, heads, force) + if hgutil.safehasattr(localrepo.localrepository, 'pull'): + # Mercurial < 3.2 + @remotesvn + def pull(self, remote, heads=[], force=False): + return wrappers.pull(self, remote, heads, force) @remotesvn def findoutgoing(self, remote, base=None, heads=None, force=False): return wrappers.findoutgoing(repo, remote, heads, force) - def svnmeta(self, uuid=None, subdir=None): - return svnmeta.SVNMeta(self, uuid, subdir) + def svnmeta(self, uuid=None, subdir=None, skiperrorcheck=False): + return svnmeta.SVNMeta(self, uuid, subdir, skiperrorcheck) repo.__class__ = svnlocalrepo diff --git a/hgsubversion/util.py b/hgsubversion/util.py --- a/hgsubversion/util.py +++ b/hgsubversion/util.py @@ -1,8 +1,10 @@ -import cPickle as pickle +import compathacks import errno import re import os import urllib +import json +import gc from mercurial import cmdutil from mercurial import error @@ -54,6 +56,18 @@ def filterdiff(diff, oldrev, newrev): diff = header_re.sub(r'Index: \1' + '\n' + ('=' * 67), diff) return diff +def gcdisable(orig): + """decorator to disable GC for a function or method""" + def wrapper(*args, **kwargs): + enabled = gc.isenabled() + if enabled: + gc.disable() + try: + orig(*args, **kwargs) + finally: + if enabled: + gc.enable() + return wrapper def parentrev(ui, repo, meta, hashes): """Find the svn parent revision of the repo's dirstate. @@ -120,57 +134,74 @@ def normalize_url(url): url = '%s#%s' % (url, checkout) return url +def _scrub(data): + if not data and not isinstance(data, list): + return '' + return data + +def _descrub(data): + if isinstance(data, list): + return tuple(data) + if data == '': + return None + return data + +def _convert(input, visitor): + if isinstance(input, dict): + scrubbed = {} + d = dict([(_convert(key, visitor), _convert(value, visitor)) + for key, value in input.iteritems()]) + for key, val in d.iteritems(): + scrubbed[visitor(key)] = visitor(val) + return scrubbed + elif isinstance(input, list): + return [_convert(element, visitor) for element in input] + elif isinstance(input, unicode): + return input.encode('utf-8') + return input + +def dump(data, file_path): + """Serialize some data to a path atomically. + + This is present because I kept corrupting my revmap by managing to hit ^C + during the serialization of that file. + """ + f = hgutil.atomictempfile(file_path, 'w+b', 0644) + json.dump(_convert(data, _scrub), f) + f.close() -def load_string(file_path, default=None, limit=1024): +def load(file_path, default=None, resave=True): + """Deserialize some data from a path. + """ + data = default if not os.path.exists(file_path): - return default + return data + + f = open(file_path) try: - f = open(file_path, 'r') - ret = f.read(limit) + data = _convert(json.load(f), _descrub) f.close() - except: - return default - if ret == '': - return default - return ret - - -def save_string(file_path, string): - if string is None: - string = "" - f = open(file_path, 'wb') - f.write(str(string)) - f.close() - -def pickle_atomic(data, file_path): - """pickle some data to a path atomically. + except ValueError: + try: + # Ok, JSON couldn't be loaded, so we'll try the old way of using pickle + data = compathacks.pickle_load(f) + except: + # well, pickle didn't work either, so we reset the file pointer and + # read the string + f.seek(0) + data = f.read() - This is present because I kept corrupting my revmap by managing to hit ^C - during the pickle of that file. - """ - f = hgutil.atomictempfile(file_path, 'w+b', 0644) - pickle.dump(data, f) - # Older versions of hg have .rename() instead of .close on - # atomictempfile. - if getattr(hgutil.atomictempfile, 'rename', False): - f.rename() - else: + # convert the file to json immediately f.close() + if resave: + dump(data, file_path) + return data def parseurl(url, heads=[]): - parsed = hg.parseurl(url, heads) - if len(parsed) == 3: - # old hg, remove when we can be 1.5-only - svn_url, heads, checkout = parsed - else: - svn_url, heads = parsed - if isinstance(heads, tuple) and len(heads) == 2: - # hg 1.6 or later - _junk, heads = heads - if heads: - checkout = heads[0] - else: - checkout = None + checkout = None + svn_url, (_junk, heads) = hg.parseurl(url, heads) + if heads: + checkout = heads[0] return svn_url, heads, checkout @@ -325,9 +356,11 @@ def revset_fromsvn(repo, subset, x): rev = repo.changelog.rev bin = node.bin + meta = repo.svnmeta(skiperrorcheck=True) try: svnrevs = set(rev(bin(l.split(' ', 2)[1])) - for l in maps.RevMap.readmapfile(repo, missingok=False)) + for l in maps.RevMap.readmapfile(meta.revmap_file, + missingok=False)) return filter(svnrevs.__contains__, subset) except IOError, err: if err.errno != errno.ENOENT: @@ -350,8 +383,9 @@ def revset_svnrev(repo, subset, x): rev = rev + ' ' revs = [] + meta = repo.svnmeta(skiperrorcheck=True) try: - for l in maps.RevMap.readmapfile(repo, missingok=False): + for l in maps.RevMap.readmapfile(meta.revmap_file, missingok=False): if l.startswith(rev): n = l.split(' ', 2)[1] r = repo[node.bin(n)].rev() diff --git a/hgsubversion/wrappers.py b/hgsubversion/wrappers.py --- a/hgsubversion/wrappers.py +++ b/hgsubversion/wrappers.py @@ -2,6 +2,12 @@ from hgext import rebase as hgrebase from mercurial import cmdutil from mercurial import discovery +try: + from mercurial import exchange + exchange.push # existed in first iteration of this file +except ImportError: + # We only *use* the exchange module in hg 3.2+, so this is safe + pass from mercurial import patch from mercurial import hg from mercurial import util as hgutil @@ -97,7 +103,7 @@ def incoming(orig, ui, repo, origsource= meta = repo.svnmeta(svn.uuid, svn.subdir) ui.status('incoming changes from %s\n' % other.svnurl) - svnrevisions = list(svn.revisions(start=meta.revmap.youngest)) + svnrevisions = list(svn.revisions(start=meta.lastpulled)) if opts.get('newest_first'): svnrevisions.reverse() # Returns 0 if there are incoming changes, 1 otherwise. @@ -181,7 +187,8 @@ def push(repo, dest, force, revs): checkpush = getattr(repo, 'checkpush', None) if checkpush: try: - # The checkpush function changed as of e10000369b47 in mercurial + # The checkpush function changed as of e10000369b47 (first + # in 3.0) in mercurial from mercurial.exchange import pushoperation pushop = pushoperation(repo, dest, force, revs, False) checkpush(pushop) @@ -209,6 +216,7 @@ def push(repo, dest, force, revs): ui.status('Cowardly refusing to push branch merge\n') return 0 # results in nonzero exit status, see hg's commands.py workingrev = repo.parents()[0] + workingbranch = workingrev.branch() ui.status('searching for changes\n') hashes = meta.revmap.hashes() outgoing = util.outgoing_revisions(repo, hashes, workingrev.node()) @@ -272,9 +280,16 @@ def push(repo, dest, force, revs): "in svn.\n" % current_ctx) return + # This hook is here purely for testing. It allows us to + # onsistently trigger hit the race condition between + # pushing and pulling here. In particular, we use it to + # trigger another revision landing between the time we + # push a revision and pull it back. + repo.hook('debug-hgsubversion-between-push-and-pull-for-tests') + # 5. Pull the latest changesets from subversion, which will # include the one we just committed (and possibly others). - r = repo.pull(dest, force=force) + r = pull(repo, dest, force=force) assert not r or r == 0 meta = repo.svnmeta(svn.uuid, svn.subdir) hashes = meta.revmap.hashes() @@ -324,7 +339,7 @@ def push(repo, dest, force, revs): util.swap_out_encoding(old_encoding) try: - hg.update(repo, repo['tip'].node()) + hg.update(repo, repo.branchtip(workingbranch)) finally: util.swap_out_encoding() @@ -353,6 +368,16 @@ def push(repo, dest, force, revs): util.swap_out_encoding(old_encoding) return 1 # so we get a sane exit status, see hg's commands.push +def exchangepush(orig, repo, remote, force=False, revs=None, newbranch=False, + bookmarks=()): + capable = getattr(remote, 'capable', lambda x: False) + if capable('subversion'): + pushop = exchange.pushoperation(repo, remote, force, revs, newbranch, + bookmarks=bookmarks) + pushop.cgresult = push(repo, remote, force, revs) + return pushop + else: + return orig(repo, remote, force, revs, newbranch, bookmarks=bookmarks) def pull(repo, source, heads=[], force=False): """pull new revisions from Subversion""" @@ -390,7 +415,7 @@ def pull(repo, source, heads=[], force=F meta.branchmap['default'] = branch ui = repo.ui - start = meta.revmap.youngest + start = meta.lastpulled origrevcount = len(meta.revmap) if start <= 0: @@ -484,7 +509,7 @@ def pull(repo, source, heads=[], force=F util.swap_out_encoding(old_encoding) if lastpulled is not None: - meta.revmap.youngest = lastpulled + meta.lastpulled = lastpulled revisions = len(meta.revmap) - oldrevisions if revisions == 0: @@ -493,6 +518,19 @@ def pull(repo, source, heads=[], force=F else: ui.status("pulled %d revisions\n" % revisions) +def exchangepull(orig, repo, remote, heads=None, force=False, bookmarks=()): + capable = getattr(remote, 'capable', lambda x: False) + if capable('subversion'): + pullop = exchange.pulloperation(repo, remote, heads, force, + bookmarks=bookmarks) + try: + pullop.cgresult = pull(repo, remote, heads, force) + return pullop + finally: + pullop.releasetransaction() + else: + return orig(repo, remote, heads, force, bookmarks=bookmarks) + def rebase(orig, ui, repo, **opts): """rebase current unpushed revisions onto the Subversion head @@ -573,11 +611,7 @@ def clone(orig, ui, source, dest=None, * data = {} def hgclonewrapper(orig, ui, *args, **opts): - if getattr(hg, 'peer', None): - # Since 1.9 (d976542986d2) - origsource = args[1] - else: - origsource = args[0] + origsource = args[1] if isinstance(origsource, str): source, branch, checkout = util.parseurl(ui.expandpath(origsource), @@ -612,14 +646,11 @@ def clone(orig, ui, source, dest=None, * dstrepo = data.get('dstrepo') srcrepo = data.get('srcrepo') + dst = dstrepo.local() if dstrepo.local() and srcrepo.capable('subversion'): dst = dstrepo.local() - if isinstance(dst, bool): - # Apparently <= hg@1.9 - fd = dstrepo.opener("hgrc", "a", text=True) - else: - fd = dst.opener("hgrc", "a", text=True) + fd = dst.opener("hgrc", "a", text=True) preservesections = set(s for s, v in optionmap.itervalues()) preservesections |= extrasections for section in preservesections: diff --git a/tests/comprehensive/test_custom_layout.py b/tests/comprehensive/test_custom_layout.py --- a/tests/comprehensive/test_custom_layout.py +++ b/tests/comprehensive/test_custom_layout.py @@ -1,5 +1,4 @@ import os -import pickle import sys import unittest diff --git a/tests/comprehensive/test_rebuildmeta.py b/tests/comprehensive/test_rebuildmeta.py --- a/tests/comprehensive/test_rebuildmeta.py +++ b/tests/comprehensive/test_rebuildmeta.py @@ -1,5 +1,4 @@ import os -import pickle import unittest import sys @@ -14,11 +13,14 @@ except ImportError: from mercurial import context from mercurial import extensions from mercurial import hg +from mercurial import localrepo from mercurial import ui +from mercurial import util as hgutil from hgsubversion import compathacks from hgsubversion import svncommands from hgsubversion import svnmeta +from hgsubversion import util # These test repositories have harmless skew in rebuildmeta for the # last-pulled-rev because the last rev in svn causes absolutely no @@ -85,7 +87,12 @@ def _do_case(self, name, layout): # remove the wrapper context.changectx.children = origchildren - dest.pull(src) + if hgutil.safehasattr(localrepo.localrepository, 'pull'): + dest.pull(src) + else: + # Mercurial >= 3.2 + from mercurial import exchange + exchange.pull(dest, src) # insert a wrapper that prevents calling changectx.children() extensions.wrapfunction(context.changectx, 'children', failfn) @@ -110,23 +117,29 @@ def _run_assertions(self, name, single, for tf in ('lastpulled', 'rev_map', 'uuid', 'tagmap', 'layout', 'subdir',): stf = os.path.join(src.path, 'svn', tf) - self.assertTrue(os.path.isfile(stf), '%r is missing!' % stf) + # the generation of tagmap is lazy so it doesn't strictly need to exist + # if it's not being used + if not stf.endswith('tagmap'): + self.assertTrue(os.path.isfile(stf), '%r is missing!' % stf) dtf = os.path.join(dest.path, 'svn', tf) - self.assertTrue(os.path.isfile(dtf), '%r is missing!' % tf) - old, new = open(stf).read(), open(dtf).read() + old, new = None, None + if not dtf.endswith('tagmap'): + self.assertTrue(os.path.isfile(dtf), '%r is missing!' % tf) + if os.path.isfile(stf) and os.path.isfile(dtf): + old, new = util.load(stf, resave=False), util.load(dtf, resave=False) if tf == 'lastpulled' and (name, self.stupid, single) in expect_youngest_skew: self.assertNotEqual(old, new, 'rebuildmeta unexpected match on youngest rev!') continue - self.assertMultiLineEqual(old, new, tf + ' differs') + self.assertEqual(old, new, tf + ' differs') try: self.assertEqual(src.branchmap(), dest.branchmap()) except AttributeError: # hg 2.8 and earlier self.assertEqual(src.branchtags(), dest.branchtags()) - srcbi = pickle.load(open(os.path.join(src.path, 'svn', 'branch_info'))) - destbi = pickle.load(open(os.path.join(dest.path, 'svn', 'branch_info'))) + srcbi = util.load(os.path.join(src.path, 'svn', 'branch_info')) + destbi = util.load(os.path.join(dest.path, 'svn', 'branch_info')) self.assertEqual(sorted(srcbi.keys()), sorted(destbi.keys())) revkeys = svnmeta.SVNMeta(dest).revmap.keys() for branch in destbi: diff --git a/tests/comprehensive/test_stupid_pull.py b/tests/comprehensive/test_stupid_pull.py --- a/tests/comprehensive/test_stupid_pull.py +++ b/tests/comprehensive/test_stupid_pull.py @@ -1,5 +1,4 @@ import os -import pickle import sys import unittest diff --git a/tests/comprehensive/test_verify_and_startrev.py b/tests/comprehensive/test_verify_and_startrev.py --- a/tests/comprehensive/test_verify_and_startrev.py +++ b/tests/comprehensive/test_verify_and_startrev.py @@ -1,5 +1,4 @@ import os -import pickle import sys import unittest diff --git a/tests/fixtures/copyafterclose.sh b/tests/fixtures/copyafterclose.sh new file mode 100755 --- /dev/null +++ b/tests/fixtures/copyafterclose.sh @@ -0,0 +1,60 @@ +#!/bin/sh + +rm -rf temp +mkdir temp +cd temp +svnadmin create repo +repo=file://`pwd`/repo +svn co $repo wc +cd wc +mkdir branches trunk tags +svn add * +svn ci -m 'btt' + +cd trunk +echo trunk1 > file +mkdir dir +echo trunk1 > dir/file +svn add file dir +svn ci -m 'Add file and dir.' +cd .. +svn up + +svn cp trunk branches/test +svn ci -m 'Branch.' +svn up + +cd branches/test/ +echo branch1 > file +echo branch1 > dir/file +svn ci -m 'edit on branch.' +cd ../../ +svn up + +cd trunk +echo trunk2 > file +echo trunk2 > dir/file +svn ci -m 'edit on trunk' +cd .. +svn up + +svn rm trunk +svn ci -m 'Close trunk.' +svn up + +cd branches/test +svn rm file +svn cp $repo/trunk/file@5 file +svn rm dir +svn cp $repo/trunk/dir@5 dir +svn ci -m 'copy from trunk before close' +cd ../.. +svn up + +cd ../.. +svnadmin dump temp/repo > copyafterclose.svndump +echo +echo 'Complete.' +echo 'You probably want to clean up temp now.' +echo 'Dump in copyafterclose.svndump' +exit 0 diff --git a/tests/fixtures/copyafterclose.svndump b/tests/fixtures/copyafterclose.svndump new file mode 100644 --- /dev/null +++ b/tests/fixtures/copyafterclose.svndump @@ -0,0 +1,285 @@ +SVN-fs-dump-format-version: 2 + +UUID: 288797d9-b527-4683-aa49-2eb9e084ffad + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2014-04-03T22:42:41.334418Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 108 +Content-length: 108 + +K 10 +svn:author +V 10 +dschleimer +K 8 +svn:date +V 27 +2014-04-03T22:42:41.393547Z +K 7 +svn:log +V 3 +btt +PROPS-END + +Node-path: branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: tags +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Revision-number: 2 +Prop-content-length: 123 +Content-length: 123 + +K 10 +svn:author +V 10 +dschleimer +K 8 +svn:date +V 27 +2014-04-03T22:42:41.442353Z +K 7 +svn:log +V 17 +Add file and dir. +PROPS-END + +Node-path: trunk/dir +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk/dir/file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 7 +Text-content-md5: 5f2a436c7d4aa15dfbdca7b303fcae35 +Text-content-sha1: 391157987cb6fefff86fd89353356611ea621906 +Content-length: 17 + +PROPS-END +trunk1 + + +Node-path: trunk/file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 7 +Text-content-md5: 5f2a436c7d4aa15dfbdca7b303fcae35 +Text-content-sha1: 391157987cb6fefff86fd89353356611ea621906 +Content-length: 17 + +PROPS-END +trunk1 + + +Revision-number: 3 +Prop-content-length: 112 +Content-length: 112 + +K 10 +svn:author +V 10 +dschleimer +K 8 +svn:date +V 27 +2014-04-03T22:42:41.504478Z +K 7 +svn:log +V 7 +Branch. +PROPS-END + +Node-path: branches/test +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk + + +Revision-number: 4 +Prop-content-length: 121 +Content-length: 121 + +K 10 +svn:author +V 10 +dschleimer +K 8 +svn:date +V 27 +2014-04-03T22:42:41.549176Z +K 7 +svn:log +V 15 +edit on branch. +PROPS-END + +Node-path: branches/test/dir/file +Node-kind: file +Node-action: change +Text-content-length: 8 +Text-content-md5: ed787ace107676c1dfcced2ae527df92 +Text-content-sha1: b8486c4feca589a4237a1ee428322d7109ede12e +Content-length: 8 + +branch1 + + +Node-path: branches/test/file +Node-kind: file +Node-action: change +Text-content-length: 8 +Text-content-md5: ed787ace107676c1dfcced2ae527df92 +Text-content-sha1: b8486c4feca589a4237a1ee428322d7109ede12e +Content-length: 8 + +branch1 + + +Revision-number: 5 +Prop-content-length: 119 +Content-length: 119 + +K 10 +svn:author +V 10 +dschleimer +K 8 +svn:date +V 27 +2014-04-03T22:42:41.600193Z +K 7 +svn:log +V 13 +edit on trunk +PROPS-END + +Node-path: trunk/dir/file +Node-kind: file +Node-action: change +Text-content-length: 7 +Text-content-md5: 28d0a7e7ef2864416b7a9398623e4d09 +Text-content-sha1: 91454e2d3487f712490f17481157e389c11a6fe0 +Content-length: 7 + +trunk2 + + +Node-path: trunk/file +Node-kind: file +Node-action: change +Text-content-length: 7 +Text-content-md5: 28d0a7e7ef2864416b7a9398623e4d09 +Text-content-sha1: 91454e2d3487f712490f17481157e389c11a6fe0 +Content-length: 7 + +trunk2 + + +Revision-number: 6 +Prop-content-length: 118 +Content-length: 118 + +K 10 +svn:author +V 10 +dschleimer +K 8 +svn:date +V 27 +2014-04-03T22:42:41.650888Z +K 7 +svn:log +V 12 +Close trunk. +PROPS-END + +Node-path: trunk +Node-action: delete + + +Revision-number: 7 +Prop-content-length: 134 +Content-length: 134 + +K 10 +svn:author +V 10 +dschleimer +K 8 +svn:date +V 27 +2014-04-03T22:42:41.757761Z +K 7 +svn:log +V 28 +copy from trunk before close +PROPS-END + +Node-path: branches/test/dir +Node-kind: dir +Node-action: delete + +Node-path: branches/test/dir +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 5 +Node-copyfrom-path: trunk/dir + + + + +Node-path: branches/test/file +Node-kind: file +Node-action: delete + +Node-path: branches/test/file +Node-kind: file +Node-action: add +Node-copyfrom-rev: 5 +Node-copyfrom-path: trunk/file +Text-copy-source-md5: 28d0a7e7ef2864416b7a9398623e4d09 +Text-copy-source-sha1: 91454e2d3487f712490f17481157e389c11a6fe0 + + + + diff --git a/tests/fixtures/renames.sh b/tests/fixtures/renames.sh --- a/tests/fixtures/renames.sh +++ b/tests/fixtures/renames.sh @@ -3,6 +3,10 @@ # Generate renames.svndump # +set -e + +rm -rf temp + mkdir temp cd temp @@ -21,82 +25,112 @@ cd project/trunk # Entries for regular tests echo a > a echo b > b +ln -s a linka +ln -s b linkb mkdir -p da/db echo c > da/daf +ln -s daf da/dalink echo d > da/db/dbf +ln -s ../daf da/db/dblink # Entries to test delete + copy echo deleted > deletedfile +ln -s b deletedlink mkdir deleteddir echo deleteddir > deleteddir/f +ln -s f deleteddir/link # Entries to test copy before change echo changed > changed +ln -s changed changedlink mkdir changeddir echo changed2 > changeddir/f +ln -s f changeddir/link # Entries unchanged in the rest of history echo unchanged > unchanged +ln -s unchanged unchangedlink mkdir unchangeddir echo unchanged2 > unchangeddir/f +ln -s f unchangeddir/link # One of the files will be changed afterwards, to test # group copies detection mkdir groupdir echo a > groupdir/a echo b > groupdir/b -svn add a b da deletedfile deleteddir changed changeddir unchanged unchangeddir groupdir -svn ci -m "add a and b" +ln -s a groupdir/linka +ln -s b groupdir/linkb +svn add a b linka linkb da deleted* changed* unchanged* groupdir +svn ci -m "add everything" # Remove files to be copied later svn rm deletedfile svn rm deleteddir +svn rm deletedlink # Update files to be copied before this change echo changed >> changed echo changed2 >> changeddir/f +ln -sfn changeddir/f changedlink +ln -sfn ../changed changeddir/link # Update one of the groupdir files echo a >> groupdir/a +ln -sfn ../a groupdir/linka svn ci -m "delete files and dirs" cd ../branches svn cp ../trunk branch1 svn ci -m "create branch1" cd branch1 echo c > c -svn add c -svn ci -m "add c" +ln -s c linkc +svn add c linkc +svn ci -m "add c and linkc" cd ../../trunk # Regular copy and rename svn cp a a1 +svn cp linka linka1 svn mv a a2 +svn mv linka linka2 # Copy and update of source and dest svn cp b b1 +svn cp linkb linkb1 echo b >> b echo c >> b1 +ln -sfn bb linkb +ln -sfn bc linkb1 # Directory copy and renaming svn cp da da1 svn mv da da2 # Test one copy operation in branch cd ../branches/branch1 svn cp c c1 +svn cp linkc linkc1 echo c >> c1 +ln -sfn cc linkc1 cd ../.. -svn ci -m "rename and copy a, b and da" +svn ci -m "rename and copy a, b, c and da, plus their links" cd trunk # Copy across branch svn cp ../branches/branch1/c c -svn ci -m "copy b from branch1" +svn cp ../branches/branch1/linkc linkc +svn ci -m "copy c from branch1" # Copy deleted stuff from the past svn cp $svnurl/trunk/deletedfile@2 deletedfile svn cp $svnurl/trunk/deleteddir@2 deleteddir +svn cp $svnurl/trunk/deletedlink@2 deletedlink svn ci -m "copy stuff from the past" # Copy data from the past before it was changed svn cp $svnurl/trunk/changed@2 changed2 svn cp $svnurl/trunk/changeddir@2 changeddir2 +svn cp $svnurl/trunk/changedlink@2 changedlink2 # Harder, copy from the past before change and change it again # This confused the stupid diff path svn cp $svnurl/trunk/changed@2 changed3 +svn cp $svnurl/trunk/changedlink@2 changedlink3 echo changed3 >> changed3 +ln -sfn changed3 changedlink3 svn ci -m "copy stuff from the past before change" # Copy unchanged stuff from the past. Since no changed occured in these files # between the source and parent revision, we record them as copy from parent # instead of source rev. svn cp $svnurl/trunk/unchanged@2 unchanged2 svn cp $svnurl/trunk/unchangeddir@2 unchangeddir2 +svn cp $svnurl/trunk/unchangedlink@2 unchangedlink2 svn ci -m "copy unchanged stuff from the past" # Copy groupdir, unfortunately one file was changed after r2 so the # copy should not be recorded at all diff --git a/tests/fixtures/renames.svndump b/tests/fixtures/renames.svndump --- a/tests/fixtures/renames.svndump +++ b/tests/fixtures/renames.svndump @@ -1,6 +1,6 @@ SVN-fs-dump-format-version: 2 -UUID: 113560bd-ec36-42a6-acef-e4688a33b129 +UUID: e86bec6c-370f-405d-b639-abb48bba3962 Revision-number: 0 Prop-content-length: 56 @@ -9,25 +9,25 @@ Content-length: 56 K 8 svn:date V 27 -2008-12-05T22:48:38.139917Z +2014-04-07T22:34:17.074854Z PROPS-END Revision-number: 1 -Prop-content-length: 114 -Content-length: 114 +Prop-content-length: 118 +Content-length: 118 -K 7 -svn:log -V 12 -init project K 10 svn:author -V 7 -pmezard +V 10 +dschleimer K 8 svn:date V 27 -2008-12-05T22:48:38.525864Z +2014-04-07T22:34:17.214759Z +K 7 +svn:log +V 12 +init project PROPS-END Node-path: branches @@ -49,21 +49,21 @@ PROPS-END Revision-number: 2 -Prop-content-length: 113 -Content-length: 113 +Prop-content-length: 120 +Content-length: 120 -K 7 -svn:log -V 11 -add a and b K 10 svn:author -V 7 -pmezard +V 10 +dschleimer K 8 svn:date V 27 -2008-12-05T22:48:39.313010Z +2014-04-07T22:34:17.391940Z +K 7 +svn:log +V 14 +add everything PROPS-END Node-path: trunk/a @@ -72,6 +72,7 @@ Node-action: add Prop-content-length: 10 Text-content-length: 2 Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3 +Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b Content-length: 12 PROPS-END @@ -84,6 +85,7 @@ Node-action: add Prop-content-length: 10 Text-content-length: 2 Text-content-md5: 3b5d5c3712955042212316173ccf37be +Text-content-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b Content-length: 12 PROPS-END @@ -96,6 +98,7 @@ Node-action: add Prop-content-length: 10 Text-content-length: 8 Text-content-md5: ec1bebaea2c042beb68f7679ddd106a4 +Text-content-sha1: 2f6933b5ee0f5fdd823d9717d8729f3c2523811b Content-length: 18 PROPS-END @@ -117,12 +120,45 @@ Node-action: add Prop-content-length: 10 Text-content-length: 9 Text-content-md5: 2dfdfd8492a2c558ec838d69f73f5f6b +Text-content-sha1: fc7acf217b976525893922a9ed1bb3c3ab24f3a9 Content-length: 19 PROPS-END changed2 +Node-path: trunk/changeddir/link +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 6 +Text-content-md5: af3f1e8f8fa51f08e4985bda241ee7b8 +Text-content-sha1: f11a0ea0293755a1ec59d29628130cf3fcd3ec1c +Content-length: 39 + +K 11 +svn:special +V 1 +* +PROPS-END +link f + +Node-path: trunk/changedlink +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 12 +Text-content-md5: d91fb1e1062e62a17f97b44932d454c4 +Text-content-sha1: 8c147187742f58ed0cd8707ddd0c0942fe8b2616 +Content-length: 45 + +K 11 +svn:special +V 1 +* +PROPS-END +link changed + Node-path: trunk/da Node-kind: dir Node-action: add @@ -138,12 +174,29 @@ Node-action: add Prop-content-length: 10 Text-content-length: 2 Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1 +Text-content-sha1: 2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69 Content-length: 12 PROPS-END c +Node-path: trunk/da/dalink +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 8 +Text-content-md5: 21af4beda4f4d197c0b1cecbf11543dc +Text-content-sha1: 52f2276428bcb4cf45fefaf293521b5b3a26aa5f +Content-length: 41 + +K 11 +svn:special +V 1 +* +PROPS-END +link daf + Node-path: trunk/da/db Node-kind: dir Node-action: add @@ -159,12 +212,29 @@ Node-action: add Prop-content-length: 10 Text-content-length: 2 Text-content-md5: e29311f6f1bf1af907f9ef9f44b8328b +Text-content-sha1: e983f374794de9c64e3d1c1de1d490c0756eeeff Content-length: 12 PROPS-END d +Node-path: trunk/da/db/dblink +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 11 +Text-content-md5: 301198daf87f24796a8be0746389da42 +Text-content-sha1: af5485e6ea78867c36f7993542cbaadb570b79c8 +Content-length: 44 + +K 11 +svn:special +V 1 +* +PROPS-END +link ../daf + Node-path: trunk/deleteddir Node-kind: dir Node-action: add @@ -180,24 +250,58 @@ Node-action: add Prop-content-length: 10 Text-content-length: 11 Text-content-md5: 49b72b575e26ecddb296dd59b24c3e67 +Text-content-sha1: 02801293a2cd7e4c105239d34a3cfa4a4eb9c921 Content-length: 21 PROPS-END deleteddir +Node-path: trunk/deleteddir/link +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 6 +Text-content-md5: af3f1e8f8fa51f08e4985bda241ee7b8 +Text-content-sha1: f11a0ea0293755a1ec59d29628130cf3fcd3ec1c +Content-length: 39 + +K 11 +svn:special +V 1 +* +PROPS-END +link f + Node-path: trunk/deletedfile Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 8 Text-content-md5: 4d742b2f247bec99b41a60acbebc149a +Text-content-sha1: 1ef5922542033869106719d682a87ed706af4ddd Content-length: 18 PROPS-END deleted +Node-path: trunk/deletedlink +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 6 +Text-content-md5: e9292b8c4fca95ac8c70b4ae040d0b79 +Text-content-sha1: 7325442a5f7383205e66db563025d51535883784 +Content-length: 39 + +K 11 +svn:special +V 1 +* +PROPS-END +link b + Node-path: trunk/groupdir Node-kind: dir Node-action: add @@ -213,6 +317,7 @@ Node-action: add Prop-content-length: 10 Text-content-length: 2 Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3 +Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b Content-length: 12 PROPS-END @@ -225,18 +330,84 @@ Node-action: add Prop-content-length: 10 Text-content-length: 2 Text-content-md5: 3b5d5c3712955042212316173ccf37be +Text-content-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b Content-length: 12 PROPS-END b +Node-path: trunk/groupdir/linka +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 6 +Text-content-md5: c118dba188202a1efc975bef6064180b +Text-content-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf +Content-length: 39 + +K 11 +svn:special +V 1 +* +PROPS-END +link a + +Node-path: trunk/groupdir/linkb +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 6 +Text-content-md5: e9292b8c4fca95ac8c70b4ae040d0b79 +Text-content-sha1: 7325442a5f7383205e66db563025d51535883784 +Content-length: 39 + +K 11 +svn:special +V 1 +* +PROPS-END +link b + +Node-path: trunk/linka +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 6 +Text-content-md5: c118dba188202a1efc975bef6064180b +Text-content-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf +Content-length: 39 + +K 11 +svn:special +V 1 +* +PROPS-END +link a + +Node-path: trunk/linkb +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 6 +Text-content-md5: e9292b8c4fca95ac8c70b4ae040d0b79 +Text-content-sha1: 7325442a5f7383205e66db563025d51535883784 +Content-length: 39 + +K 11 +svn:special +V 1 +* +PROPS-END +link b + Node-path: trunk/unchanged Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 10 Text-content-md5: 85ae5b04dd0a666efad8633d142a4635 +Text-content-sha1: c654a5435f170cfad37e136fee9672ecc40afd4a Content-length: 20 PROPS-END @@ -258,28 +429,61 @@ Node-action: add Prop-content-length: 10 Text-content-length: 11 Text-content-md5: a11092875079a002afb9ecef07f510e7 +Text-content-sha1: c18ebadf1cffd6a79e4b74c50474b3cf8d5cb32b Content-length: 21 PROPS-END unchanged2 +Node-path: trunk/unchangeddir/link +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 6 +Text-content-md5: af3f1e8f8fa51f08e4985bda241ee7b8 +Text-content-sha1: f11a0ea0293755a1ec59d29628130cf3fcd3ec1c +Content-length: 39 + +K 11 +svn:special +V 1 +* +PROPS-END +link f + +Node-path: trunk/unchangedlink +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 14 +Text-content-md5: 1aa9c01278c74a273e3117dc42426153 +Text-content-sha1: 3f1a6ab03b820f0baab56c083f77bd9279db2486 +Content-length: 47 + +K 11 +svn:special +V 1 +* +PROPS-END +link unchanged + Revision-number: 3 -Prop-content-length: 123 -Content-length: 123 +Prop-content-length: 127 +Content-length: 127 -K 7 -svn:log -V 21 -delete files and dirs K 10 svn:author -V 7 -pmezard +V 10 +dschleimer K 8 svn:date V 27 -2008-12-05T22:48:40.224632Z +2014-04-07T22:34:17.496578Z +K 7 +svn:log +V 21 +delete files and dirs PROPS-END Node-path: trunk/changed @@ -287,6 +491,7 @@ Node-kind: file Node-action: change Text-content-length: 16 Text-content-md5: 1725f40a29aad369a39b0f96c82d50f9 +Text-content-sha1: bd7078ed7302005a46b0f32b08cb81406df5d5cf Content-length: 16 changed @@ -298,23 +503,55 @@ Node-kind: file Node-action: change Text-content-length: 18 Text-content-md5: 984b8c4ab9193b7659b9f914897a949c +Text-content-sha1: df588cfa0799c1d4447646645ff2799e23e59f57 Content-length: 18 changed2 changed2 +Node-path: trunk/changeddir/link +Node-kind: file +Node-action: change +Text-content-length: 15 +Text-content-md5: 19b47078b08789b4af5bc8d78b09f051 +Text-content-sha1: 697d35a9a0857889666f1cae1baa9e294b4cf36f +Content-length: 15 + +link ../changed + +Node-path: trunk/changedlink +Node-kind: file +Node-action: change +Text-content-length: 17 +Text-content-md5: 78d7d7c917f0f0355f01f23508cc0a0a +Text-content-sha1: 6d7057bfb5ba8dffc0184f491e4fa43ec1904cdd +Content-length: 17 + +link changeddir/f + Node-path: trunk/groupdir/a Node-kind: file Node-action: change Text-content-length: 4 Text-content-md5: 0d227f1abf8c2932d342e9b99cc957eb +Text-content-sha1: d7c8127a20a396cff08af086a1c695b0636f0c29 Content-length: 4 a a +Node-path: trunk/groupdir/linka +Node-kind: file +Node-action: change +Text-content-length: 9 +Text-content-md5: 37f1cfbed04f4354d8e706a1f1f626b6 +Text-content-sha1: 7e368116b09c906ec1b989cefe328fd6dec4f759 +Content-length: 9 + +link ../a + Node-path: trunk/deleteddir Node-action: delete @@ -323,22 +560,26 @@ Node-path: trunk/deletedfile Node-action: delete +Node-path: trunk/deletedlink +Node-action: delete + + Revision-number: 4 -Prop-content-length: 116 -Content-length: 116 +Prop-content-length: 120 +Content-length: 120 -K 7 -svn:log -V 14 -create branch1 K 10 svn:author -V 7 -pmezard +V 10 +dschleimer K 8 svn:date V 27 -2008-12-05T22:48:42.184704Z +2014-04-07T22:34:17.595148Z +K 7 +svn:log +V 14 +create branch1 PROPS-END Node-path: branches/branch1 @@ -346,14 +587,6 @@ Node-kind: dir Node-action: add Node-copyfrom-rev: 1 Node-copyfrom-path: trunk -Prop-content-length: 34 -Content-length: 34 - -K 13 -svn:mergeinfo -V 0 - -PROPS-END Node-path: branches/branch1/a @@ -362,6 +595,7 @@ Node-action: add Node-copyfrom-rev: 2 Node-copyfrom-path: trunk/a Text-copy-source-md5: 60b725f10c9c85c70d97880dfe8191b3 +Text-copy-source-sha1: 3f786850e387550fdab836ed7e6dc881de23001b Node-path: branches/branch1/b @@ -370,6 +604,7 @@ Node-action: add Node-copyfrom-rev: 2 Node-copyfrom-path: trunk/b Text-copy-source-md5: 3b5d5c3712955042212316173ccf37be +Text-copy-source-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b Node-path: branches/branch1/changed @@ -378,6 +613,7 @@ Node-action: add Node-copyfrom-rev: 3 Node-copyfrom-path: trunk/changed Text-copy-source-md5: 1725f40a29aad369a39b0f96c82d50f9 +Text-copy-source-sha1: bd7078ed7302005a46b0f32b08cb81406df5d5cf Node-path: branches/branch1/changeddir @@ -397,8 +633,33 @@ Node-action: add Node-copyfrom-rev: 3 Node-copyfrom-path: trunk/changeddir/f Text-copy-source-md5: 984b8c4ab9193b7659b9f914897a949c +Text-copy-source-sha1: df588cfa0799c1d4447646645ff2799e23e59f57 + + +Node-path: branches/branch1/changeddir/link +Node-kind: file +Node-action: delete + +Node-path: branches/branch1/changeddir/link +Node-kind: file +Node-action: add +Node-copyfrom-rev: 3 +Node-copyfrom-path: trunk/changeddir/link +Text-copy-source-md5: 19b47078b08789b4af5bc8d78b09f051 +Text-copy-source-sha1: 697d35a9a0857889666f1cae1baa9e294b4cf36f + + + + +Node-path: branches/branch1/changedlink +Node-kind: file +Node-action: add +Node-copyfrom-rev: 3 +Node-copyfrom-path: trunk/changedlink +Text-copy-source-md5: 78d7d7c917f0f0355f01f23508cc0a0a +Text-copy-source-sha1: 6d7057bfb5ba8dffc0184f491e4fa43ec1904cdd Node-path: branches/branch1/da @@ -425,8 +686,42 @@ Node-action: add Node-copyfrom-rev: 3 Node-copyfrom-path: trunk/groupdir/a Text-copy-source-md5: 0d227f1abf8c2932d342e9b99cc957eb +Text-copy-source-sha1: d7c8127a20a396cff08af086a1c695b0636f0c29 + + + + +Node-path: branches/branch1/groupdir/linka +Node-kind: file +Node-action: delete + +Node-path: branches/branch1/groupdir/linka +Node-kind: file +Node-action: add +Node-copyfrom-rev: 3 +Node-copyfrom-path: trunk/groupdir/linka +Text-copy-source-md5: 37f1cfbed04f4354d8e706a1f1f626b6 +Text-copy-source-sha1: 7e368116b09c906ec1b989cefe328fd6dec4f759 + + + + +Node-path: branches/branch1/linka +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk/linka +Text-copy-source-md5: c118dba188202a1efc975bef6064180b +Text-copy-source-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf +Node-path: branches/branch1/linkb +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk/linkb +Text-copy-source-md5: e9292b8c4fca95ac8c70b4ae040d0b79 +Text-copy-source-sha1: 7325442a5f7383205e66db563025d51535883784 Node-path: branches/branch1/unchanged @@ -435,6 +730,7 @@ Node-action: add Node-copyfrom-rev: 2 Node-copyfrom-path: trunk/unchanged Text-copy-source-md5: 85ae5b04dd0a666efad8633d142a4635 +Text-copy-source-sha1: c654a5435f170cfad37e136fee9672ecc40afd4a Node-path: branches/branch1/unchangeddir @@ -444,22 +740,31 @@ Node-copyfrom-rev: 2 Node-copyfrom-path: trunk/unchangeddir +Node-path: branches/branch1/unchangedlink +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk/unchangedlink +Text-copy-source-md5: 1aa9c01278c74a273e3117dc42426153 +Text-copy-source-sha1: 3f1a6ab03b820f0baab56c083f77bd9279db2486 + + Revision-number: 5 -Prop-content-length: 106 -Content-length: 106 +Prop-content-length: 121 +Content-length: 121 -K 7 -svn:log -V 5 -add c K 10 svn:author -V 7 -pmezard +V 10 +dschleimer K 8 svn:date V 27 -2008-12-05T22:48:43.175723Z +2014-04-07T22:34:17.655921Z +K 7 +svn:log +V 15 +add c and linkc PROPS-END Node-path: branches/branch1/c @@ -468,28 +773,45 @@ Node-action: add Prop-content-length: 10 Text-content-length: 2 Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1 +Text-content-sha1: 2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69 Content-length: 12 PROPS-END c +Node-path: branches/branch1/linkc +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 6 +Text-content-md5: aaa83258c434079cc82a5b4868340dc0 +Text-content-sha1: d347f561790aa523b963fc1714c64e1d158cc5d7 +Content-length: 39 + +K 11 +svn:special +V 1 +* +PROPS-END +link c + Revision-number: 6 -Prop-content-length: 129 -Content-length: 129 +Prop-content-length: 154 +Content-length: 154 -K 7 -svn:log -V 27 -rename and copy a, b and da K 10 svn:author -V 7 -pmezard +V 10 +dschleimer K 8 svn:date V 27 -2008-12-05T22:48:50.200094Z +2014-04-07T22:34:17.901507Z +K 7 +svn:log +V 48 +rename and copy a, b, c and da, plus their links PROPS-END Node-path: branches/branch1/c1 @@ -498,34 +820,37 @@ Node-action: add Node-copyfrom-rev: 5 Node-copyfrom-path: branches/branch1/c Text-copy-source-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1 -Prop-content-length: 34 +Text-copy-source-sha1: 2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69 Text-content-length: 4 Text-content-md5: 63fad9092ad37713ebe26b3193f89c41 -Content-length: 38 - -K 13 -svn:mergeinfo -V 0 +Text-content-sha1: ccfb93b7bac6f1520f0adc0eebc2cafe9da80f42 +Content-length: 4 -PROPS-END c c +Node-path: branches/branch1/linkc1 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 5 +Node-copyfrom-path: branches/branch1/linkc +Text-copy-source-md5: aaa83258c434079cc82a5b4868340dc0 +Text-copy-source-sha1: d347f561790aa523b963fc1714c64e1d158cc5d7 +Text-content-length: 7 +Text-content-md5: ea7a177c3c3af680cf62010efe71275f +Text-content-sha1: d780ef86a4c5016931861dc32373a1155755e404 +Content-length: 7 + +link cc + Node-path: trunk/a1 Node-kind: file Node-action: add Node-copyfrom-rev: 2 Node-copyfrom-path: trunk/a Text-copy-source-md5: 60b725f10c9c85c70d97880dfe8191b3 -Prop-content-length: 34 -Content-length: 34 - -K 13 -svn:mergeinfo -V 0 - -PROPS-END +Text-copy-source-sha1: 3f786850e387550fdab836ed7e6dc881de23001b Node-path: trunk/a2 @@ -534,14 +859,7 @@ Node-action: add Node-copyfrom-rev: 2 Node-copyfrom-path: trunk/a Text-copy-source-md5: 60b725f10c9c85c70d97880dfe8191b3 -Prop-content-length: 34 -Content-length: 34 - -K 13 -svn:mergeinfo -V 0 - -PROPS-END +Text-copy-source-sha1: 3f786850e387550fdab836ed7e6dc881de23001b Node-path: trunk/b @@ -549,6 +867,7 @@ Node-kind: file Node-action: change Text-content-length: 4 Text-content-md5: 06ac26ed8b614fc0b141e4542aa067c2 +Text-content-sha1: f6980469e74f7125178e88ec571e06fe6ce86e95 Content-length: 4 b @@ -561,16 +880,12 @@ Node-action: add Node-copyfrom-rev: 2 Node-copyfrom-path: trunk/b Text-copy-source-md5: 3b5d5c3712955042212316173ccf37be -Prop-content-length: 34 +Text-copy-source-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b Text-content-length: 4 Text-content-md5: 33cb6785d50937d8d307ebb66d6259a7 -Content-length: 38 - -K 13 -svn:mergeinfo -V 0 +Text-content-sha1: 7a6478264aa11a0f4befef356c03e83f2b1f6eba +Content-length: 4 -PROPS-END b c @@ -580,14 +895,6 @@ Node-kind: dir Node-action: add Node-copyfrom-rev: 2 Node-copyfrom-path: trunk/da -Prop-content-length: 34 -Content-length: 34 - -K 13 -svn:mergeinfo -V 0 - -PROPS-END Node-path: trunk/da2 @@ -595,16 +902,50 @@ Node-kind: dir Node-action: add Node-copyfrom-rev: 2 Node-copyfrom-path: trunk/da -Prop-content-length: 34 -Content-length: 34 -K 13 -svn:mergeinfo -V 0 -PROPS-END +Node-path: trunk/linka1 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk/linka +Text-copy-source-md5: c118dba188202a1efc975bef6064180b +Text-copy-source-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf +Node-path: trunk/linka2 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk/linka +Text-copy-source-md5: c118dba188202a1efc975bef6064180b +Text-copy-source-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf + + +Node-path: trunk/linkb +Node-kind: file +Node-action: change +Text-content-length: 7 +Text-content-md5: 00b18251bf95a42453612a62619254c0 +Text-content-sha1: 8d00f006e36676f00d40c3935b6992cbb8349c2b +Content-length: 7 + +link bb + +Node-path: trunk/linkb1 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk/linkb +Text-copy-source-md5: e9292b8c4fca95ac8c70b4ae040d0b79 +Text-copy-source-sha1: 7325442a5f7383205e66db563025d51535883784 +Text-content-length: 7 +Text-content-md5: 1c9f17365658cf9ea7904568eff11f27 +Text-content-sha1: 163de098ef857862584154ca26e3d028fb34ba45 +Content-length: 7 + +link bc + Node-path: trunk/a Node-action: delete @@ -613,22 +954,26 @@ Node-path: trunk/da Node-action: delete +Node-path: trunk/linka +Node-action: delete + + Revision-number: 7 -Prop-content-length: 121 -Content-length: 121 +Prop-content-length: 125 +Content-length: 125 -K 7 -svn:log -V 19 -copy b from branch1 K 10 svn:author -V 7 -pmezard +V 10 +dschleimer K 8 svn:date V 27 -2008-12-05T22:48:52.154125Z +2014-04-07T22:34:17.969842Z +K 7 +svn:log +V 19 +copy c from branch1 PROPS-END Node-path: trunk/c @@ -637,32 +982,34 @@ Node-action: add Node-copyfrom-rev: 5 Node-copyfrom-path: branches/branch1/c Text-copy-source-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1 -Prop-content-length: 34 -Content-length: 34 +Text-copy-source-sha1: 2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69 -K 13 -svn:mergeinfo -V 0 -PROPS-END +Node-path: trunk/linkc +Node-kind: file +Node-action: add +Node-copyfrom-rev: 5 +Node-copyfrom-path: branches/branch1/linkc +Text-copy-source-md5: aaa83258c434079cc82a5b4868340dc0 +Text-copy-source-sha1: d347f561790aa523b963fc1714c64e1d158cc5d7 Revision-number: 8 -Prop-content-length: 126 -Content-length: 126 +Prop-content-length: 130 +Content-length: 130 -K 7 -svn:log -V 24 -copy stuff from the past K 10 svn:author -V 7 -pmezard +V 10 +dschleimer K 8 svn:date V 27 -2008-12-05T22:48:55.160439Z +2014-04-07T22:34:18.068356Z +K 7 +svn:log +V 24 +copy stuff from the past PROPS-END Node-path: trunk/deleteddir @@ -678,24 +1025,34 @@ Node-action: add Node-copyfrom-rev: 2 Node-copyfrom-path: trunk/deletedfile Text-copy-source-md5: 4d742b2f247bec99b41a60acbebc149a +Text-copy-source-sha1: 1ef5922542033869106719d682a87ed706af4ddd + + +Node-path: trunk/deletedlink +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk/deletedlink +Text-copy-source-md5: e9292b8c4fca95ac8c70b4ae040d0b79 +Text-copy-source-sha1: 7325442a5f7383205e66db563025d51535883784 Revision-number: 9 -Prop-content-length: 140 -Content-length: 140 +Prop-content-length: 144 +Content-length: 144 -K 7 -svn:log -V 38 -copy stuff from the past before change K 10 svn:author -V 7 -pmezard +V 10 +dschleimer K 8 svn:date V 27 -2008-12-05T22:48:59.163460Z +2014-04-07T22:34:18.201489Z +K 7 +svn:log +V 38 +copy stuff from the past before change PROPS-END Node-path: trunk/changed2 @@ -704,6 +1061,7 @@ Node-action: add Node-copyfrom-rev: 2 Node-copyfrom-path: trunk/changed Text-copy-source-md5: ec1bebaea2c042beb68f7679ddd106a4 +Text-copy-source-sha1: 2f6933b5ee0f5fdd823d9717d8729f3c2523811b Node-path: trunk/changed3 @@ -712,8 +1070,10 @@ Node-action: add Node-copyfrom-rev: 2 Node-copyfrom-path: trunk/changed Text-copy-source-md5: ec1bebaea2c042beb68f7679ddd106a4 +Text-copy-source-sha1: 2f6933b5ee0f5fdd823d9717d8729f3c2523811b Text-content-length: 17 Text-content-md5: 7d93e8c4d61c2a7b05c20b7d8bf11f83 +Text-content-sha1: d78c3e7f04c44b599787ec534a3193357df1fa37 Content-length: 17 changed @@ -727,22 +1087,45 @@ Node-copyfrom-rev: 2 Node-copyfrom-path: trunk/changeddir +Node-path: trunk/changedlink2 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk/changedlink +Text-copy-source-md5: d91fb1e1062e62a17f97b44932d454c4 +Text-copy-source-sha1: 8c147187742f58ed0cd8707ddd0c0942fe8b2616 + + +Node-path: trunk/changedlink3 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk/changedlink +Text-copy-source-md5: d91fb1e1062e62a17f97b44932d454c4 +Text-copy-source-sha1: 8c147187742f58ed0cd8707ddd0c0942fe8b2616 +Text-content-length: 13 +Text-content-md5: 0ad81e7d7fc35e744e0ad3dea0f158fe +Text-content-sha1: bae4618ecddda5e6ab6e5fa3b116f5d14dc3464b +Content-length: 13 + +link changed3 + Revision-number: 10 -Prop-content-length: 136 -Content-length: 136 +Prop-content-length: 140 +Content-length: 140 -K 7 -svn:log -V 34 -copy unchanged stuff from the past K 10 svn:author -V 7 -pmezard +V 10 +dschleimer K 8 svn:date V 27 -2008-12-05T22:49:02.160163Z +2014-04-07T22:34:18.298821Z +K 7 +svn:log +V 34 +copy unchanged stuff from the past PROPS-END Node-path: trunk/unchanged2 @@ -751,6 +1134,7 @@ Node-action: add Node-copyfrom-rev: 2 Node-copyfrom-path: trunk/unchanged Text-copy-source-md5: 85ae5b04dd0a666efad8633d142a4635 +Text-copy-source-sha1: c654a5435f170cfad37e136fee9672ecc40afd4a Node-path: trunk/unchangeddir2 @@ -760,22 +1144,31 @@ Node-copyfrom-rev: 2 Node-copyfrom-path: trunk/unchangeddir +Node-path: trunk/unchangedlink2 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk/unchangedlink +Text-copy-source-md5: 1aa9c01278c74a273e3117dc42426153 +Text-copy-source-sha1: 3f1a6ab03b820f0baab56c083f77bd9279db2486 + + Revision-number: 11 -Prop-content-length: 129 -Content-length: 129 +Prop-content-length: 133 +Content-length: 133 -K 7 -svn:log -V 27 -copy groupdir from the past K 10 svn:author -V 7 -pmezard +V 10 +dschleimer K 8 svn:date V 27 -2008-12-05T22:49:04.157303Z +2014-04-07T22:34:18.370254Z +K 7 +svn:log +V 27 +copy groupdir from the past PROPS-END Node-path: trunk/groupdir2 diff --git a/tests/fixtures/renames.sh b/tests/fixtures/renames_with_prefix.sh copy from tests/fixtures/renames.sh copy to tests/fixtures/renames_with_prefix.sh --- a/tests/fixtures/renames.sh +++ b/tests/fixtures/renames_with_prefix.sh @@ -1,102 +1,137 @@ #!/bin/sh # -# Generate renames.svndump +# Generate renames_with_prefix.svndump # +set -e + +rm -rf temp + mkdir temp cd temp -mkdir project-orig -cd project-orig +mkdir -p project-orig/prefix +cd project-orig/prefix mkdir trunk mkdir branches -cd .. +cd ../.. svnadmin create testrepo svnurl=file://`pwd`/testrepo svn import project-orig $svnurl -m "init project" +svnurl=$svnurl/prefix svn co $svnurl project cd project/trunk # Entries for regular tests echo a > a echo b > b +ln -s a linka +ln -s b linkb mkdir -p da/db echo c > da/daf +ln -s daf da/dalink echo d > da/db/dbf +ln -s ../daf da/db/dblink # Entries to test delete + copy echo deleted > deletedfile +ln -s b deletedlink mkdir deleteddir echo deleteddir > deleteddir/f +ln -s f deleteddir/link # Entries to test copy before change echo changed > changed +ln -s changed changedlink mkdir changeddir echo changed2 > changeddir/f +ln -s f changeddir/link # Entries unchanged in the rest of history echo unchanged > unchanged +ln -s unchanged unchangedlink mkdir unchangeddir echo unchanged2 > unchangeddir/f +ln -s f unchangeddir/link # One of the files will be changed afterwards, to test # group copies detection mkdir groupdir echo a > groupdir/a echo b > groupdir/b -svn add a b da deletedfile deleteddir changed changeddir unchanged unchangeddir groupdir -svn ci -m "add a and b" +ln -s a groupdir/linka +ln -s b groupdir/linkb +svn add a b linka linkb da deleted* changed* unchanged* groupdir +svn ci -m "add everything" # Remove files to be copied later svn rm deletedfile svn rm deleteddir +svn rm deletedlink # Update files to be copied before this change echo changed >> changed echo changed2 >> changeddir/f +ln -sfn changeddir/f changedlink +ln -sfn ../changed changeddir/link # Update one of the groupdir files echo a >> groupdir/a +ln -sfn ../a groupdir/linka svn ci -m "delete files and dirs" cd ../branches svn cp ../trunk branch1 svn ci -m "create branch1" cd branch1 echo c > c -svn add c -svn ci -m "add c" +ln -s c linkc +svn add c linkc +svn ci -m "add c and linkc" cd ../../trunk # Regular copy and rename svn cp a a1 +svn cp linka linka1 svn mv a a2 +svn mv linka linka2 # Copy and update of source and dest svn cp b b1 +svn cp linkb linkb1 echo b >> b echo c >> b1 +ln -sfn bb linkb +ln -sfn bc linkb1 # Directory copy and renaming svn cp da da1 svn mv da da2 # Test one copy operation in branch cd ../branches/branch1 svn cp c c1 +svn cp linkc linkc1 echo c >> c1 +ln -sfn cc linkc1 cd ../.. -svn ci -m "rename and copy a, b and da" +svn ci -m "rename and copy a, b, c and da, plus their links" cd trunk # Copy across branch svn cp ../branches/branch1/c c -svn ci -m "copy b from branch1" +svn cp ../branches/branch1/linkc linkc +svn ci -m "copy c from branch1" # Copy deleted stuff from the past svn cp $svnurl/trunk/deletedfile@2 deletedfile svn cp $svnurl/trunk/deleteddir@2 deleteddir +svn cp $svnurl/trunk/deletedlink@2 deletedlink svn ci -m "copy stuff from the past" # Copy data from the past before it was changed svn cp $svnurl/trunk/changed@2 changed2 svn cp $svnurl/trunk/changeddir@2 changeddir2 +svn cp $svnurl/trunk/changedlink@2 changedlink2 # Harder, copy from the past before change and change it again # This confused the stupid diff path svn cp $svnurl/trunk/changed@2 changed3 +svn cp $svnurl/trunk/changedlink@2 changedlink3 echo changed3 >> changed3 +ln -sfn changed3 changedlink3 svn ci -m "copy stuff from the past before change" # Copy unchanged stuff from the past. Since no changed occured in these files # between the source and parent revision, we record them as copy from parent # instead of source rev. svn cp $svnurl/trunk/unchanged@2 unchanged2 svn cp $svnurl/trunk/unchangeddir@2 unchangeddir2 +svn cp $svnurl/trunk/unchangedlink@2 unchangedlink2 svn ci -m "copy unchanged stuff from the past" # Copy groupdir, unfortunately one file was changed after r2 so the # copy should not be recorded at all @@ -104,4 +139,4 @@ svn cp $svnurl/trunk/groupdir@2 groupdir svn ci -m "copy groupdir from the past" cd ../.. -svnadmin dump testrepo > ../renames.svndump +svnadmin dump testrepo > ../renames_with_prefix.svndump diff --git a/tests/fixtures/renames_with_prefix.svndump b/tests/fixtures/renames_with_prefix.svndump new file mode 100644 --- /dev/null +++ b/tests/fixtures/renames_with_prefix.svndump @@ -0,0 +1,1189 @@ +SVN-fs-dump-format-version: 2 + +UUID: ae30a990-0fd3-493e-b5d7-883bdd606745 + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2014-04-08T01:02:22.118401Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 118 +Content-length: 118 + +K 10 +svn:author +V 10 +dschleimer +K 8 +svn:date +V 27 +2014-04-08T01:02:22.138394Z +K 7 +svn:log +V 12 +init project +PROPS-END + +Node-path: prefix +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: prefix/branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: prefix/trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Revision-number: 2 +Prop-content-length: 120 +Content-length: 120 + +K 10 +svn:author +V 10 +dschleimer +K 8 +svn:date +V 27 +2014-04-08T01:02:22.304127Z +K 7 +svn:log +V 14 +add everything +PROPS-END + +Node-path: prefix/trunk/a +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 2 +Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3 +Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b +Content-length: 12 + +PROPS-END +a + + +Node-path: prefix/trunk/b +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 2 +Text-content-md5: 3b5d5c3712955042212316173ccf37be +Text-content-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b +Content-length: 12 + +PROPS-END +b + + +Node-path: prefix/trunk/changed +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 8 +Text-content-md5: ec1bebaea2c042beb68f7679ddd106a4 +Text-content-sha1: 2f6933b5ee0f5fdd823d9717d8729f3c2523811b +Content-length: 18 + +PROPS-END +changed + + +Node-path: prefix/trunk/changeddir +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: prefix/trunk/changeddir/f +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 9 +Text-content-md5: 2dfdfd8492a2c558ec838d69f73f5f6b +Text-content-sha1: fc7acf217b976525893922a9ed1bb3c3ab24f3a9 +Content-length: 19 + +PROPS-END +changed2 + + +Node-path: prefix/trunk/changeddir/link +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 6 +Text-content-md5: af3f1e8f8fa51f08e4985bda241ee7b8 +Text-content-sha1: f11a0ea0293755a1ec59d29628130cf3fcd3ec1c +Content-length: 39 + +K 11 +svn:special +V 1 +* +PROPS-END +link f + +Node-path: prefix/trunk/changedlink +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 12 +Text-content-md5: d91fb1e1062e62a17f97b44932d454c4 +Text-content-sha1: 8c147187742f58ed0cd8707ddd0c0942fe8b2616 +Content-length: 45 + +K 11 +svn:special +V 1 +* +PROPS-END +link changed + +Node-path: prefix/trunk/da +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: prefix/trunk/da/daf +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 2 +Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1 +Text-content-sha1: 2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69 +Content-length: 12 + +PROPS-END +c + + +Node-path: prefix/trunk/da/dalink +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 8 +Text-content-md5: 21af4beda4f4d197c0b1cecbf11543dc +Text-content-sha1: 52f2276428bcb4cf45fefaf293521b5b3a26aa5f +Content-length: 41 + +K 11 +svn:special +V 1 +* +PROPS-END +link daf + +Node-path: prefix/trunk/da/db +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: prefix/trunk/da/db/dbf +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 2 +Text-content-md5: e29311f6f1bf1af907f9ef9f44b8328b +Text-content-sha1: e983f374794de9c64e3d1c1de1d490c0756eeeff +Content-length: 12 + +PROPS-END +d + + +Node-path: prefix/trunk/da/db/dblink +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 11 +Text-content-md5: 301198daf87f24796a8be0746389da42 +Text-content-sha1: af5485e6ea78867c36f7993542cbaadb570b79c8 +Content-length: 44 + +K 11 +svn:special +V 1 +* +PROPS-END +link ../daf + +Node-path: prefix/trunk/deleteddir +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: prefix/trunk/deleteddir/f +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 11 +Text-content-md5: 49b72b575e26ecddb296dd59b24c3e67 +Text-content-sha1: 02801293a2cd7e4c105239d34a3cfa4a4eb9c921 +Content-length: 21 + +PROPS-END +deleteddir + + +Node-path: prefix/trunk/deleteddir/link +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 6 +Text-content-md5: af3f1e8f8fa51f08e4985bda241ee7b8 +Text-content-sha1: f11a0ea0293755a1ec59d29628130cf3fcd3ec1c +Content-length: 39 + +K 11 +svn:special +V 1 +* +PROPS-END +link f + +Node-path: prefix/trunk/deletedfile +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 8 +Text-content-md5: 4d742b2f247bec99b41a60acbebc149a +Text-content-sha1: 1ef5922542033869106719d682a87ed706af4ddd +Content-length: 18 + +PROPS-END +deleted + + +Node-path: prefix/trunk/deletedlink +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 6 +Text-content-md5: e9292b8c4fca95ac8c70b4ae040d0b79 +Text-content-sha1: 7325442a5f7383205e66db563025d51535883784 +Content-length: 39 + +K 11 +svn:special +V 1 +* +PROPS-END +link b + +Node-path: prefix/trunk/groupdir +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: prefix/trunk/groupdir/a +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 2 +Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3 +Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b +Content-length: 12 + +PROPS-END +a + + +Node-path: prefix/trunk/groupdir/b +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 2 +Text-content-md5: 3b5d5c3712955042212316173ccf37be +Text-content-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b +Content-length: 12 + +PROPS-END +b + + +Node-path: prefix/trunk/groupdir/linka +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 6 +Text-content-md5: c118dba188202a1efc975bef6064180b +Text-content-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf +Content-length: 39 + +K 11 +svn:special +V 1 +* +PROPS-END +link a + +Node-path: prefix/trunk/groupdir/linkb +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 6 +Text-content-md5: e9292b8c4fca95ac8c70b4ae040d0b79 +Text-content-sha1: 7325442a5f7383205e66db563025d51535883784 +Content-length: 39 + +K 11 +svn:special +V 1 +* +PROPS-END +link b + +Node-path: prefix/trunk/linka +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 6 +Text-content-md5: c118dba188202a1efc975bef6064180b +Text-content-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf +Content-length: 39 + +K 11 +svn:special +V 1 +* +PROPS-END +link a + +Node-path: prefix/trunk/linkb +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 6 +Text-content-md5: e9292b8c4fca95ac8c70b4ae040d0b79 +Text-content-sha1: 7325442a5f7383205e66db563025d51535883784 +Content-length: 39 + +K 11 +svn:special +V 1 +* +PROPS-END +link b + +Node-path: prefix/trunk/unchanged +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 10 +Text-content-md5: 85ae5b04dd0a666efad8633d142a4635 +Text-content-sha1: c654a5435f170cfad37e136fee9672ecc40afd4a +Content-length: 20 + +PROPS-END +unchanged + + +Node-path: prefix/trunk/unchangeddir +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: prefix/trunk/unchangeddir/f +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 11 +Text-content-md5: a11092875079a002afb9ecef07f510e7 +Text-content-sha1: c18ebadf1cffd6a79e4b74c50474b3cf8d5cb32b +Content-length: 21 + +PROPS-END +unchanged2 + + +Node-path: prefix/trunk/unchangeddir/link +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 6 +Text-content-md5: af3f1e8f8fa51f08e4985bda241ee7b8 +Text-content-sha1: f11a0ea0293755a1ec59d29628130cf3fcd3ec1c +Content-length: 39 + +K 11 +svn:special +V 1 +* +PROPS-END +link f + +Node-path: prefix/trunk/unchangedlink +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 14 +Text-content-md5: 1aa9c01278c74a273e3117dc42426153 +Text-content-sha1: 3f1a6ab03b820f0baab56c083f77bd9279db2486 +Content-length: 47 + +K 11 +svn:special +V 1 +* +PROPS-END +link unchanged + +Revision-number: 3 +Prop-content-length: 127 +Content-length: 127 + +K 10 +svn:author +V 10 +dschleimer +K 8 +svn:date +V 27 +2014-04-08T01:02:22.400275Z +K 7 +svn:log +V 21 +delete files and dirs +PROPS-END + +Node-path: prefix/trunk/changed +Node-kind: file +Node-action: change +Text-content-length: 16 +Text-content-md5: 1725f40a29aad369a39b0f96c82d50f9 +Text-content-sha1: bd7078ed7302005a46b0f32b08cb81406df5d5cf +Content-length: 16 + +changed +changed + + +Node-path: prefix/trunk/changeddir/f +Node-kind: file +Node-action: change +Text-content-length: 18 +Text-content-md5: 984b8c4ab9193b7659b9f914897a949c +Text-content-sha1: df588cfa0799c1d4447646645ff2799e23e59f57 +Content-length: 18 + +changed2 +changed2 + + +Node-path: prefix/trunk/changeddir/link +Node-kind: file +Node-action: change +Text-content-length: 15 +Text-content-md5: 19b47078b08789b4af5bc8d78b09f051 +Text-content-sha1: 697d35a9a0857889666f1cae1baa9e294b4cf36f +Content-length: 15 + +link ../changed + +Node-path: prefix/trunk/changedlink +Node-kind: file +Node-action: change +Text-content-length: 17 +Text-content-md5: 78d7d7c917f0f0355f01f23508cc0a0a +Text-content-sha1: 6d7057bfb5ba8dffc0184f491e4fa43ec1904cdd +Content-length: 17 + +link changeddir/f + +Node-path: prefix/trunk/groupdir/a +Node-kind: file +Node-action: change +Text-content-length: 4 +Text-content-md5: 0d227f1abf8c2932d342e9b99cc957eb +Text-content-sha1: d7c8127a20a396cff08af086a1c695b0636f0c29 +Content-length: 4 + +a +a + + +Node-path: prefix/trunk/groupdir/linka +Node-kind: file +Node-action: change +Text-content-length: 9 +Text-content-md5: 37f1cfbed04f4354d8e706a1f1f626b6 +Text-content-sha1: 7e368116b09c906ec1b989cefe328fd6dec4f759 +Content-length: 9 + +link ../a + +Node-path: prefix/trunk/deleteddir +Node-action: delete + + +Node-path: prefix/trunk/deletedfile +Node-action: delete + + +Node-path: prefix/trunk/deletedlink +Node-action: delete + + +Revision-number: 4 +Prop-content-length: 120 +Content-length: 120 + +K 10 +svn:author +V 10 +dschleimer +K 8 +svn:date +V 27 +2014-04-08T01:02:22.494631Z +K 7 +svn:log +V 14 +create branch1 +PROPS-END + +Node-path: prefix/branches/branch1 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: prefix/trunk + + +Node-path: prefix/branches/branch1/a +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/a +Text-copy-source-md5: 60b725f10c9c85c70d97880dfe8191b3 +Text-copy-source-sha1: 3f786850e387550fdab836ed7e6dc881de23001b + + +Node-path: prefix/branches/branch1/b +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/b +Text-copy-source-md5: 3b5d5c3712955042212316173ccf37be +Text-copy-source-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b + + +Node-path: prefix/branches/branch1/changed +Node-kind: file +Node-action: add +Node-copyfrom-rev: 3 +Node-copyfrom-path: prefix/trunk/changed +Text-copy-source-md5: 1725f40a29aad369a39b0f96c82d50f9 +Text-copy-source-sha1: bd7078ed7302005a46b0f32b08cb81406df5d5cf + + +Node-path: prefix/branches/branch1/changeddir +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/changeddir + + +Node-path: prefix/branches/branch1/changeddir/f +Node-kind: file +Node-action: delete + +Node-path: prefix/branches/branch1/changeddir/f +Node-kind: file +Node-action: add +Node-copyfrom-rev: 3 +Node-copyfrom-path: prefix/trunk/changeddir/f +Text-copy-source-md5: 984b8c4ab9193b7659b9f914897a949c +Text-copy-source-sha1: df588cfa0799c1d4447646645ff2799e23e59f57 + + + + +Node-path: prefix/branches/branch1/changeddir/link +Node-kind: file +Node-action: delete + +Node-path: prefix/branches/branch1/changeddir/link +Node-kind: file +Node-action: add +Node-copyfrom-rev: 3 +Node-copyfrom-path: prefix/trunk/changeddir/link +Text-copy-source-md5: 19b47078b08789b4af5bc8d78b09f051 +Text-copy-source-sha1: 697d35a9a0857889666f1cae1baa9e294b4cf36f + + + + +Node-path: prefix/branches/branch1/changedlink +Node-kind: file +Node-action: add +Node-copyfrom-rev: 3 +Node-copyfrom-path: prefix/trunk/changedlink +Text-copy-source-md5: 78d7d7c917f0f0355f01f23508cc0a0a +Text-copy-source-sha1: 6d7057bfb5ba8dffc0184f491e4fa43ec1904cdd + + +Node-path: prefix/branches/branch1/da +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/da + + +Node-path: prefix/branches/branch1/groupdir +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/groupdir + + +Node-path: prefix/branches/branch1/groupdir/a +Node-kind: file +Node-action: delete + +Node-path: prefix/branches/branch1/groupdir/a +Node-kind: file +Node-action: add +Node-copyfrom-rev: 3 +Node-copyfrom-path: prefix/trunk/groupdir/a +Text-copy-source-md5: 0d227f1abf8c2932d342e9b99cc957eb +Text-copy-source-sha1: d7c8127a20a396cff08af086a1c695b0636f0c29 + + + + +Node-path: prefix/branches/branch1/groupdir/linka +Node-kind: file +Node-action: delete + +Node-path: prefix/branches/branch1/groupdir/linka +Node-kind: file +Node-action: add +Node-copyfrom-rev: 3 +Node-copyfrom-path: prefix/trunk/groupdir/linka +Text-copy-source-md5: 37f1cfbed04f4354d8e706a1f1f626b6 +Text-copy-source-sha1: 7e368116b09c906ec1b989cefe328fd6dec4f759 + + + + +Node-path: prefix/branches/branch1/linka +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/linka +Text-copy-source-md5: c118dba188202a1efc975bef6064180b +Text-copy-source-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf + + +Node-path: prefix/branches/branch1/linkb +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/linkb +Text-copy-source-md5: e9292b8c4fca95ac8c70b4ae040d0b79 +Text-copy-source-sha1: 7325442a5f7383205e66db563025d51535883784 + + +Node-path: prefix/branches/branch1/unchanged +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/unchanged +Text-copy-source-md5: 85ae5b04dd0a666efad8633d142a4635 +Text-copy-source-sha1: c654a5435f170cfad37e136fee9672ecc40afd4a + + +Node-path: prefix/branches/branch1/unchangeddir +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/unchangeddir + + +Node-path: prefix/branches/branch1/unchangedlink +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/unchangedlink +Text-copy-source-md5: 1aa9c01278c74a273e3117dc42426153 +Text-copy-source-sha1: 3f1a6ab03b820f0baab56c083f77bd9279db2486 + + +Revision-number: 5 +Prop-content-length: 121 +Content-length: 121 + +K 10 +svn:author +V 10 +dschleimer +K 8 +svn:date +V 27 +2014-04-08T01:02:22.558149Z +K 7 +svn:log +V 15 +add c and linkc +PROPS-END + +Node-path: prefix/branches/branch1/c +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 2 +Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1 +Text-content-sha1: 2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69 +Content-length: 12 + +PROPS-END +c + + +Node-path: prefix/branches/branch1/linkc +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 6 +Text-content-md5: aaa83258c434079cc82a5b4868340dc0 +Text-content-sha1: d347f561790aa523b963fc1714c64e1d158cc5d7 +Content-length: 39 + +K 11 +svn:special +V 1 +* +PROPS-END +link c + +Revision-number: 6 +Prop-content-length: 154 +Content-length: 154 + +K 10 +svn:author +V 10 +dschleimer +K 8 +svn:date +V 27 +2014-04-08T01:02:22.742494Z +K 7 +svn:log +V 48 +rename and copy a, b, c and da, plus their links +PROPS-END + +Node-path: prefix/branches/branch1/c1 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 5 +Node-copyfrom-path: prefix/branches/branch1/c +Text-copy-source-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1 +Text-copy-source-sha1: 2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69 +Text-content-length: 4 +Text-content-md5: 63fad9092ad37713ebe26b3193f89c41 +Text-content-sha1: ccfb93b7bac6f1520f0adc0eebc2cafe9da80f42 +Content-length: 4 + +c +c + + +Node-path: prefix/branches/branch1/linkc1 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 5 +Node-copyfrom-path: prefix/branches/branch1/linkc +Text-copy-source-md5: aaa83258c434079cc82a5b4868340dc0 +Text-copy-source-sha1: d347f561790aa523b963fc1714c64e1d158cc5d7 +Text-content-length: 7 +Text-content-md5: ea7a177c3c3af680cf62010efe71275f +Text-content-sha1: d780ef86a4c5016931861dc32373a1155755e404 +Content-length: 7 + +link cc + +Node-path: prefix/trunk/a1 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/a +Text-copy-source-md5: 60b725f10c9c85c70d97880dfe8191b3 +Text-copy-source-sha1: 3f786850e387550fdab836ed7e6dc881de23001b + + +Node-path: prefix/trunk/a2 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/a +Text-copy-source-md5: 60b725f10c9c85c70d97880dfe8191b3 +Text-copy-source-sha1: 3f786850e387550fdab836ed7e6dc881de23001b + + +Node-path: prefix/trunk/b +Node-kind: file +Node-action: change +Text-content-length: 4 +Text-content-md5: 06ac26ed8b614fc0b141e4542aa067c2 +Text-content-sha1: f6980469e74f7125178e88ec571e06fe6ce86e95 +Content-length: 4 + +b +b + + +Node-path: prefix/trunk/b1 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/b +Text-copy-source-md5: 3b5d5c3712955042212316173ccf37be +Text-copy-source-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b +Text-content-length: 4 +Text-content-md5: 33cb6785d50937d8d307ebb66d6259a7 +Text-content-sha1: 7a6478264aa11a0f4befef356c03e83f2b1f6eba +Content-length: 4 + +b +c + + +Node-path: prefix/trunk/da1 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/da + + +Node-path: prefix/trunk/da2 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/da + + +Node-path: prefix/trunk/linka1 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/linka +Text-copy-source-md5: c118dba188202a1efc975bef6064180b +Text-copy-source-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf + + +Node-path: prefix/trunk/linka2 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/linka +Text-copy-source-md5: c118dba188202a1efc975bef6064180b +Text-copy-source-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf + + +Node-path: prefix/trunk/linkb +Node-kind: file +Node-action: change +Text-content-length: 7 +Text-content-md5: 00b18251bf95a42453612a62619254c0 +Text-content-sha1: 8d00f006e36676f00d40c3935b6992cbb8349c2b +Content-length: 7 + +link bb + +Node-path: prefix/trunk/linkb1 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/linkb +Text-copy-source-md5: e9292b8c4fca95ac8c70b4ae040d0b79 +Text-copy-source-sha1: 7325442a5f7383205e66db563025d51535883784 +Text-content-length: 7 +Text-content-md5: 1c9f17365658cf9ea7904568eff11f27 +Text-content-sha1: 163de098ef857862584154ca26e3d028fb34ba45 +Content-length: 7 + +link bc + +Node-path: prefix/trunk/a +Node-action: delete + + +Node-path: prefix/trunk/da +Node-action: delete + + +Node-path: prefix/trunk/linka +Node-action: delete + + +Revision-number: 7 +Prop-content-length: 125 +Content-length: 125 + +K 10 +svn:author +V 10 +dschleimer +K 8 +svn:date +V 27 +2014-04-08T01:02:22.807910Z +K 7 +svn:log +V 19 +copy c from branch1 +PROPS-END + +Node-path: prefix/trunk/c +Node-kind: file +Node-action: add +Node-copyfrom-rev: 5 +Node-copyfrom-path: prefix/branches/branch1/c +Text-copy-source-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1 +Text-copy-source-sha1: 2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69 + + +Node-path: prefix/trunk/linkc +Node-kind: file +Node-action: add +Node-copyfrom-rev: 5 +Node-copyfrom-path: prefix/branches/branch1/linkc +Text-copy-source-md5: aaa83258c434079cc82a5b4868340dc0 +Text-copy-source-sha1: d347f561790aa523b963fc1714c64e1d158cc5d7 + + +Revision-number: 8 +Prop-content-length: 130 +Content-length: 130 + +K 10 +svn:author +V 10 +dschleimer +K 8 +svn:date +V 27 +2014-04-08T01:02:22.895608Z +K 7 +svn:log +V 24 +copy stuff from the past +PROPS-END + +Node-path: prefix/trunk/deleteddir +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/deleteddir + + +Node-path: prefix/trunk/deletedfile +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/deletedfile +Text-copy-source-md5: 4d742b2f247bec99b41a60acbebc149a +Text-copy-source-sha1: 1ef5922542033869106719d682a87ed706af4ddd + + +Node-path: prefix/trunk/deletedlink +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/deletedlink +Text-copy-source-md5: e9292b8c4fca95ac8c70b4ae040d0b79 +Text-copy-source-sha1: 7325442a5f7383205e66db563025d51535883784 + + +Revision-number: 9 +Prop-content-length: 144 +Content-length: 144 + +K 10 +svn:author +V 10 +dschleimer +K 8 +svn:date +V 27 +2014-04-08T01:02:23.017668Z +K 7 +svn:log +V 38 +copy stuff from the past before change +PROPS-END + +Node-path: prefix/trunk/changed2 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/changed +Text-copy-source-md5: ec1bebaea2c042beb68f7679ddd106a4 +Text-copy-source-sha1: 2f6933b5ee0f5fdd823d9717d8729f3c2523811b + + +Node-path: prefix/trunk/changed3 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/changed +Text-copy-source-md5: ec1bebaea2c042beb68f7679ddd106a4 +Text-copy-source-sha1: 2f6933b5ee0f5fdd823d9717d8729f3c2523811b +Text-content-length: 17 +Text-content-md5: 7d93e8c4d61c2a7b05c20b7d8bf11f83 +Text-content-sha1: d78c3e7f04c44b599787ec534a3193357df1fa37 +Content-length: 17 + +changed +changed3 + + +Node-path: prefix/trunk/changeddir2 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/changeddir + + +Node-path: prefix/trunk/changedlink2 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/changedlink +Text-copy-source-md5: d91fb1e1062e62a17f97b44932d454c4 +Text-copy-source-sha1: 8c147187742f58ed0cd8707ddd0c0942fe8b2616 + + +Node-path: prefix/trunk/changedlink3 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/changedlink +Text-copy-source-md5: d91fb1e1062e62a17f97b44932d454c4 +Text-copy-source-sha1: 8c147187742f58ed0cd8707ddd0c0942fe8b2616 +Text-content-length: 13 +Text-content-md5: 0ad81e7d7fc35e744e0ad3dea0f158fe +Text-content-sha1: bae4618ecddda5e6ab6e5fa3b116f5d14dc3464b +Content-length: 13 + +link changed3 + +Revision-number: 10 +Prop-content-length: 140 +Content-length: 140 + +K 10 +svn:author +V 10 +dschleimer +K 8 +svn:date +V 27 +2014-04-08T01:02:23.111440Z +K 7 +svn:log +V 34 +copy unchanged stuff from the past +PROPS-END + +Node-path: prefix/trunk/unchanged2 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/unchanged +Text-copy-source-md5: 85ae5b04dd0a666efad8633d142a4635 +Text-copy-source-sha1: c654a5435f170cfad37e136fee9672ecc40afd4a + + +Node-path: prefix/trunk/unchangeddir2 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/unchangeddir + + +Node-path: prefix/trunk/unchangedlink2 +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/unchangedlink +Text-copy-source-md5: 1aa9c01278c74a273e3117dc42426153 +Text-copy-source-sha1: 3f1a6ab03b820f0baab56c083f77bd9279db2486 + + +Revision-number: 11 +Prop-content-length: 133 +Content-length: 133 + +K 10 +svn:author +V 10 +dschleimer +K 8 +svn:date +V 27 +2014-04-08T01:02:23.179096Z +K 7 +svn:log +V 27 +copy groupdir from the past +PROPS-END + +Node-path: prefix/trunk/groupdir2 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: prefix/trunk/groupdir + + diff --git a/tests/test_fetch_branches.py b/tests/test_fetch_branches.py --- a/tests/test_fetch_branches.py +++ b/tests/test_fetch_branches.py @@ -50,9 +50,7 @@ class TestFetchBranches(test_util.TestBa self.assertEqual(['a', 'c', 'z'], sorted(r.manifest())) def test_renamed_branch_to_trunk(self): - config = {'hgsubversion.failonmissing': 'true'} - repo = self._load_fixture_and_fetch('branch_rename_to_trunk.svndump', - config=config) + repo = self._load_fixture_and_fetch('branch_rename_to_trunk.svndump') self.assertEqual(repo['default'].parents()[0].branch(), 'dev_branch') self.assert_('iota' in repo['default']) self.assertEqual(repo['old_trunk'].parents()[0].branch(), 'default') @@ -73,6 +71,15 @@ class TestFetchBranches(test_util.TestBa self.assertEqual(repo['test'].extra().get('close'), '1') self.assertEqual(repo['test']['b'].data(), 'a\n') + def test_copyafterclose(self): + repo = self._load_fixture_and_fetch('copyafterclose.svndump') + self.assertEqual(repo['tip'].branch(), 'test') + self.assert_('file' in repo['test']) + self.assertEqual(repo['test']['file'].data(), 'trunk2\n') + self.assert_('dir/file' in repo['test']) + self.assertEqual(repo['test']['dir/file'].data(), 'trunk2\n') + + def test_branch_create_with_dir_delete_works(self): repo = self._load_fixture_and_fetch('branch_create_with_dir_delete.svndump') self.assertEqual(repo['tip'].manifest().keys(), diff --git a/tests/test_fetch_mappings.py b/tests/test_fetch_mappings.py --- a/tests/test_fetch_mappings.py +++ b/tests/test_fetch_mappings.py @@ -84,10 +84,15 @@ class MapTests(test_util.TestBase): def test_author_map_no_overwrite(self): cwd = os.path.dirname(__file__) orig = os.path.join(cwd, 'fixtures', 'author-map-test.txt') - new = open(self.authors, 'w') + # create a fake hgsubversion repo + repopath = os.path.join(self.wc_path, '.hg') + repopath = os.path.join(repopath, 'svn') + if not os.path.isdir(repopath): + os.makedirs(repopath) + new = open(os.path.join(repopath, 'authors'), 'w') new.write(open(orig).read()) new.close() - test = maps.AuthorMap(self.ui(), self.authors) + test = maps.AuthorMap(self.repo.svnmeta(skiperrorcheck=True)) fromself = set(test) test.load(orig) all_tests = set(test) @@ -285,21 +290,6 @@ class MapTests(test_util.TestBase): for r in repo: self.assertEquals(verify.verify(ui, repo, rev=r), 0) - def test_branchmap_no_replacement(self): - ''' - test that empty mappings are rejected - - Empty mappings are lines like 'this ='. The most sensible thing to do - is to not convert the 'this' branches. Until we can do that, we settle - with aborting. - ''' - repo_path = self.load_svndump('propset-branch.svndump') - branchmap = open(self.branchmap, 'w') - branchmap.write("closeme =\n") - branchmap.close() - self.assertRaises(hgutil.Abort, - maps.BranchMap, self.ui(), self.branchmap) - def test_tagmap(self): repo_path = self.load_svndump('basic_tag_tests.svndump') tagmap = open(self.tagmap, 'w') diff --git a/tests/test_fetch_renames.py b/tests/test_fetch_renames.py --- a/tests/test_fetch_renames.py +++ b/tests/test_fetch_renames.py @@ -18,29 +18,56 @@ class TestFetchRenames(test_util.TestBas def test_rename(self): config = { 'hgsubversion.filestoresize': '0', + # we set this because we expect all of the copies to be + # handled via replay, and we want to notice if that + # changes. + 'hgsubversion.failonmissing': 'yes', } repo = self._load_fixture_and_fetch('renames.svndump', config=config) + self._run_assertions(repo) + def test_rename_with_prefix(self): + config = { + 'hgsubversion.filestoresize': '0', + 'hgsubversion.failonmissing': 'yes', + } + repo = self._load_fixture_and_fetch('renames_with_prefix.svndump', + subdir='prefix', + config=config) + self._run_assertions(repo) + + def _run_assertions(self, repo): # Map revnum to mappings of dest name to (source name, dest content) copies = { 4: { 'a1': ('a', 'a\n'), + 'linka1': ('linka', 'a'), 'a2': ('a', 'a\n'), + 'linka2': ('linka', 'a'), 'b1': ('b', 'b\nc\n'), + 'linkb1': ('linkb', 'bc'), 'da1/daf': ('da/daf', 'c\n'), + 'da1/dalink': ('da/dalink', 'daf'), 'da1/db/dbf': ('da/db/dbf', 'd\n'), + 'da1/db/dblink': ('da/db/dblink', '../daf'), 'da2/daf': ('da/daf', 'c\n'), + 'da2/dalink': ('da/dalink', 'daf'), 'da2/db/dbf': ('da/db/dbf', 'd\n'), + 'da2/db/dblink': ('da/db/dblink', '../daf'), }, 5: { 'c1': ('c', 'c\nc\n'), + 'linkc1': ('linkc', 'cc'), }, 9: { 'unchanged2': ('unchanged', 'unchanged\n'), + 'unchangedlink2': ('unchangedlink', 'unchanged'), 'unchangeddir2/f': ('unchangeddir/f', 'unchanged2\n'), + 'unchangeddir2/link': ('unchangeddir/link', 'f'), }, 10: { - 'groupdir2/b': ('groupdir/b', 'b\n') + 'groupdir2/b': ('groupdir/b', 'b\n'), + 'groupdir2/linkb': ('groupdir/linkb', 'b'), }, } for rev in repo: diff --git a/tests/test_push_command.py b/tests/test_push_command.py --- a/tests/test_push_command.py +++ b/tests/test_push_command.py @@ -373,7 +373,7 @@ class PushTests(test_util.TestBase): def test_delete_file(self): repo = self.repo def file_callback(repo, memctx, path): - raise IOError(errno.ENOENT, '%s is deleted' % path) + return compathacks.filectxfn_deleted(memctx, path) old_files = set(repo['default'].manifest().keys()) ctx = context.memctx(repo, (repo['default'].node(), node.nullid), @@ -552,7 +552,7 @@ class PushTests(test_util.TestBase): copied=False) ctx = context.memctx(repo, (repo['default'].node(), node.nullid), - 'message', + 'mutate already-special file alpha', ['alpha', ], file_callback2, 'author', @@ -577,7 +577,7 @@ class PushTests(test_util.TestBase): copied=False) ctx = context.memctx(repo, (repo['default'].node(), node.nullid), - 'message', + 'convert alpha back to regular file', ['alpha', ], file_callback3, 'author', @@ -754,3 +754,20 @@ class PushTests(test_util.TestBase): self.assertEqual(tip['adding_file'].data(), 'fooFirstFile') self.assertEqual(tip['newdir/new_file'].data(), 'fooNewFile') self.assertEqual(tip.branch(), 'default') + + def test_update_after_push(self): + repo = self.repo + ui = repo.ui + + ui.setconfig('hooks', + 'debug-hgsubversion-between-push-and-pull-for-tests', + lambda ui, repo, hooktype: self.add_svn_rev( + self.repo_path, + {'trunk/racey_file': 'race conditions suck'})) + + self.test_push_to_branch(push=False) + commands.push(ui, repo) + newctx = self.repo['.'] + self.assertNotEqual(newctx.node(), self.repo['tip'].node()) + self.assertEqual(newctx['adding_file'].data(), 'foo') + self.assertEqual(newctx.branch(), 'the_branch') diff --git a/tests/test_tags.py b/tests/test_tags.py --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -175,6 +175,8 @@ rename a tag repo = self._load_fixture_and_fetch('tag_name_same_as_branch.svndump') tm = os.path.join(repo.path, 'svn', 'tagmap') open(tm, 'w').write('1\n') + # force tags to load since it is lazily loaded when needed + repo.svnmeta().tags commands.pull(repo.ui, repo) self.assertEqual(open(tm).read().splitlines()[0], '2') diff --git a/tests/test_util.py b/tests/test_util.py --- a/tests/test_util.py +++ b/tests/test_util.py @@ -24,7 +24,7 @@ from mercurial import i18n from mercurial import node from mercurial import scmutil from mercurial import ui -from mercurial import util +from mercurial import util as hgutil from mercurial import extensions from hgsubversion import compathacks @@ -108,6 +108,7 @@ subdir = {'truncatedhistory.svndump': '/ 'non_ascii_path_1.svndump': '/b\xC3\xB8b', 'non_ascii_path_2.svndump': '/b%C3%B8b', 'subdir_is_file_prefix.svndump': '/flaf', + 'renames_with_prefix.svndump': '/prefix', } # map defining the layouts of the fixtures we can use with custom layout # these are really popular layouts, so I gave them names @@ -138,6 +139,10 @@ custom = { 'old_trunk': 'branches/old_trunk', }, 'copies.svndump': trunk_only, + 'copyafterclose.svndump': { + 'default': 'trunk', + 'test': 'branches/test' + }, 'copybeforeclose.svndump': { 'default': 'trunk', 'test': 'branches/test' @@ -159,6 +164,10 @@ custom = { 'default': 'trunk', 'branch1': 'branches/branch1', }, + 'renames_with_prefix.svndump': { + 'default': 'trunk', + 'branch1': 'branches/branch1', + }, 'replace_branch_with_branch.svndump': { 'default': 'trunk', 'branch1': 'branches/branch1', @@ -445,9 +454,15 @@ class TestBase(unittest.TestCase): self.oldenv = dict([(k, os.environ.get(k, None),) for k in ('LANG', 'LC_ALL', 'HGRCPATH',)]) - self.oldt = i18n.t - os.environ['LANG'] = os.environ['LC_ALL'] = 'C' - i18n.t = gettext.translation('hg', i18n.localedir, fallback=True) + try: + self.oldugettext = i18n._ugettext # Mercurial >= 3.2 + except AttributeError: + self.oldt = i18n.t + os.environ['LANG'] = os.environ['LC_ALL'] = 'C' + i18n.t = gettext.translation('hg', i18n.localedir, fallback=True) + else: + os.environ['LANG'] = os.environ['LC_ALL'] = 'C' + i18n.setdatapath(hgutil.datapath) self.oldwd = os.getcwd() self.tmpdir = tempfile.mkdtemp( @@ -494,7 +509,10 @@ class TestBase(unittest.TestCase): del os.environ[var] else: os.environ[var] = val - i18n.t = self.oldt + try: + i18n._ugettext = self.oldugettext # Mercurial >= 3.2 + except AttributeError: + i18n.t = self.oldt rmtree(self.tmpdir) os.chdir(self.oldwd) setattr(ui.ui, self.patch[0].func_name, self.patch[0]) @@ -658,8 +676,7 @@ class TestBase(unittest.TestCase): def filectxfn(repo, memctx, path): if path in removed: - raise IOError(errno.ENOENT, - "File \"%s\" no longer exists" % path) + return compathacks.filectxfn_deleted(memctx, path) entry = [e for e in changes if path == e[1]][0] source, dest, newdata = entry if newdata is None: